summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
authorPrabhu Ramachandran2018-01-02 18:07:39 +0530
committerGitHub2018-01-02 18:07:39 +0530
commite566d54239efcb46f253e324b7295a676378f656 (patch)
tree3a93f3305c8b72dc2052ca33837b9b3dadb5d7af /yaksh
parent29a06f7372690796a05262fac6c428580e1f3155 (diff)
parent9bfdc506c3a54835ba9a1cd1fb70e9c2f825f0fb (diff)
downloadonline_test-e566d54239efcb46f253e324b7295a676378f656.tar.gz
online_test-e566d54239efcb46f253e324b7295a676378f656.tar.bz2
online_test-e566d54239efcb46f253e324b7295a676378f656.zip
Merge pull request #401 from FOSSEE/yaksh_video_lessons
Yaksh Learning Module
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/evaluator_tests/test_simple_question_types.py6
-rw-r--r--yaksh/forms.py238
-rw-r--r--yaksh/live_server_tests/load_test.py41
-rw-r--r--yaksh/live_server_tests/selenium_test.py51
-rw-r--r--yaksh/models.py684
-rw-r--r--yaksh/static/yaksh/css/design_course.css17
-rw-r--r--yaksh/static/yaksh/js/add_quiz.js14
-rw-r--r--yaksh/static/yaksh/js/course.js3
-rw-r--r--yaksh/static/yaksh/js/design_course.js26
-rw-r--r--yaksh/static/yaksh/js/edit_question.js10
-rw-r--r--yaksh/static/yaksh/js/lesson.js78
-rw-r--r--yaksh/static/yaksh/js/requesthandler.js2
-rw-r--r--yaksh/templates/exam.html11
-rw-r--r--yaksh/templates/manage.html32
-rw-r--r--yaksh/templates/user.html9
-rw-r--r--yaksh/templates/yaksh/add_course.html6
-rw-r--r--yaksh/templates/yaksh/add_lesson.html74
-rw-r--r--yaksh/templates/yaksh/add_module.html163
-rw-r--r--yaksh/templates/yaksh/add_quiz.html13
-rw-r--r--yaksh/templates/yaksh/complete.html27
-rw-r--r--yaksh/templates/yaksh/course_detail.html10
-rw-r--r--yaksh/templates/yaksh/course_modules.html112
-rw-r--r--yaksh/templates/yaksh/courses.html576
-rw-r--r--yaksh/templates/yaksh/design_course_session.html125
-rw-r--r--yaksh/templates/yaksh/grade_user.html12
-rw-r--r--yaksh/templates/yaksh/intro.html25
-rw-r--r--yaksh/templates/yaksh/moderator_dashboard.html56
-rw-r--r--yaksh/templates/yaksh/monitor.html10
-rw-r--r--yaksh/templates/yaksh/question.html41
-rw-r--r--yaksh/templates/yaksh/quit.html9
-rw-r--r--yaksh/templates/yaksh/quizzes_user.html53
-rw-r--r--yaksh/templates/yaksh/regrade.html4
-rw-r--r--yaksh/templates/yaksh/show_video.html90
-rw-r--r--yaksh/templates/yaksh/statistics_question.html2
-rw-r--r--yaksh/templates/yaksh/user_data.html13
-rw-r--r--yaksh/templatetags/custom_filters.py21
-rw-r--r--yaksh/test_models.py399
-rw-r--r--yaksh/test_views.py1069
-rw-r--r--yaksh/urls.py88
-rw-r--r--yaksh/views.py852
40 files changed, 3899 insertions, 1173 deletions
diff --git a/yaksh/evaluator_tests/test_simple_question_types.py b/yaksh/evaluator_tests/test_simple_question_types.py
index c0b81d6..b86a9d8 100644
--- a/yaksh/evaluator_tests/test_simple_question_types.py
+++ b/yaksh/evaluator_tests/test_simple_question_types.py
@@ -26,8 +26,7 @@ def setUpModule():
tzinfo=pytz.utc),
duration=30, active=True, attempts_allowed=1,
time_between_attempts=0, description='demo quiz 100',
- pass_criteria=0,language='Python',
- prerequisite=None,course=course,
+ pass_criteria=0,
instructions="Demo Instructions"
)
question_paper = QuestionPaper.objects.create(quiz=quiz,
@@ -38,7 +37,8 @@ def setUpModule():
question_paper=question_paper,
end_time=timezone.now()
+timedelta(minutes=5),
- attempt_number=1
+ attempt_number=1,
+ course=course
)
def tearDownModule():
diff --git a/yaksh/forms.py b/yaksh/forms.py
index 2740497..52e6a12 100644
--- a/yaksh/forms.py
+++ b/yaksh/forms.py
@@ -1,15 +1,11 @@
from django import forms
-from yaksh.models import get_model_class, Profile, Quiz, Question, TestCase, Course,\
- QuestionPaper, StandardTestCase, StdIOBasedTestCase, \
- HookTestCase, IntegerTestCase, StringTestCase
+from yaksh.models import (
+ get_model_class, Profile, Quiz, Question, Course, QuestionPaper, Lesson,
+ LearningModule
+)
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
from django.conf import settings
-from taggit.managers import TaggableManager
-from taggit.forms import TagField
-from django.forms.models import inlineformset_factory
-from django.db.models import Q
from django.utils import timezone
from textwrap import dedent
try:
@@ -17,7 +13,6 @@ try:
except ImportError:
from string import ascii_letters as letters
from string import punctuation, digits
-import datetime
import pytz
from .send_emails import generate_activation_key
@@ -29,7 +24,7 @@ languages = (
("cpp", "C++ Language"),
("java", "Java Language"),
("scilab", "Scilab"),
- )
+)
question_types = (
("select", "Select Question Type"),
@@ -40,17 +35,17 @@ question_types = (
("integer", "Answer in Integer"),
("string", "Answer in String"),
("float", "Answer in Float"),
- )
+)
test_case_types = (
- ("standardtestcase", "Standard Testcase"),
- ("stdiobasedtestcase", "StdIO Based Testcase"),
- ("mcqtestcase", "MCQ Testcase"),
- ("hooktestcase", "Hook Testcase"),
- ("integertestcase", "Integer Testcase"),
- ("stringtestcase", "String Testcase"),
- ("floattestcase", "Float Testcase"),
- )
+ ("standardtestcase", "Standard Testcase"),
+ ("stdiobasedtestcase", "StdIO Based Testcase"),
+ ("mcqtestcase", "MCQ Testcase"),
+ ("hooktestcase", "Hook Testcase"),
+ ("integertestcase", "Integer Testcase"),
+ ("stringtestcase", "String Testcase"),
+ ("floattestcase", "Float Testcase"),
+)
UNAME_CHARS = letters + "._" + digits
PWD_CHARS = letters + punctuation + digits
@@ -59,8 +54,10 @@ attempts = [(i, i) for i in range(1, 6)]
attempts.append((-1, 'Infinite'))
days_between_attempts = ((j, j) for j in range(401))
-def get_object_form(model, exclude_fields=None):
+
+def get_object_form(model, exclude_fields=None):
model_class = get_model_class(model)
+
class _ObjectForm(forms.ModelForm):
class Meta:
model = model_class
@@ -77,20 +74,21 @@ class UserRegisterForm(forms.Form):
period and underscores only.')
email = forms.EmailField()
password = forms.CharField(max_length=30, widget=forms.PasswordInput())
- confirm_password = forms.CharField\
- (max_length=30, widget=forms.PasswordInput())
+ confirm_password = forms.CharField(
+ max_length=30, widget=forms.PasswordInput())
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
- roll_number = forms.CharField\
- (max_length=30, help_text="Use a dummy if you don't have one.")
- institute = forms.CharField\
- (max_length=128, help_text='Institute/Organization')
- department = forms.CharField\
- (max_length=64, help_text='Department you work/study at')
- position = forms.CharField\
- (max_length=64, help_text='Student/Faculty/Researcher/Industry/etc.')
- timezone = forms.ChoiceField(choices=[(tz, tz) for tz in pytz.common_timezones],
- initial=pytz.utc)
+ roll_number = forms.CharField(
+ max_length=30, help_text="Use a dummy if you don't have one.")
+ institute = forms.CharField(
+ max_length=128, help_text='Institute/Organization')
+ department = forms.CharField(
+ max_length=64, help_text='Department you work/study at')
+ position = forms.CharField(
+ max_length=64, help_text='Student/Faculty/Researcher/Industry/etc.')
+ timezone = forms.ChoiceField(
+ choices=[(tz, tz) for tz in pytz.common_timezones],
+ initial=pytz.utc)
def clean_username(self):
u_name = self.cleaned_data["username"]
@@ -146,9 +144,10 @@ class UserRegisterForm(forms.Form):
if settings.IS_DEVELOPMENT:
new_profile.is_email_verified = True
else:
- new_profile.activation_key = generate_activation_key(new_user.username)
- new_profile.key_expiry_time = timezone.now() + \
- timezone.timedelta(minutes=20)
+ new_profile.activation_key = generate_activation_key(
+ new_user.username)
+ new_profile.key_expiry_time = timezone.now() + timezone.timedelta(
+ minutes=20)
new_profile.save()
return u_name, pwd, new_user.email, new_profile.activation_key
@@ -166,8 +165,9 @@ class UserLoginForm(forms.Form):
self.cleaned_data["password"]
user = authenticate(username=u_name, password=pwd)
except Exception:
- raise forms.ValidationError\
- ("Username and/or Password is not entered")
+ raise forms.ValidationError(
+ "Username and/or Password is not entered"
+ )
if not user:
raise forms.ValidationError("Invalid username/password")
return user
@@ -178,82 +178,38 @@ class QuizForm(forms.ModelForm):
It has the related fields and functions required."""
def __init__(self, *args, **kwargs):
- user = kwargs.pop('user')
- course_id = kwargs.pop('course')
super(QuizForm, self).__init__(*args, **kwargs)
- prerequisite_list = Quiz.objects.filter(
- course__id=course_id,
- is_trial=False
- ).exclude(id=self.instance.id)
-
- self.fields['prerequisite'] = forms.ModelChoiceField(prerequisite_list)
- self.fields['prerequisite'].required = False
- self.fields['course'] = forms.ModelChoiceField(
- queryset=Course.objects.filter(id=course_id), empty_label=None)
- self.fields["instructions"].initial = 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 clean_prerequisite(self):
- prereq = self.cleaned_data['prerequisite']
- if prereq and prereq.prerequisite:
- if prereq.prerequisite.id == self.instance.id:
- raise forms.ValidationError("Please set another prerequisite quiz")
- return prereq
+ self.fields["instructions"].initial = 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>
+ """)
class Meta:
model = Quiz
- exclude = ["is_trial"]
+ exclude = ["is_trial", "creator"]
class QuestionForm(forms.ModelForm):
@@ -266,15 +222,19 @@ class QuestionForm(forms.ModelForm):
class FileForm(forms.Form):
- file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}),
- required=False)
+ file_field = forms.FileField(widget=forms.ClearableFileInput(
+ attrs={'multiple': True}),
+ required=False)
class RandomQuestionForm(forms.Form):
- question_type = forms.CharField(max_length=8, widget=forms.Select\
- (choices=question_types))
- marks = forms.CharField(max_length=8, widget=forms.Select\
- (choices=(('select', 'Select Marks'),)))
+ question_type = forms.CharField(
+ max_length=8, widget=forms.Select(choices=question_types)
+ )
+ marks = forms.CharField(
+ max_length=8, widget=forms.Select(
+ choices=(('select', 'Select Marks'),))
+ )
shuffle_questions = forms.BooleanField(required=False)
@@ -286,13 +246,14 @@ class QuestionFilterForm(forms.Form):
points_list = questions.values_list('points', flat=True).distinct()
points_options = [(None, 'Select Marks')]
points_options.extend([(point, point) for point in points_list])
- self.fields['marks'] = forms.FloatField(widget=forms.Select\
- (choices=points_options))
-
- language = forms.CharField(max_length=8, widget=forms.Select\
- (choices=languages))
- question_type = forms.CharField(max_length=8, widget=forms.Select\
- (choices=question_types))
+ self.fields['marks'] = forms.FloatField(
+ widget=forms.Select(choices=points_options)
+ )
+ language = forms.CharField(
+ max_length=8, widget=forms.Select(choices=languages))
+ question_type = forms.CharField(
+ max_length=8, widget=forms.Select(choices=question_types)
+ )
class CourseForm(forms.ModelForm):
@@ -311,8 +272,8 @@ class CourseForm(forms.ModelForm):
class Meta:
model = Course
- exclude = ['creator', 'requests', 'students', 'rejected',
- 'created_on', 'is_trial', 'hidden', 'teachers']
+ fields = ['name', 'enrollment', 'active', 'code', 'instructions',
+ 'start_enroll_time', 'end_enroll_time']
class ProfileForm(forms.ModelForm):
@@ -342,3 +303,34 @@ class QuestionPaperForm(forms.ModelForm):
class Meta:
model = QuestionPaper
fields = ['shuffle_questions']
+
+
+class LessonForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ super(LessonForm, self).__init__(*args, **kwargs)
+ des_msg = "Enter Lesson Description as Markdown text"
+ name_msg = "Enter Lesson Name"
+ self.fields['description'].widget.attrs['placeholder'] = des_msg
+ self.fields['name'].widget.attrs['placeholder'] = name_msg
+
+ class Meta:
+ model = Lesson
+ exclude = ['completed_lessons', 'creator', 'html_data']
+
+
+class LessonFileForm(forms.Form):
+ Lesson_files = forms.FileField(widget=forms.ClearableFileInput(
+ attrs={'multiple': True}),
+ required=False)
+
+
+class LearningModuleForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ super(LearningModuleForm, self).__init__(*args, **kwargs)
+ name_msg = "Enter Learning Module Name"
+ self.fields['name'].widget.attrs['placeholder'] = name_msg
+ self.fields['name'].widget.attrs['size'] = 30
+
+ class Meta:
+ model = LearningModule
+ fields = ['name', 'description']
diff --git a/yaksh/live_server_tests/load_test.py b/yaksh/live_server_tests/load_test.py
index 5ab1cc2..520bebe 100644
--- a/yaksh/live_server_tests/load_test.py
+++ b/yaksh/live_server_tests/load_test.py
@@ -1,17 +1,10 @@
-import os
-import signal
-import subprocess
-from datetime import datetime
-import pytz
from threading import Thread
-from selenium.webdriver.firefox.webdriver import WebDriver
-
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
-from yaksh.models import User, Profile, Question, Quiz, Course, QuestionPaper, TestCase
+# Local imports
+from yaksh.models import User, Profile, Course
from yaksh.code_server import ServerPool
from yaksh import settings
-
from .selenium_test import SeleniumTest
@@ -33,20 +26,24 @@ class YakshSeleniumTests(StaticLiveServerTestCase):
cls.code_server_thread = t = Thread(target=code_server_pool.run)
t.start()
- cls.demo_student = User.objects.create_user(username='demo_student',
- password='demo_student',
- email='demo_student@test.com'
+ cls.demo_student = User.objects.create_user(
+ username='demo_student',
+ password='demo_student',
+ email='demo_student@test.com'
)
- cls.demo_student_profile = Profile.objects.create(user=cls.demo_student,
+ cls.demo_student_profile = Profile.objects.create(
+ user=cls.demo_student,
roll_number=3, institute='IIT',
department='Chemical', position='Student'
)
- cls.demo_mod = User.objects.create_user(username='demo_mod',
- password='demo_mod',
- email='demo_mod@test.com'
+ cls.demo_mod = User.objects.create_user(
+ username='demo_mod',
+ password='demo_mod',
+ email='demo_mod@test.com'
)
- cls.demo_mod_profile = Profile.objects.create(user=cls.demo_mod,
+ cls.demo_mod_profile = Profile.objects.create(
+ user=cls.demo_mod,
roll_number=0, institute='IIT',
department='Chemical', position='Moderator'
)
@@ -73,5 +70,11 @@ class YakshSeleniumTests(StaticLiveServerTestCase):
def test_load(self):
url = '%s%s' % (self.live_server_url, '/exam/login/')
quiz_name = "Yaksh Demo quiz"
- selenium_test = SeleniumTest(url=url, quiz_name=quiz_name)
- selenium_test.run_load_test(url=url, username='demo_student', password='demo_student')
+ module_name = "Demo Module"
+ course_name = "Yaksh Demo course"
+ selenium_test = SeleniumTest(url=url, quiz_name=quiz_name,
+ module_name=module_name,
+ course_name=course_name)
+ selenium_test.run_load_test(
+ url=url, username='demo_student', password='demo_student'
+ )
diff --git a/yaksh/live_server_tests/selenium_test.py b/yaksh/live_server_tests/selenium_test.py
index 31efcac..5635add 100644
--- a/yaksh/live_server_tests/selenium_test.py
+++ b/yaksh/live_server_tests/selenium_test.py
@@ -1,9 +1,7 @@
from selenium import webdriver
-from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
-from selenium.common.exceptions import WebDriverException
import multiprocessing
import argparse
@@ -18,17 +16,20 @@ class ElementDisplay(object):
try:
element = EC._find_element(driver, self.locator)
return element.value_of_css_property("display") == "none"
- except Exception as e:
+ except Exception:
return False
class SeleniumTestError(Exception):
pass
+
class SeleniumTest():
- def __init__(self, url, quiz_name):
+ def __init__(self, url, quiz_name, module_name, course_name):
self.driver = webdriver.Firefox()
self.quiz_name = quiz_name
+ self.module_name = module_name
+ self.course_name = course_name
self.url = url
def run_load_test(self, url, username, password):
@@ -44,8 +45,8 @@ class SeleniumTest():
except Exception as e:
self.driver.close()
msg = ("An Error occurred while running the Selenium load"
- " test on Yaksh!\n"
- "Error:\n{0}".format(e))
+ " test on Yaksh!\n"
+ "Error:\n{0}".format(e))
raise SeleniumTestError(msg)
@@ -53,7 +54,8 @@ class SeleniumTest():
# get the username, password and submit form elements
username_elem = self.driver.find_element_by_id("id_username")
password_elem = self.driver.find_element_by_id("id_password")
- submit_login_elem = self.driver.find_element_by_css_selector('button.btn')
+ submit_login_elem = self.driver.find_element_by_css_selector(
+ 'button.btn')
# Type in the username, password and submit form
username_elem.send_keys(username)
@@ -65,7 +67,8 @@ class SeleniumTest():
for count in range(loop_count):
self.driver.find_element_by_link_text(question_label).click()
submit_answer_elem = self.driver.find_element_by_id("check")
- self.driver.execute_script('global_editor.editor.setValue({});'.format(answer))
+ self.driver.execute_script(
+ 'global_editor.editor.setValue({});'.format(answer))
submit_answer_elem.click()
WebDriverWait(self.driver, 90).until(ElementDisplay(
(By.XPATH, "//*[@id='ontop']")))
@@ -119,9 +122,13 @@ class SeleniumTest():
self.submit_answer(question_label, answer, loop_count)
def open_quiz(self):
+ # open module link
+ self.driver.find_elements_by_partial_link_text(
+ self.course_name)[0].click()
+ self.driver.find_element_by_link_text(self.module_name).click()
# open quiz link
- quiz_link_elem = self.driver.find_element_by_link_text(self.quiz_name).click()
-
+ self.driver.find_element_by_link_text(self.quiz_name).click()
+
# Get page elements
start_exam_elem = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.NAME, "start"))
@@ -155,8 +162,10 @@ class SeleniumTest():
)
logout_link_elem.click()
+
def user_gen(url, ids):
- return [(url, 'User%d'%x, 'User%d'%x) for x in ids]
+ return [(url, 'User%d' % x, 'User%d' % x) for x in ids]
+
def wrap_run_load_test(args):
url = "http://yaksh.fossee.aero.iitb.ac.in/exam/"
@@ -165,15 +174,25 @@ def wrap_run_load_test(args):
if __name__ == '__main__':
parser = argparse.ArgumentParser()
- parser.add_argument('url', type=str, help="url of the website being tested")
+ parser.add_argument(
+ 'url', type=str, help="url of the website being tested"
+ )
parser.add_argument('start', type=int, help="Starting user id")
- parser.add_argument("-n", "--number", type=int, default=10, help="number of users")
+ parser.add_argument(
+ "-n", "--number", type=int, default=10, help="number of users"
+ )
opts = parser.parse_args()
quiz_name = "Demo quiz"
- selenium_test = SeleniumTest(url=opts.url, quiz_name=quiz_name)
+ module_name = "Demo Module"
+ course_name = "Yaksh Demo course"
+ selenium_test = SeleniumTest(url=opts.url, quiz_name=quiz_name,
+ module_name=module_name,
+ course_name=course_name)
pool = multiprocessing.Pool(opts.number)
- pool.map(wrap_run_load_test, user_gen(opts.url, range(opts.start, opts.start + opts.number)))
+ pool.map(
+ wrap_run_load_test,
+ user_gen(opts.url, range(opts.start, opts.start + opts.number))
+ )
pool.close()
pool.join()
-
diff --git a/yaksh/models.py b/yaksh/models.py
index 7e97e6c..5eca3d1 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -121,6 +121,12 @@ def dict_to_yaml(dictionary):
allow_unicode=True,
)
+
+def get_file_dir(instance, filename):
+ upload_dir = instance.lesson.name.replace(" ", "_")
+ return os.sep.join((upload_dir, filename))
+
+
###############################################################################
class CourseManager(models.Manager):
@@ -134,6 +140,349 @@ class CourseManager(models.Manager):
def get_hidden_courses(self, code):
return self.filter(code=code, hidden=True)
+
+#############################################################################
+class Lesson(models.Model):
+ # Lesson name
+ name = models.CharField(max_length=255)
+
+ # Markdown text of lesson content
+ description = models.TextField()
+
+ # Markdown text should be in html format
+ html_data = models.TextField(null=True, blank=True)
+
+ # Creator of the lesson
+ creator = models.ForeignKey(User)
+
+ def __str__(self):
+ return "{0}".format(self.name)
+
+ def get_files(self):
+ return LessonFile.objects.filter(lesson=self)
+
+
+#############################################################################
+class LessonFile(models.Model):
+ lesson = models.ForeignKey(Lesson, related_name="lesson")
+ file = models.FileField(upload_to=get_file_dir)
+
+ def remove(self):
+ if os.path.exists(self.file.path):
+ os.remove(self.file.path)
+ if os.listdir(os.path.dirname(self.file.path)) == []:
+ os.rmdir(os.path.dirname(self.file.path))
+ self.delete()
+
+
+###############################################################################
+class QuizManager(models.Manager):
+ def get_active_quizzes(self):
+ return self.filter(active=True, is_trial=False)
+
+ def create_trial_quiz(self, user):
+ """Creates a trial quiz for testing questions"""
+ trial_quiz = self.create(duration=1000,
+ description="trial_questions",
+ is_trial=True,
+ time_between_attempts=0,
+ creator=user
+ )
+ return trial_quiz
+
+ def create_trial_from_quiz(self, original_quiz_id, user, godmode,
+ course_id):
+ """Creates a trial quiz from existing quiz"""
+ trial_course_name = "Trial_course_for_course_{0}_{1}".format(
+ course_id, "godmode" if godmode else "usermode")
+ trial_quiz_name = "Trial_orig_id_{0}_{1}".format(
+ original_quiz_id,
+ "godmode" if godmode else "usermode"
+ )
+ # Get or create Trial Course for usermode/godmode
+ trial_course = Course.objects.filter(name=trial_course_name)
+ if trial_course.exists():
+ trial_course = trial_course.get(name=trial_course_name)
+ else:
+ trial_course = Course.objects.create(
+ name=trial_course_name, creator=user, enrollment="open",
+ is_trial=True)
+
+ # Get or create Trial Quiz for usermode/godmode
+ if self.filter(description=trial_quiz_name).exists():
+ trial_quiz = self.get(description=trial_quiz_name)
+
+ else:
+ trial_quiz = self.get(id=original_quiz_id)
+ trial_quiz.user = user
+ trial_quiz.pk = None
+ trial_quiz.description = trial_quiz_name
+ trial_quiz.is_trial = True
+ trial_quiz.prerequisite = None
+ if godmode:
+ trial_quiz.time_between_attempts = 0
+ trial_quiz.duration = 1000
+ trial_quiz.attempts_allowed = -1
+ trial_quiz.active = True
+ trial_quiz.start_date_time = timezone.now()
+ trial_quiz.end_date_time = datetime(
+ 2199, 1, 1, 0, 0, 0, 0, tzinfo=pytz.utc
+ )
+ trial_quiz.save()
+
+ # Get or create Trial Ordered Lesson for usermode/godmode
+ learning_modules = trial_course.get_learning_modules()
+ if learning_modules:
+ quiz = learning_modules[0].learning_unit.filter(quiz=trial_quiz)
+ if not quiz.exists():
+ trial_learning_unit = LearningUnit.objects.create(
+ order=1, quiz=trial_quiz, type="quiz",
+ check_prerequisite=False)
+ learning_modules[0].learning_unit.add(trial_learning_unit.id)
+ trial_learning_module = learning_modules[0]
+ else:
+ trial_learning_module = LearningModule.objects.create(
+ name="Trial for {}".format(trial_course), order=1,
+ check_prerequisite=False, creator=user, is_trial=True)
+ trial_learning_unit = LearningUnit.objects.create(
+ order=1, quiz=trial_quiz, type="quiz",
+ check_prerequisite=False)
+ trial_learning_module.learning_unit.add(trial_learning_unit.id)
+ trial_course.learning_module.add(trial_learning_module.id)
+
+ # Add user to trial_course
+ trial_course.enroll(False, user)
+ return trial_quiz, trial_course, trial_learning_module
+
+
+###############################################################################
+class Quiz(models.Model):
+ """A quiz that students will participate in. One can think of this
+ as the "examination" event.
+ """
+
+ # The start date of the quiz.
+ start_date_time = models.DateTimeField(
+ "Start Date and Time of the quiz",
+ default=timezone.now,
+ null=True
+ )
+
+ # The end date and time of the quiz
+ end_date_time = models.DateTimeField(
+ "End Date and Time of the quiz",
+ default=datetime(
+ 2199, 1, 1,
+ tzinfo=pytz.timezone(timezone.get_current_timezone_name())
+ ),
+ null=True
+ )
+
+ # This is always in minutes.
+ duration = models.IntegerField("Duration of quiz in minutes", default=20)
+
+ # Is the quiz active. The admin should deactivate the quiz once it is
+ # complete.
+ active = models.BooleanField(default=True)
+
+ # Description of quiz.
+ description = models.CharField(max_length=256)
+
+ # Mininum passing percentage condition.
+ pass_criteria = models.FloatField("Passing percentage", default=40)
+
+ # Number of attempts for the quiz
+ attempts_allowed = models.IntegerField(default=1, choices=attempts)
+
+ time_between_attempts = models.IntegerField(
+ "Number of Days", choices=days_between_attempts
+ )
+
+ is_trial = models.BooleanField(default=False)
+
+ instructions = models.TextField('Instructions for Students',
+ default=None, blank=True, null=True)
+
+ view_answerpaper = models.BooleanField('Allow student to view their answer\
+ paper', default=False)
+
+ allow_skip = models.BooleanField("Allow students to skip questions",
+ default=True)
+
+ weightage = models.FloatField(default=1.0)
+
+ creator = models.ForeignKey(User, null=True)
+
+ objects = QuizManager()
+
+ class Meta:
+ verbose_name_plural = "Quizzes"
+
+ def is_expired(self):
+ return not self.start_date_time <= timezone.now() < self.end_date_time
+
+ def create_demo_quiz(self, user):
+ demo_quiz = Quiz.objects.create(
+ start_date_time=timezone.now(),
+ end_date_time=timezone.now() + timedelta(176590),
+ duration=30, active=True,
+ attempts_allowed=-1, time_between_attempts=0,
+ description='Yaksh Demo quiz', pass_criteria=0,
+ creator=user
+ )
+ return demo_quiz
+
+ def get_total_students(self, course):
+ return AnswerPaper.objects.filter(
+ question_paper=self.questionpaper_set.get().id,
+ course=course
+ ).values_list("user", flat=True).distinct().count()
+
+ def get_passed_students(self, course):
+ return AnswerPaper.objects.filter(
+ question_paper=self.questionpaper_set.get().id,
+ course=course, passed=True
+ ).values_list("user", flat=True).distinct().count()
+
+ def get_failed_students(self, course):
+ return AnswerPaper.objects.filter(
+ question_paper=self.questionpaper_set.get().id,
+ course=course, passed=False
+ ).values_list("user", flat=True).distinct().count()
+
+ def __str__(self):
+ desc = self.description or 'Quiz'
+ return '%s: on %s for %d minutes' % (desc, self.start_date_time,
+ self.duration)
+
+
+##########################################################################
+class LearningUnit(models.Model):
+ """ Maintain order of lesson and quiz added in the course """
+ order = models.IntegerField()
+ type = models.CharField(max_length=16)
+ lesson = models.ForeignKey(Lesson, null=True, blank=True)
+ quiz = models.ForeignKey(Quiz, null=True, blank=True)
+ check_prerequisite = models.BooleanField(default=True)
+
+ def toggle_check_prerequisite(self):
+ if self.check_prerequisite:
+ self.check_prerequisite = False
+ else:
+ self.check_prerequisite = True
+
+ def get_completion_status(self, user, course):
+ course_status = CourseStatus.objects.filter(user=user, course=course)
+ state = "not attempted"
+ if course_status.exists():
+ if self in course_status.first().completed_units.all():
+ state = "completed"
+ elif course_status.first().current_unit == self:
+ state = "inprogress"
+ return state
+
+ def has_prerequisite(self):
+ return self.check_prerequisite
+
+ def is_prerequisite_passed(self, user, learning_module, course):
+ ordered_units = learning_module.learning_unit.order_by("order")
+ ordered_units_ids = list(ordered_units.values_list("id", flat=True))
+ current_unit_index = ordered_units_ids.index(self.id)
+ if current_unit_index == 0:
+ success = True
+ else:
+ prev_unit = ordered_units.get(
+ id=ordered_units_ids[current_unit_index-1])
+ status = prev_unit.get_completion_status(user, course)
+ if status == "completed":
+ success = True
+ else:
+ success = False
+ return success
+
+
+###############################################################################
+class LearningModule(models.Model):
+ """ Learning Module to maintain learning units"""
+ learning_unit = models.ManyToManyField(LearningUnit,
+ related_name="learning_unit")
+ name = models.CharField(max_length=255)
+ description = models.TextField(default=None, null=True, blank=True)
+ order = models.IntegerField(default=0)
+ creator = models.ForeignKey(User, related_name="module_creator")
+ check_prerequisite = models.BooleanField(default=True)
+ html_data = models.TextField(null=True, blank=True)
+ is_trial = models.BooleanField(default=False)
+
+ def get_quiz_units(self):
+ return [unit.quiz for unit in self.learning_unit.filter(
+ type="quiz")]
+
+ def get_learning_units(self):
+ return self.learning_unit.order_by("order")
+
+ def get_added_quiz_lesson(self):
+ learning_units = self.learning_unit.order_by("order")
+ added_quiz_lessons = []
+ if learning_units.exists():
+ for unit in learning_units:
+ if unit.type == "quiz":
+ added_quiz_lessons.append(("quiz", unit.quiz))
+ else:
+ added_quiz_lessons.append(("lesson", unit.lesson))
+ return added_quiz_lessons
+
+ def toggle_check_prerequisite(self):
+ self.check_prerequisite = not self.check_prerequisite
+
+ def get_next_unit(self, current_unit_id):
+ ordered_units = self.learning_unit.order_by("order")
+ ordered_units_ids = list(ordered_units.values_list("id", flat=True))
+ current_unit_index = ordered_units_ids.index(current_unit_id)
+ next_index = current_unit_index + 1
+ if next_index == len(ordered_units_ids):
+ next_index = 0
+ return ordered_units.get(id=ordered_units_ids[next_index])
+
+ def get_status(self, user, course):
+ """ Get module status if it completed, inprogress or not attempted"""
+ learning_module = course.learning_module.prefetch_related(
+ "learning_unit").get(id=self.id)
+ ordered_units = learning_module.learning_unit.order_by("order")
+ status_list = [unit.get_completion_status(user, course)
+ for unit in ordered_units]
+ if all([status == "completed" for status in status_list]):
+ return "completed"
+ elif "inprogress" in status_list:
+ return "inprogress"
+ else:
+ return "not attempted"
+
+ def is_prerequisite_passed(self, user, course):
+ """ Check if prerequisite module is completed """
+ ordered_modules = course.learning_module.order_by("order")
+ ordered_modules_ids = list(ordered_modules.values_list(
+ "id", flat=True))
+ current_module_index = ordered_modules_ids.index(self.id)
+ if current_module_index == 0:
+ success = True
+ else:
+ prev_module = ordered_modules.get(
+ id=ordered_modules_ids[current_module_index-1])
+ status = prev_module.get_status(user, course)
+ if status == "completed":
+ success = True
+ else:
+ success = False
+ return success
+
+ def has_prerequisite(self):
+ return self.check_prerequisite
+
+ def __str__(self):
+ return self.name
+
+
###############################################################################
class Course(models.Model):
""" Course for students"""
@@ -150,6 +499,8 @@ class Course(models.Model):
teachers = models.ManyToManyField(User, related_name='teachers')
is_trial = models.BooleanField(default=False)
instructions = models.TextField(default=None, null=True, blank=True)
+ learning_module = models.ManyToManyField(LearningModule,
+ related_name='learning_module')
# The start date of the course enrollment.
start_enroll_time = models.DateTimeField(
@@ -179,30 +530,12 @@ class Course(models.Model):
return new_course
def create_duplicate_course(self, user):
- quizzes = self.quiz_set.all()
- prerequisite_map = []
- duplicate_quiz_map = {}
+ learning_modules = self.learning_module.all()
new_course_name = "Copy Of {0}".format(self.name)
new_course = self._create_duplicate_instance(user, new_course_name)
- for q in quizzes:
- new_quiz = q._create_duplicate_quiz(new_course)
- if q.id not in duplicate_quiz_map.keys():
- duplicate_quiz_map[q.id] = new_quiz.id
-
- for orig_qid, dupl_qid in duplicate_quiz_map.items():
- original_quiz = Quiz.objects.get(id=orig_qid)
- if original_quiz.prerequisite:
- duplicate_quiz = Quiz.objects.get(id=dupl_qid)
- prereq_id = original_quiz.prerequisite.id
- duplicate_prereq_id = duplicate_quiz_map.get(prereq_id)
- if duplicate_prereq_id:
- duplicate_prerequisite = Quiz.objects.get(
- id=duplicate_prereq_id
- )
- duplicate_quiz.prerequisite = duplicate_prerequisite
- duplicate_quiz.save()
+ new_course.learning_module.add(*learning_modules)
return new_course
@@ -247,9 +580,6 @@ class Course(models.Model):
def is_self_enroll(self):
return True if self.enrollment == enrollment_methods[1][0] else False
- def get_quizzes(self):
- return self.quiz_set.filter(is_trial=False)
-
def activate(self):
self.active = True
@@ -266,18 +596,32 @@ class Course(models.Model):
self.teachers.remove(*teachers)
def create_demo(self, user):
- course = Course.objects.filter(creator=user, name="Yaksh Demo course")
+ course = Course.objects.filter(creator=user,
+ name="Yaksh Demo course").exists()
if not course:
course = Course.objects.create(name="Yaksh Demo course",
enrollment="open",
creator=user)
quiz = Quiz()
- demo_quiz = quiz.create_demo_quiz(course)
+ demo_quiz = quiz.create_demo_quiz(user)
demo_ques = Question()
demo_ques.create_demo_questions(user)
demo_que_ppr = QuestionPaper()
demo_que_ppr.create_demo_quiz_ppr(demo_quiz, user)
success = True
+ demo_lesson = Lesson.objects.create(
+ name="Demo lesson", description="demo lesson",
+ html_data="demo lesson", creator=user)
+ quiz_unit = LearningUnit.objects.create(
+ order=1, type="quiz", quiz=demo_quiz)
+ lesson_unit = LearningUnit.objects.create(
+ order=2, type="lesson", lesson=demo_lesson)
+ learning_module = LearningModule.objects.create(
+ name="demo module", description="demo module", creator=user,
+ html_data="demo module")
+ learning_module.learning_unit.add(quiz_unit)
+ learning_module.learning_unit.add(lesson_unit)
+ course.learning_module.add(learning_module)
else:
success = False
return success
@@ -288,11 +632,57 @@ class Course(models.Model):
students = self.students.exclude(id__in=teachers)
return students
+ def get_learning_modules(self):
+ return self.learning_module.order_by("order")
+
+ def get_unit_completion_status(self, module, user, unit):
+ course_module = self.learning_module.get(id=module.id)
+ learning_unit = course_module.learning_unit.get(id=unit.id)
+ return learning_unit.get_completion_status(user, self)
+
+ def get_quizzes(self):
+ learning_modules = self.learning_module.all()
+ quiz_list = []
+ for module in learning_modules:
+ quiz_list.extend(module.get_quiz_units())
+ return quiz_list
+
+ def get_quiz_details(self):
+ return [(quiz, quiz.get_total_students(self),
+ quiz.get_passed_students(self),
+ quiz.get_failed_students(self))
+ for quiz in self.get_quizzes()]
+
+ def get_learning_units(self):
+ learning_modules = self.learning_module.all()
+ learning_units = []
+ for module in learning_modules:
+ learning_units.extend(module.get_learning_units())
+ return learning_units
+
+ def remove_trial_modules(self):
+ learning_modules = self.learning_module.all()
+ for module in learning_modules:
+ module.learning_unit.all().delete()
+ learning_modules.delete()
+
def __str__(self):
return self.name
###############################################################################
+class CourseStatus(models.Model):
+ completed_units = models.ManyToManyField(LearningUnit,
+ related_name="completed_units")
+ current_unit = models.ForeignKey(LearningUnit, related_name="current_unit",
+ null=True, blank=True)
+ course = models.ForeignKey(Course)
+ user = models.ForeignKey(User)
+ grade = models.CharField(max_length=255, null=True, blank=True)
+ total_marks = models.FloatField(default=0.0)
+
+
+###############################################################################
class ConcurrentUser(models.Model):
concurrent_user = models.OneToOneField(User)
session_key = models.CharField(max_length=40)
@@ -621,165 +1011,6 @@ class Answer(models.Model):
###############################################################################
-class QuizManager(models.Manager):
- def get_active_quizzes(self):
- return self.filter(active=True, is_trial=False)
-
- def create_trial_quiz(self, trial_course, user):
- """Creates a trial quiz for testing questions"""
- trial_quiz = self.create(course=trial_course,
- duration=1000,
- description="trial_questions",
- is_trial=True,
- time_between_attempts=0
- )
- return trial_quiz
-
- def create_trial_from_quiz(self, original_quiz_id, user, godmode):
- """Creates a trial quiz from existing quiz"""
- trial_quiz_name = "Trial_orig_id_{0}_{1}".format(
- original_quiz_id,
- "godmode" if godmode else "usermode"
- )
-
- if self.filter(description=trial_quiz_name).exists():
- trial_quiz = self.get(description=trial_quiz_name)
-
- else:
- trial_quiz = self.get(id=original_quiz_id)
- trial_quiz.course.enroll(False, user)
- trial_quiz.pk = None
- trial_quiz.description = trial_quiz_name
- trial_quiz.is_trial = True
- trial_quiz.prerequisite = None
- if godmode:
- trial_quiz.time_between_attempts = 0
- trial_quiz.duration = 1000
- trial_quiz.attempts_allowed = -1
- trial_quiz.active = True
- trial_quiz.start_date_time = timezone.now()
- trial_quiz.end_date_time = datetime(
- 2199, 1, 1, 0, 0, 0, 0, tzinfo=pytz.utc
- )
- trial_quiz.save()
- return trial_quiz
-
-
-###############################################################################
-class Quiz(models.Model):
- """A quiz that students will participate in. One can think of this
- as the "examination" event.
- """
-
- course = models.ForeignKey(Course)
-
- # The start date of the quiz.
- start_date_time = models.DateTimeField(
- "Start Date and Time of the quiz",
- default=timezone.now,
- null=True
- )
-
- # The end date and time of the quiz
- end_date_time = models.DateTimeField(
- "End Date and Time of the quiz",
- default=datetime(
- 2199, 1, 1,
- tzinfo=pytz.timezone(timezone.get_current_timezone_name())
- ),
- null=True
- )
-
- # This is always in minutes.
- duration = models.IntegerField("Duration of quiz in minutes", default=20)
-
- # Is the quiz active. The admin should deactivate the quiz once it is
- # complete.
- active = models.BooleanField(default=True)
-
- # Description of quiz.
- description = models.CharField(max_length=256)
-
- # Mininum passing percentage condition.
- pass_criteria = models.FloatField("Passing percentage", default=40)
-
- # List of prerequisite quizzes to be passed to take this quiz
- prerequisite = models.ForeignKey("Quiz", null=True, blank=True)
-
- # Programming language for a quiz
- language = models.CharField(max_length=20, choices=languages)
-
- # Number of attempts for the quiz
- attempts_allowed = models.IntegerField(default=1, choices=attempts)
-
- time_between_attempts = models.IntegerField(
- "Number of Days", choices=days_between_attempts
- )
-
- is_trial = models.BooleanField(default=False)
-
- instructions = models.TextField('Instructions for Students',
- default=None, blank=True, null=True)
-
- view_answerpaper = models.BooleanField('Allow student to view their answer\
- paper', default=False)
-
- allow_skip = models.BooleanField("Allow students to skip questions",
- default=True)
-
- objects = QuizManager()
-
- class Meta:
- verbose_name_plural = "Quizzes"
-
- def is_expired(self):
- return not self.start_date_time <= timezone.now() < self.end_date_time
-
- def has_prerequisite(self):
- return True if self.prerequisite else False
-
- def _create_duplicate_quiz(self, course):
- questionpaper = self.questionpaper_set.all()
- new_quiz = Quiz.objects.create(course=course,
- start_date_time=self.start_date_time,
- end_date_time=self.end_date_time,
- duration=self.duration,
- active=self.active,
- description="Copy Of {0}".format(self.description),
- pass_criteria=self.pass_criteria,
- language=self.language,
- attempts_allowed=self.attempts_allowed,
- time_between_attempts=self.time_between_attempts,
- is_trial=self.is_trial,
- instructions=self.instructions,
- allow_skip=self.allow_skip
- )
-
- for qp in questionpaper:
- qp._create_duplicate_questionpaper(new_quiz)
-
- return new_quiz
-
- def create_demo_quiz(self, course):
- demo_quiz = Quiz.objects.create(
- start_date_time=timezone.now(),
- end_date_time=timezone.now() + timedelta(176590),
- duration=30, active=True,
- attempts_allowed=-1,
- time_between_attempts=0,
- description='Yaksh Demo quiz', pass_criteria=0,
- language='Python', prerequisite=None,
- course=course
- )
- return demo_quiz
-
- def __str__(self):
- desc = self.description or 'Quiz'
- return '%s: on %s for %d minutes' % (desc, self.start_date_time,
- self.duration)
-
-
-###############################################################################
class QuestionPaperManager(models.Manager):
def _create_trial_from_questionpaper(self, original_quiz_id):
@@ -884,18 +1115,20 @@ class QuestionPaper(models.Model):
all_questions = questions
return all_questions
- def make_answerpaper(self, user, ip, attempt_num):
+ def make_answerpaper(self, user, ip, attempt_num, course_id):
"""Creates an answer paper for the user to attempt the quiz"""
try:
ans_paper = AnswerPaper.objects.get(user=user,
attempt_number=attempt_num,
- question_paper=self
+ question_paper=self,
+ course_id=course_id
)
except AnswerPaper.DoesNotExist:
ans_paper = AnswerPaper(
user=user,
user_ip=ip,
- attempt_number=attempt_num
+ attempt_number=attempt_num,
+ course_id=course_id
)
ans_paper.start_time = timezone.now()
ans_paper.end_time = ans_paper.start_time + \
@@ -911,25 +1144,22 @@ class QuestionPaper(models.Model):
except AnswerPaper.MultipleObjectsReturned:
ans_paper = AnswerPaper.objects.get(user=user,
attempt_number=attempt_num,
- question_paper=self
+ question_paper=self,
+ course_id=course_id
).order_by('-id')
ans_paper = ans_paper[0]
return ans_paper
- def _is_questionpaper_passed(self, user):
- return AnswerPaper.objects.filter(question_paper=self, user=user,
- passed=True).exists()
-
def _is_attempt_allowed(self, user):
attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self,
user=user)
return attempts != self.quiz.attempts_allowed
- def can_attempt_now(self, user):
+ def can_attempt_now(self, user, course_id):
if self._is_attempt_allowed(user):
last_attempt = AnswerPaper.objects.get_user_last_attempt(
- user=user, questionpaper=self
+ user=user, questionpaper=self, course_id=course_id
)
if last_attempt:
time_lag = (timezone.now() - last_attempt.start_time).days
@@ -939,14 +1169,6 @@ class QuestionPaper(models.Model):
else:
return False
- def _get_prequisite_paper(self):
- return self.quiz.prerequisite.questionpaper_set.get()
-
- def is_prerequisite_passed(self, user):
- if self.quiz.has_prerequisite():
- prerequisite = self._get_prequisite_paper()
- return prerequisite._is_questionpaper_passed(user)
-
def create_demo_quiz_ppr(self, demo_quiz, user):
question_paper = QuestionPaper.objects.create(quiz=demo_quiz,
shuffle_questions=False
@@ -1038,27 +1260,32 @@ class AnswerPaperManager(models.Manager):
questions_answered.append(question.id)
return Counter(questions_answered)
- def get_attempt_numbers(self, questionpaper_id, status='completed'):
+ def get_attempt_numbers(self, questionpaper_id, course_id,
+ status='completed'):
''' Return list of attempt numbers'''
attempt_numbers = self.filter(
- question_paper_id=questionpaper_id, status=status
+ question_paper_id=questionpaper_id, status=status,
+ course_id=course_id
).values_list('attempt_number', flat=True).distinct()
return attempt_numbers
- def has_attempt(self, questionpaper_id, attempt_number,
+ def has_attempt(self, questionpaper_id, attempt_number, course_id,
status='completed'):
''' Whether question paper is attempted'''
return self.filter(
question_paper_id=questionpaper_id,
- attempt_number=attempt_number, status=status
+ attempt_number=attempt_number, status=status,
+ course_id=course_id
).exists()
- def get_count(self, questionpaper_id, attempt_number, status='completed'):
+ def get_count(self, questionpaper_id, attempt_number, course_id,
+ status='completed'):
''' Return count of answerpapers for a specfic question paper
and attempt number'''
return self.filter(
question_paper_id=questionpaper_id,
- attempt_number=attempt_number, status=status
+ attempt_number=attempt_number, status=status,
+ course_id=course_id
).count()
def get_question_statistics(self, questionpaper_id, attempt_number,
@@ -1083,18 +1310,21 @@ class AnswerPaperManager(models.Manager):
question_stats[question] = [0, questions[question.id]]
return question_stats
- def _get_answerpapers_for_quiz(self, questionpaper_id, status=False):
+ def _get_answerpapers_for_quiz(self, questionpaper_id, course_id,
+ status=False):
if not status:
- return self.filter(question_paper_id=questionpaper_id)
+ return self.filter(question_paper_id=questionpaper_id,
+ course_id=course_id)
else:
return self.filter(question_paper_id=questionpaper_id,
+ course_id=course_id,
status="completed")
def _get_answerpapers_users(self, answerpapers):
return answerpapers.values_list('user', flat=True).distinct()
- def get_latest_attempts(self, questionpaper_id):
- papers = self._get_answerpapers_for_quiz(questionpaper_id)
+ def get_latest_attempts(self, questionpaper_id, course_id):
+ papers = self._get_answerpapers_for_quiz(questionpaper_id, course_id)
users = self._get_answerpapers_users(papers)
latest_attempts = []
for user in users:
@@ -1106,9 +1336,10 @@ class AnswerPaperManager(models.Manager):
user_id=user_id
).order_by('-attempt_number')[0]
- def get_user_last_attempt(self, questionpaper, user):
+ def get_user_last_attempt(self, questionpaper, user, course_id):
attempts = self.filter(question_paper=questionpaper,
- user=user).order_by('-attempt_number')
+ user=user,
+ course_id=course_id).order_by('-attempt_number')
if attempts:
return attempts[0]
@@ -1118,23 +1349,28 @@ class AnswerPaperManager(models.Manager):
def get_total_attempt(self, questionpaper, user):
return self.filter(question_paper=questionpaper, user=user).count()
- def get_users_for_questionpaper(self, questionpaper_id):
- return self._get_answerpapers_for_quiz(questionpaper_id, status=True)\
+ def get_users_for_questionpaper(self, questionpaper_id, course_id):
+ return self._get_answerpapers_for_quiz(questionpaper_id, course_id,
+ status=True)\
.values("user__id", "user__first_name", "user__last_name")\
.order_by("user__first_name")\
.distinct()
- def get_user_all_attempts(self, questionpaper, user):
- return self.filter(question_paper=questionpaper, user=user)\
+ def get_user_all_attempts(self, questionpaper, user, course_id):
+ return self.filter(question_paper=questionpaper, user=user,
+ course_id=course_id)\
.order_by('-attempt_number')
- def get_user_data(self, user, questionpaper_id, attempt_number=None):
+ def get_user_data(self, user, questionpaper_id, course_id,
+ attempt_number=None):
if attempt_number is not None:
papers = self.filter(user=user, question_paper_id=questionpaper_id,
+ course_id=course_id,
attempt_number=attempt_number)
else:
papers = self.filter(
- user=user, question_paper_id=questionpaper_id
+ user=user, question_paper_id=questionpaper_id,
+ course_id=course_id
).order_by("-attempt_number")
data = {}
profile = user.profile if hasattr(user, 'profile') else None
@@ -1144,9 +1380,9 @@ class AnswerPaperManager(models.Manager):
data['questionpaperid'] = questionpaper_id
return data
- def get_user_best_of_attempts_marks(self, quiz, user_id):
+ def get_user_best_of_attempts_marks(self, quiz, user_id, course_id):
best_attempt = 0.0
- papers = self.filter(question_paper__quiz=quiz,
+ papers = self.filter(question_paper__quiz=quiz, course_id=course_id,
user=user_id).values("marks_obtained")
if papers:
best_attempt = max([marks["marks_obtained"] for marks in papers])
@@ -1165,6 +1401,9 @@ class AnswerPaper(models.Model):
# The Quiz to which this question paper is attached to.
question_paper = models.ForeignKey(QuestionPaper)
+ # Answepaper will be unique to the course
+ course = models.ForeignKey(Course, null=True)
+
# The attempt number for the question paper.
attempt_number = models.IntegerField()
@@ -1438,6 +1677,7 @@ class AnswerPaper(models.Model):
result['error'] = ['Correct answer']
elif question.type == 'float':
+ user_answer = float(user_answer)
tc_status = []
user_answer = float(user_answer)
for tc in question.get_test_cases():
diff --git a/yaksh/static/yaksh/css/design_course.css b/yaksh/static/yaksh/css/design_course.css
new file mode 100644
index 0000000..d1bf4bd
--- /dev/null
+++ b/yaksh/static/yaksh/css/design_course.css
@@ -0,0 +1,17 @@
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 18px;
+ color: #404040;
+}
+
+#available-lesson-quiz .col-md-8 > div{
+ background: #f5f5f5;
+ border: 1px solid #333333;
+ overflow-y: scroll;
+ height: 300px;
+}
+#available-lesson-quiz .available-list > div{
+ height: 300px;
+}
diff --git a/yaksh/static/yaksh/js/add_quiz.js b/yaksh/static/yaksh/js/add_quiz.js
index f4067ed..57993ef 100644
--- a/yaksh/static/yaksh/js/add_quiz.js
+++ b/yaksh/static/yaksh/js/add_quiz.js
@@ -21,15 +21,5 @@ String.prototype.beginsWith = function (string) {
function usermode(location)
{
- var select = document.getElementById("id_prerequisite");
- var select_text = select.options[select.selectedIndex].text;
- if (select_text.beginsWith("----")){
- window.alert("No prerequisite for this course.\n \
- You can attempt the quiz.");
- window.location.replace(location);
- } else {
- window.alert(select_text + " is a prerequisite for this course.\n \
- You are still allowed to attempt this quiz.")
- window.location.replace(location);
- }
- }
+ window.location.replace(location);
+}
diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js
index 6807cf4..f0d03e2 100644
--- a/yaksh/static/yaksh/js/course.js
+++ b/yaksh/static/yaksh/js/course.js
@@ -46,13 +46,12 @@ $(function() {
$("#send_mail").click(function(){
var subject = $("#subject").val();
- var body = $('#email_body').val();
+ var body = tinymce.get("email_body").getContent();
var status = false;
var selected = [];
$('#reject input:checked').each(function() {
selected.push($(this).attr('value'));
});
-
if (subject == '' || body == ''){
$("#error_msg").html("Please enter mail details");
$("#dialog").dialog();
diff --git a/yaksh/static/yaksh/js/design_course.js b/yaksh/static/yaksh/js/design_course.js
new file mode 100644
index 0000000..7b01491
--- /dev/null
+++ b/yaksh/static/yaksh/js/design_course.js
@@ -0,0 +1,26 @@
+$(document).ready(function(){
+ var checked_vals = [];
+ $('input:checkbox[name="quiz_lesson"]').click(function() {
+ if($(this).prop("checked") == true){
+ checked_vals.push($(this).val());
+ }
+ else{
+ checked_vals.pop($(this).val());
+ }
+ });
+ $('#design_course_form').submit(function(eventObj) {
+ var input_order = $("input[name*='order']");
+ var order_list = []
+ if (input_order){
+ $(input_order).each(function(index) {
+ order_list.push($(this).data('item-id')+":"+$(this).val());
+ });
+ }
+ $(this).append('<input type="hidden" name="choosen_list" value='+checked_vals+'>');
+ $(this).append('<input type="hidden" name="ordered_list" value='+order_list+'>');
+ return true;
+ });
+ var msg = "Check Prerequisite is set to Yes by default \n" +
+ "To change, select the Change checkbox and Click Change Prerequisite button \n";
+ $("#prereq_msg").attr("title", msg);
+}); \ No newline at end of file
diff --git a/yaksh/static/yaksh/js/edit_question.js b/yaksh/static/yaksh/js/edit_question.js
index 7a0f56d..4eb25d0 100644
--- a/yaksh/static/yaksh/js/edit_question.js
+++ b/yaksh/static/yaksh/js/edit_question.js
@@ -35,15 +35,7 @@ function decrease(frm,n)
function grade_data(showHideDiv)
{
- var ele=document.getElementById(showHideDiv);
- if (ele.style.display=="block")
- {
- ele.style.display = "none";
- }
- else
- {
- ele.style.display = "block";
- }
+ $("#"+showHideDiv).toggle();
}
function setSelectionRange(input, selectionStart, selectionEnd)
diff --git a/yaksh/static/yaksh/js/lesson.js b/yaksh/static/yaksh/js/lesson.js
new file mode 100644
index 0000000..6f873b9
--- /dev/null
+++ b/yaksh/static/yaksh/js/lesson.js
@@ -0,0 +1,78 @@
+$(document).ready(function(){
+ var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
+
+ function csrfSafeMethod(method) {
+ // These HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+ }
+ $.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
+ }
+ });
+ $("#preview").click(function(){
+ var description = $("#id_description").val();
+ var preview_url = window.location.protocol + "//" +
+ window.location.host + "/exam/manage/courses/lesson/preview/";
+ $.ajax({
+ url: preview_url,
+ timeout: 15000,
+ type: 'POST',
+ data: JSON.stringify({'description': description}),
+ dataType: 'json',
+ contentType: 'application/json; charset=utf-8',
+ success: function(msg) {
+ preview_text(msg['data']);
+ },
+ error: function(jqXHR, textStatus) {
+
+ }
+ });
+ });
+
+ function preview_text(data){
+ var preview_div = $("#preview_text_div");
+ if (!preview_div.is(":visible")){
+ $("#preview_text_div").toggle();
+ }
+ $("#description_body").empty();
+ $("#description_body").append(data);
+ }
+
+ $("#embed").click(function() {
+ $("#dialog_iframe").toggle();
+ $("#dialog_iframe").dialog({
+ resizable: false,
+ height: '300',
+ width: '450'
+ });
+ });
+
+ $("#submit_info").click(function(){
+ var url = $("#url").val();
+ if (url == "") {
+ if (!$("#error_div").is(":visible")){
+ $("#error_div").toggle();
+ }
+ }
+ else{
+ if ($("#error_div").is(":visible")){
+ $("#error_div").toggle();
+ }
+ $("#video_frame").attr("src", url);
+ $("#html_text").text($("#iframe_div").html().trim());
+ }
+ });
+
+ $("#copy").click(function(){
+ try{
+ var text = $("#html_text");
+ text.select();
+ document.execCommand("Copy");
+ } catch (err) {
+ alert("Unable to copy. Press Ctrl+C or Cmd+C to copy")
+ }
+ });
+});
diff --git a/yaksh/static/yaksh/js/requesthandler.js b/yaksh/static/yaksh/js/requesthandler.js
index 3a7cdba..639dc81 100644
--- a/yaksh/static/yaksh/js/requesthandler.js
+++ b/yaksh/static/yaksh/js/requesthandler.js
@@ -37,7 +37,7 @@ function unlock_screen() {
}
function get_result(uid){
- var url = "/exam/get_result/" + uid + "/";
+ var url = "/exam/get_result/" + uid + "/" + course_id + "/" + module_id + "/";
ajax_check_code(url, "GET", "html", null, uid)
}
diff --git a/yaksh/templates/exam.html b/yaksh/templates/exam.html
index 3113eb4..03c9ff8 100644
--- a/yaksh/templates/exam.html
+++ b/yaksh/templates/exam.html
@@ -18,7 +18,7 @@
</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">
+ <form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{module.id}}/{{ paper.question_paper.id }}/{{course.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">
@@ -46,15 +46,15 @@
{% 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>
+ onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.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>
+ onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')">{{ forloop.counter }}</a></li>
{% endif %}
{% endif %}
{% if qid in paper.get_questions_answered %}
<li><a style="background-color:#B4B8BA; width:25%" href="#" data-toggle="tooltip"
- onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')"
+ onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')"
title="{{ qid.description }}">{{ forloop.counter }}</a></li>
{% endif %}
{% else %}
@@ -67,6 +67,9 @@
{% endfor %}
</ul>
<p>Question(s) left: <b>{{ paper.questions_left }}</b></p>
+ <br><br><br>
+ {% block learning_units %}
+ {% endblock %}
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<div class="row">
diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html
index ac9b26d..17ce23e 100644
--- a/yaksh/templates/manage.html
+++ b/yaksh/templates/manage.html
@@ -26,6 +26,38 @@
</div><!-- /.navbar -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
+
+ <!-- iframe div for video embed -->
+ <div id="iframe_div" style="display: none;">
+ <div style="width: 640px; height: 480px; position: relative;">
+ <iframe id="video_frame" width="800" height="500" frameborder="0" allowfullscreen>
+ </iframe>
+ <div style="width: 80px; height: 80px; position: absolute; opacity: 0; right: 0px; top: 0px;">&nbsp;
+ </div>
+ </div>
+ </div>
+ <!-- end iframe div -->
+
+ <!-- Dialog to video embed -->
+ <div id="dialog_iframe" title="Embed Video URL" style="display: none;">
+ <label>Enter Url:</label>
+ <input id="url" name="url" type="text" required="true">
+ <input type="button" id="submit_info" name="submit_info" class="btn" value="Submit" />
+ <div id="error_div" style="display: none;">
+ <b> Please enter URL</b>
+ </div>
+ <div id="copy_div">
+ <br>
+ <label>Paste HTML to embed in website:</label>
+ <textarea rows="5" cols="35" id="html_text"></textarea>
+ <br>
+ <a class="btn btn-default" id="copy" data-toggle="tooltip" title="Copy to Clipboard">
+ <i class="fa fa-clipboard" aria-hidden="true"></i>
+ </a>
+ </div>
+ </div>
+ <!-- end dialog -->
+
{% endblock %}
{% block content %}
{% block main %}
diff --git a/yaksh/templates/user.html b/yaksh/templates/user.html
index e1b129f..090e93d 100644
--- a/yaksh/templates/user.html
+++ b/yaksh/templates/user.html
@@ -11,10 +11,9 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
-
- <a class="navbar-brand navbar-left" href="{{ URL_ROOT }}/exam/manage/">
+ <a class="navbar-brand navbar-left" href="{{ URL_ROOT }}/exam/">
<img src="{{ URL_ROOT }}/static/yaksh/images/yaksh_banner.png" alt="YAKSH" style="margin-top: -3px; margin-left:-15px">
- </img>
+ </img>
</a>
</div>
<div class= "collapse navbar-collapse" id="navbar">
@@ -30,8 +29,12 @@
{% endblock %}
{% block content %}
<div class="row">
+ <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 %}
{% if user %}
diff --git a/yaksh/templates/yaksh/add_course.html b/yaksh/templates/yaksh/add_course.html
index b8fc11c..47a637d 100644
--- a/yaksh/templates/yaksh/add_course.html
+++ b/yaksh/templates/yaksh/add_course.html
@@ -15,7 +15,7 @@
<form name=frm id=frm action="" method="post" >
{% csrf_token %}
<center>
- <table class=span1>
+ <table class="table table-bordered">
{{ form.as_table }}
</table>
<br/>
@@ -25,7 +25,7 @@
</script>
</center>
- <center><button class="btn btn-default" type="submit" id="submit" name="questionpaper">Save </button>
- <button class="btn btn-default" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/");'>Cancel</button> </center>
+ <center><button class="btn primary" type="submit" id="submit" name="questionpaper">Save</button>
+ <button class="btn primary" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses");'>Cancel</button> </center>
</form>
{% endblock %}
diff --git a/yaksh/templates/yaksh/add_lesson.html b/yaksh/templates/yaksh/add_lesson.html
new file mode 100644
index 0000000..d9bc1e7
--- /dev/null
+++ b/yaksh/templates/yaksh/add_lesson.html
@@ -0,0 +1,74 @@
+{% extends "manage.html" %}
+{% load custom_filters %}
+
+{% block title %}Create/Edit Lesson{% endblock %}
+
+{% block script %}
+<script src="{{ URL_ROOT }}/static/yaksh/js/lesson.js"></script>
+<script src="https://code.jquery.com/ui/1.9.1/jquery-ui.js"></script>
+{% endblock %}
+
+{% block css %}
+<link rel="stylesheet" href="https://code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css">
+{% endblock %}
+
+{% block content %}
+<form name=frm id=frm action="" method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <center>
+ <table class="table table-bordered">
+ {{ lesson_form.as_table }}
+ {{ lesson_file_form.as_table }}
+ </table>
+ </center>
+ <br><br>
+ <center>
+ {% if lesson_files %}
+ <div class="alert alert-success">
+ <h4>Files added to this lesson</h4>
+ </div>
+ {% for f in lesson_files %}
+ <h4><input type="checkbox" name="delete_files" value="{{f.id}}">&nbsp;Delete</input>&nbsp;
+ <a href="{{f.file.url}}">{{ f.file.name|file_title }}</a>
+ ({{f.file.url}})
+ </h4><br>
+ {% endfor %}
+ {% else %}
+ <div class="alert alert-warning">
+ <h4 class="alert-warning">No Files added to this lesson</h4>
+ </div>
+ {% endif %}
+
+ </center>
+ <br><br>
+ <center>
+ <button class="btn" type="submit" id="submit" name="Save"> Save
+ </button>
+ {% if lesson_files %}
+ <button class="btn" type="submit" id="submit" name="Delete"> Delete Files
+ </button>
+ {% endif %}
+ {% if course_id %}
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button>
+ {% else %}
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/all_lessons/");'>Cancel</button>
+ {% endif %}
+ </form>
+ <button class="btn" type="button" name="button" id="preview">Preview Lesson Description
+ </button>
+ <button class="btn" type="button" name="button" id="embed">
+ Embed Video link
+ </button>
+ </center>
+ <hr>
+ <div class="panel panel-default" id="preview_text_div" style="display: none;">
+ <div class="panel-heading">
+ <center>
+ <h3>Description Preview</h3>
+ </center>
+ </div>
+ <div class="panel-body" id="description_body">
+ </div>
+ </div>
+
+{% endblock %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/add_module.html b/yaksh/templates/yaksh/add_module.html
new file mode 100644
index 0000000..4efccf7
--- /dev/null
+++ b/yaksh/templates/yaksh/add_module.html
@@ -0,0 +1,163 @@
+{% extends "manage.html" %}
+{% block title %}Create/Edit Learning Module{% endblock %}
+
+{% block pagetitle %}<h4>Design Learning Module</h4>{% endblock %}
+
+{% block script %}
+<script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.9.1.min.js"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/design_course.js"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/lesson.js"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/jquery-ui.js"></script>
+{% endblock %}
+
+{% block css %}
+<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/design_course.css" />
+<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/jquery-ui/jquery-ui.css">
+{% endblock %}
+
+{% block content %}
+{% if course_id %}
+ <a href="{{URL_ROOT}}/exam/manage/courses/" class="btn btn-primary">
+ Back to Courses</a>
+{% else %}
+ <a href="{{URL_ROOT}}/exam/manage/courses/all_learning_module" class="btn btn-primary">
+ Back to Learning Modules</a>
+{% endif %}
+{% if status == "add" %}
+<form name=frm id=frm action="" method="post">
+ {% csrf_token %}
+ <br>
+ <center>
+ <table class="table table-bordered">
+ {{ module_form.as_table }}
+ </table>
+ </center>
+ <br><br>
+ <center>
+ <button class="btn" type="submit" id="submit" name="Save">
+ Save
+ </button>
+ <button class="btn" type="button" name="button" id="preview">
+ Preview Module Description
+ </button>
+ <button class="btn" type="button" name="button" id="embed">
+ Embed Video link
+ </button>
+ </center>
+</form>
+<hr>
+<div class="panel panel-default" id="preview_text_div" style="display: none;">
+ <div class="panel-heading">
+ <center>
+ <h3>Description Preview</h3>
+ </center>
+ </div>
+ <div class="panel-body" id="description_body">
+ </div>
+</div>
+{% endif %}
+<!-- Add learning Units -->
+{% if status == "design" %}
+<center><h3><u>Add/Edit Learning Units</h3></u></center>
+<form action="{{URL_ROOT}}/exam/manage/courses/designmodule/{{module_id}}/" method="POST" id="design_course_form">
+{% csrf_token %}
+ <div class="tab-pane active" id="available-lesson-quiz">
+ <div class="row">
+ <div class="col-md-8 available-list col-md-offset-2">
+ <div id="fixed-available-wrapper">
+ <p><u><b>Available Lessons and quizzes: (Add Lessons and Quizzes)</b></u></p>
+ <div id="fixed-available">
+ <ul class="inputs-list">
+ {% for type, unit in quiz_les_list %}
+ <li>
+ <label>
+ {% if type == "quiz" %}
+ <input type="checkbox" name="quiz_lesson" data-qid="{{unit.id}}:{{type}}" value="{{unit.id}}:{{type}}">
+ <span>{{ unit.description }} ({{type}})</span>
+ {% else %}
+ <input type="checkbox" name="quiz_lesson" data-qid="{{unit.id}}:{{type}}" value="{{unit.id}}:{{type}}">
+ <span>{{ unit.name }} ({{type}})</span>
+ {% endif %}
+ </label>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ <br>
+ <center>
+ <button class="btn btn-success" type="submit" id="submit" name="Add">
+ Add to Module
+ </button>
+ </center>
+ <br><br>
+ </div>
+ <div class="col-md-8 col-md-offset-2">
+ <div id="fixed-added-wrapper">
+ <p><u><b>Choosen Lessons and quizzes:</b></u>
+ </p>
+ <div id="fixed-added">
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th width="5%">Select</th>
+ <th>Quiz/Lesson</th>
+ <th width="20%">Order</th>
+ <th width="25%" colspan="2">Check Prerequisite
+ <br>
+ <a href="#" data-toggle="tooltip" id="prereq_msg">
+ <span class="glyphicon glyphicon-question-sign">
+ </span> What's This
+ </a>
+ </th>
+ </tr>
+ <tr>
+ <th scope="row">&nbsp;</th>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <th>Currently</th>
+ <th>Change</th>
+ </tr>
+ </tr>
+ {% for unit in learning_units %}
+ <tr>
+ <ul class="inputs-list">
+ <td>
+ <input type="checkbox" name="delete_list" value="{{unit.id}}">
+ </td>
+ {% if unit.type == "quiz" %}
+ <td><span>{{ unit.quiz.description }} ({{unit.type}})
+ </span></td>
+ <td><input type="number" name="order" data-item-id="{{unit.id}}" value="{{unit.order}}" step="1"></td>
+ {% else %}
+ <td><span>{{ unit.lesson.name }} ({{unit.type}})
+ </span></td>
+ <td><input type="number" name="order" data-item-id="{{unit.id}}" value="{{unit.order}}" step="1"></td>
+ {% endif %}
+ <td>
+ {% if unit.check_prerequisite %}
+ Yes
+ {% else %}
+ No
+ {% endif %}
+ </td>
+ <td>
+ <input type="checkbox" name="check_prereq" value="{{unit.id}}">
+ </td>
+ </ul>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ </div>
+ <br>
+ <center>
+ <button id="Remove" name="Remove" class="btn btn-danger" type="submit">Remove from Module</button>
+ <button id="Change" name="Change" class="btn btn-info" type="submit"> Change Order</button>
+ <button id="Change" name="Change_prerequisite" class="btn btn-primary" type="submit"> Change Prerequisite</button>
+ </center>
+ </div>
+ </div> <!-- /.row -->
+ </div>
+</form>
+{% endif %}
+{% endblock %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html
index 08bb124..d3705e3 100644
--- a/yaksh/templates/yaksh/add_quiz.html
+++ b/yaksh/templates/yaksh/add_quiz.html
@@ -18,7 +18,7 @@
<form name=frm id=frm action="" method="post" >
{% csrf_token %}
<center>
- <table class=span1>
+ <table class="span1 table">
{{ form.as_table }}
</table>
<script type="text/javascript">
@@ -34,13 +34,14 @@
<button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center>
</form>
-{% if quiz_id %}
-
- <h5>You can check the quiz by attempting it in the following modes:</h5>
+<br>
+{% if quiz_id and course_id %}
<center>
- <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz_id}}");'>User Mode</button>
+ <h4>You can check the quiz by attempting it in the following modes:</h4>
+ <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz_id}}/{{course_id}}/");'>User Mode</button>
- <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz_id}}");'>God Mode</button>
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz_id}}/{{course_id}}/");'>
+ God Mode</button>
<a data-toggle="collapse" data-target="#help">
<span class="glyphicon glyphicon-info-sign">Help</span></a>
<div id="help" class="collapse">
diff --git a/yaksh/templates/yaksh/complete.html b/yaksh/templates/yaksh/complete.html
index f0852f3..d0b7e4d 100644
--- a/yaksh/templates/yaksh/complete.html
+++ b/yaksh/templates/yaksh/complete.html
@@ -6,7 +6,7 @@ width="80" alt="YAKSH"></img>{% endblock %}
{% csrf_token %}
{% if paper.questions_answered.all or paper.questions_unanswered.all %}
<center><table class="table table-bordered" >
- <caption> Submission Status </caption>
+ <caption> <center><h3>Submission Status</h3> </center></caption>
<thead>
<tr>
<th> Question</th>
@@ -30,8 +30,25 @@ width="80" alt="YAKSH"></img>{% endblock %}
{% endfor %}
</table></center>
{% endif %}
- <center><h2> Good bye! </h2></center>
- <center><h4> {{message}} </h4></center>
- <br><center><h4>You may now close the browser.</h4></center><br>
- <center><a href="{{URL_ROOT}}/exam/" id="home"> Home </a></center>
+ <center><h3>{{message}}</h3></center>
+ <center>
+ <br>
+ {% if not module_id %}
+ <br><center><h4>You may now close the browser.</h4></center><br>
+ {% endif %}
+ <a href="{{URL_ROOT}}/exam/" id="home" class="btn btn-success"> Home </a>
+ {% if module_id and not user == "moderator" %}
+ {% if first_unit %}
+ <a href="{{URL_ROOT}}/exam/next_unit/{{course_id}}/{{module_id}}/{{learning_unit.id}}/1" class="btn btn-info"> Next Unit
+ <span class="glyphicon glyphicon-chevron-right">
+ </span>
+ </a>
+ {% else %}
+ <a href="{{URL_ROOT}}/exam/next_unit/{{course_id}}/{{module_id}}/{{learning_unit.id}}" class="btn btn-info"> Next Unit
+ <span class="glyphicon glyphicon-chevron-right">
+ </span>
+ </a>
+ {% endif %}
+ {% endif %}
+ </center>
{% endblock content %}
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index e0731f9..cf0ab18 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -2,7 +2,9 @@
{% block title %} Course Details {% endblock title %}
+<div class="col-md-9 col-md-offset-2 main">
{% block pagetitle %} Course Details for {{ course.name|title }} {% endblock %}
+</div>
{% block script %}
<script language="JavaScript" type="text/javascript" src="{{ URL_ROOT }}/static/yaksh/js/course.js"></script>
@@ -30,14 +32,6 @@
Rejected Students </a></li>
{% endif %}
<li>
- <a href="{{URL_ROOT}}/exam/manage/toggle_status/{{ course.id }}/">
- {% if course.active %}Deactivate Course {% else %} Activate Course {% endif %}</a>
- </li>
- <li>
- <a href="{{URL_ROOT}}/exam/manage/duplicate_course/{{ course.id }}/">
- Clone Course</a>
- </li>
- <li>
<a href="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/">
Send Mail</a>
</li>
diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html
new file mode 100644
index 0000000..8e6f5a6
--- /dev/null
+++ b/yaksh/templates/yaksh/course_modules.html
@@ -0,0 +1,112 @@
+{% extends "user.html" %}
+{% load custom_filters %}
+{% block title %} Course Modules {% endblock %}
+{% block pagetitle %} Curriculum for {{course}} {% endblock %}
+{% block script %}
+<script>
+ function view_unit(unit){
+ $("#"+unit+"_down").toggle();
+ $("#"+unit+"_up").toggle();
+ }
+
+</script>
+{% endblock %}
+{% block main %}
+{% if msg %}
+ <div class="alert alert-warning" role="alert">
+ <center>{{ msg }}</center>
+ </div>
+{% endif %}
+{% if learning_modules %}
+ {% for module in learning_modules %}
+ <div class="row well">
+ <table class="table">
+ <tr>
+ <td>
+ <a href="{{URL_ROOT}}/exam/quizzes/view_module/{{module.id}}/{{course.id}}">
+ {{module.name|title}}</a>
+ </td>
+ <td>
+ <span class="glyphicon glyphicon-chevron-down" id="learning_units{{module.id}}{{course.id}}_down">
+ </span>
+ <span class="glyphicon glyphicon-chevron-up" id="learning_units{{module.id}}{{course.id}}_up" style="display: none;"></span>
+ <a data-toggle="collapse" data-target="#learning_units{{module.id}}{{course.id}}" onclick="view_unit('learning_units{{module.id}}{{course.id}}');">
+ View Lessons/Quizzes</a>
+ </td>
+ <td>
+ {% get_module_status user module course as module_status %}
+ Status:
+ {% if module_status == "completed" %}
+ <span class="label label-success">
+ {{module_status|title}}
+ </span>
+ {% elif module_status == "inprogress" %}
+ <span class="label label-info">
+ {{module_status|title}}
+ </span>
+ {% else %}
+ <span class="label label-warning">
+ {{module_status|title}}
+ </span>
+ {% endif %}
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="learning_units{{module.id}}{{course.id}}" class="collapse">
+ <table class="table">
+ <tr>
+ <th>Lesson/Quiz</th>
+ <th>Status</th>
+ <th>Type</th>
+ <th>View AnswerPaper</th>
+ </tr>
+ {% for unit in module.get_learning_units %}
+ <tr>
+ <ul class="inputs-list">
+ <td>
+ {% if unit.type == "quiz" %}
+ {{unit.quiz.description}}
+ {% else %}
+ {{unit.lesson.name}}
+ {% endif %}
+ </td>
+ <td>
+ {% get_unit_status course module unit user as status %}
+ {% if status == "completed" %}
+ <span class="label label-success">{{status|title}}
+ </span>
+ {% elif status == "inprogress" %}
+ <span class="label label-info">{{status|title}}
+ </span>
+ {% else %}
+ <span class="label label-warning">{{status|title}}
+ </span>
+ {% endif %}
+ </td>
+ <td>
+ {{unit.type|title}}
+ </td>
+ <td>
+ {% if unit.type == "quiz" %}
+ {% if unit.quiz.view_answerpaper %}
+ <a href="{{ URL_ROOT }}/exam/view_answerpaper/{{ unit.quiz.questionpaper_set.get.id }}/{{course.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 %}
+ {% else %}
+ ------
+ {% endif %}
+ </td>
+ </ul>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ {% endfor %}
+{% else %}
+ <h3> No lectures found </h3>
+{% endif %}
+{% endblock %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html
index e09a9cc..0efa72a 100644
--- a/yaksh/templates/yaksh/courses.html
+++ b/yaksh/templates/yaksh/courses.html
@@ -1,173 +1,449 @@
{% extends "manage.html" %}
{% block title %} Courses {% endblock %}
{% block pagetitle %} Courses {% endblock pagetitle %}
+{% block script %}
+<script>
+ $(document).ready(function(){
+ $("#created_courses").toggle();
+ $("#link_created_courses").click(function() {
+ if ($("#allotted_courses").is(":visible")){
+ $("#allotted_courses").toggle();
+ }
+ if (!$("#created_courses").is(":visible")){
+ $("#created_courses").toggle();
+ }
+ });
+ $("#link_allotted_courses").click(function() {
+ if ($("#created_courses").is(":visible")){
+ $("#created_courses").toggle();
+ }
+ if (!$("#allotted_courses").is(":visible")){
+ $("#allotted_courses").toggle();
+ }
+ });
+ });
+</script>
+{% endblock %}
{% block content %}
+<div class="row">
+ <div class="col-sm-3 col-md-2 sidebar">
+ <ul class="nav nav-sidebar">
+ {% if type == "courses" %}
+ <li><a href="#" id="link_created_courses">My Courses</a></li>
+ <li><a href="#" id="link_allotted_courses">Allotted Courses</a></li>
+ {% else %}
+ <li><a href="{{URL_ROOT}}/exam/manage/courses">View all Courses</a></li>
+ {% endif %}
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/add_course">Add New Course</a>
+ </li>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/all_quizzes/">Add/View Quizzes</a>
+ </li>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/all_lessons/">Add/View Lessons</a>
+ </li>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/all_learning_module">
+ Add/View Modules</a>
+ </li>
+ </ul>
+ </div>
+</div>
+
+{% if type == "courses" %}
+<div id="created_courses" style="display: none;">
{% if not courses %}
<center><h4> No new Courses created </h4></center>
{% else %}
-<center><h3> Course(s) Created</h3></center>
+<div class="col-md-offset-2 main">
+ <center><h3> Course(s) Created</h3></center>
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th>Courses</th>
+ <th>Modules</th>
+ </tr>
+
{% for course in courses %}
- {% if user != course.creator %}
- <h4> {{course.creator.get_full_name}} added you to this course</h4>
- {% endif %}
- <div class="row">
- <div class="col-md-12">
+ <tr>
+ <td width="30%">
+ <a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}">{{ course.name }}
+ </a>
+ {% if course.active %}
+ <span class="label label-success">Active</span>
+ {% else %}
+ <span class="label label-danger">Closed</span>
+ {% endif %}
+ <br><br>
+ <center><b><u>Teacher(s) Added to {{ course }}</u></b></center>
+ <br>
+ <form action="{{URL_ROOT}}/exam/manage/remove_teachers/{{ course.id }}/" method="post">
+ {% if course.get_teachers %}
+ <div align="left">
+ {% csrf_token %}
+ {% for teacher in course.get_teachers %}
+ <div class="well">
<div class="row">
- <div class="col-md-6">
- <p>
- <b><u>Course</u></b>
- {% if course.active %}
- <span class="label label-success">Active</span>
- {% else %}
- <span class="label label-danger">Closed</span>
- {% endif %}
- </p>
- <a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}/">{{ course.name }}</a>
- </br></br>
- {% if user == course.creator %}
- <div class="row">
- <div class="col-md-4 ">
- <center><b><u>Teacher(s) Added to {{ course }}</u></b></center>
- {% if course.get_teachers %}
- <div align="left">
- <form action="{{URL_ROOT}}/exam/manage/remove_teachers/{{ course.id }}/" method="post">
- {% csrf_token %}
- {% for teacher in course.get_teachers %}
- <div class="well">
- <div class="row">
- <div class="col-md-333" style="width: auto;">
- <input type="checkbox" name="remove" value="{{ teacher.id }}">&nbsp;{{ teacher.get_full_name }}
- </div>
- </div>
- </div>
- {% endfor %}
- <button class="btn btn-danger" type="submit">Remove Selected</button>
- </div>
- {% else %}
- <center><b>No Teacher(s) Added</b></center>
- {% endif %}
- </form>
- </div>
- </div>
- {% endif %}
- </div>
- {% if user == course.creator %}
- <p><b><a href="{{URL_ROOT}}/exam/manage/searchteacher/{{course.id}}/">Add Teacher</a></b></p>
- {% endif %}
- <div class="col-md-2" style="text-align:left">
- {% if course.get_quizzes %}
- <p><b><u>Quiz(zes)</u></b></p>
- {% for quiz in course.get_quizzes %}
- <a href="{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/{{quiz.id}}/">{{ quiz.description }}</a><br>
- {% endfor %}
- </div>
- <div class="col-md-4" style="text-align:left">
- <p><b><u>Question Paper(s)</u></b></p>
- {% for quiz in course.get_quizzes %}
- {% if quiz.questionpaper_set.get %}
- <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/">Question Paper for {{ quiz.description }}</a><br>
- {% else %}
- <p>No Question Paper
- <a href="#" onClick='location.replace("{{URL_ROOT}}/exam/manage/quiz/designquestionpaper/{{ quiz.id }}/");'>Add</a>
- </p>
- {% endif %}
- {% endfor %}
- {% else %}
- <p><b>No quiz </b></p>
- {% endif %}
+ <div class="col-md-333" style="width: auto;">
+ <input type="checkbox" name="remove" value="{{ teacher.id }}">&nbsp;{{ teacher.get_full_name }}
</div>
</div>
- <br/>
- <a class="btn btn-success" href="{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/">Add New Quiz</a>
- <a class="btn btn-primary"href="{{URL_ROOT}}/exam/manage/edit_course/{{course.id}}">Edit Course</a>
- <a class="btn btn-default"href="{{URL_ROOT}}/exam/manage/courses/download_course_csv/{{course.id}}">Download CSV</a>
</div>
- </div>
+ {% endfor %} <!-- end for teachers -->
+ <button class="btn btn-danger" type="submit" data-toggle="tooltip" title="Remove Selected Teachers from this course">Remove Teachers</button>
+ </div>
+ {% else %}
+ <center><b>No Teacher(s) Added</b></center>
+ {% endif %}
+ </form>
+ <br><br>
+ <ul>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/designcourse/{{course.id}}/">Design Course
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/edit_course/{{course.id}}">Edit Course</a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/download_course_csv/{{course.id}}">Download CSV
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/searchteacher/{{course.id}}/">Add Teacher</a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/toggle_status/{{ course.id }}/">
+ {% if course.active %}Deactivate Course {% else %} Activate Course {% endif %}
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/duplicate_course/{{ course.id }}/">
+ Clone Course</a>
+ </li>
+ </ul>
+ </td>
+ <td>
+ <table id="course-details" class="table table-bordered">
+ {% if course.get_learning_modules %}
+ <tr>
+ <th>Module</th>
+ <th>Module Design</th>
+ <th>Lessons/Quizzes</th>
+ </tr>
+ {% for module in course.get_learning_modules %}
+ <tr>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/add_module/{{module.id}}/{{course.id}}">
+ {{module.name}}</a>
+ </td>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/designmodule/{{module.id}}/{{course.id}}/">
+ Add Quizzes/Lessons for {{module.name}}
+ </a>
+ </td>
+ <td>
+ {% for unit in module.get_learning_units %}
+ <ul class="inputs-list">
+ <li>
+ {% if unit.type == "quiz" %}
+ <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}">
+ {{unit.quiz.description}}</a>
+ {% else %}
+ <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{unit.lesson.id}}/{{course.id}}">
+ {{unit.lesson.name}}</a>
+ {% endif %}
+ </li>
+ </ul>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %} <!-- end for modules -->
+ {% else %}
+ No learning modules
+ {% endif %}
+ </table>
+ </td>
+ </tr>
+ {% endfor %} <!-- end for courses -->
+ </table>
+</div>
+{% endif %}
+</div>
+
+<!-- Show Alotted courses -->
+<div id="allotted_courses" style="display: none;">
+{% if not allotted_courses %}
+ <center><h4> No new Courses allotted</h4></center>
<br><br>
- {% endfor %}
- {% endif %}
-<hr/>
-{% if allotted_courses %}
+{% else %}
+<div class="col-md-offset-2 main">
<center><h3> Course(s) Allotted </h3></center>
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th>Courses</th>
+ <th>Modules</th>
+ </tr>
+
{% for course in allotted_courses %}
- <div class="row">
- <div class="col-md-12">
+ <tr>
+ <td width="30%">
+ <ul class="list-group">
+ <a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}/">{{ course.name }}</a>
+ {% if course.active %}
+ <span class="label label-success">Active</span>
+ {% else %}
+ <span class="label label-danger">Closed</span>
+ {% endif %}
+ <br><br>
+ <center><b><u> Course Creator</u></b><br>
+ <h4>{{course.creator.get_full_name.title}}</h4>
+ </center><br>
+ <center><b><u>Teacher(s) Added to {{ course }}</u></b></center>
+ <br>
+ <form action="{{URL_ROOT}}/exam/manage/remove_teachers/{{ course.id }}/" method="post">
+ {% if course.get_teachers %}
+ <div align="left">
+ {% csrf_token %}
+ {% for teacher in course.get_teachers %}
+ <div class="well">
<div class="row">
- <div class="col-md-6">
- <p>
- <b><u>Course</u></b>
- {% if course.active %}
- <span class="label label-success">Active</span>
+ <div class="col-md-333" style="width: auto;">
+ <input type="checkbox" name="remove" value="{{ teacher.id }}">&nbsp;
+ {{ teacher.get_full_name }}
+ </div>
+ </div>
+ </div>
+ {% endfor %} <!-- end for teachers -->
+ <button class="btn btn-danger" type="submit" data-toggle="tooltip" title="Remove Selected Teachers from this course">Remove Teachers</button>
+ </div>
+ {% else %}
+ <center><b>No Teacher(s) Added</b></center>
+ {% endif %}
+ </form>
+ <br><br>
+ <ul>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/designcourse/{{course.id}}/">Design Course
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/edit_course/{{course.id}}">Edit Course</a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/courses/download_course_csv/{{course.id}}">Download CSV
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/searchteacher/{{course.id}}/">Add Teacher
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/toggle_status/{{ course.id }}/">
+ {% if course.active %}Deactivate Course {% else %} Activate Course {% endif %}
+ </a>
+ </li>
+ <br>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/duplicate_course/{{ course.id }}/">
+ Clone Course</a>
+ </li>
+ </ul>
+ </td>
+ <td>
+ <table id="course-details" class="table table-bordered">
+ {% if course.get_learning_modules %}
+ <tr>
+ <th>Module</th>
+ <th>Module Design</th>
+ <th>Lessons/Quizzes</th>
+ </tr>
+ {% for module in course.get_learning_modules %}
+ <tr>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/add_module/{{module.id}}/{{course.id}}">
+ {{module.name}}</a>
+ </td>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/designmodule/{{module.id}}/{{course.id}}/">
+ Add Quizzes/Lessons for {{module.name}}
+ </a>
+ </td>
+ <td>
+ {% for unit in module.get_learning_units %}
+ <ul class="inputs-list">
+ <li>
+ {% if unit.type == "quiz" %}
+ <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}">
+ {{unit.quiz.description}}</a>
{% else %}
- <span class="label label-danger">Closed</span>
+ <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{unit.lesson.id}}/{{course.id}}">
+ {{unit.lesson.name}}</a>
{% endif %}
- </p>
- <a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}/">{{ course.name }}</a>
- </br></br>
- <div class="row">
- <div class="col-md-4 wrap">
- <center><b><u> Course Creator</u></b></center>
- {{course.creator}}
- <center><b><u>Teacher(s) Added to {{ course }}</u></b></center>
- {% if course.get_teachers %}
- <div align="left">
- <form action="{{URL_ROOT}}/exam/manage/remove_teachers/{{ course.id }}/" method="post">
- {% csrf_token %}
- {% for teacher in course.get_teachers %}
- <div class="well">
- <div class="row">
- <div class="col-md-3" style="width: auto;">
- <input type="checkbox" name="remove" value="{{ teacher.id }}">&nbsp;{{ teacher.get_full_name }}
- </div>
- </div>
- </div>
- {% endfor %}
- <button class="btn btn-danger" type="submit">Remove Selected</button>
- </div>
- {% else %}
- <center><b>No Teacher(s) Added</b></center>
- {% endif %}
- </form>
- </div>
- </div>
- </div>
- <div>
- <p><b><a href="{{URL_ROOT}}/exam/manage/searchteacher/{{course.id}}/">Add Teacher</a></b></p>
- </div>
- <div class="col-md-2">
- <p><b><u>Quiz(zes)</u></b></p>
- {% if course.get_quizzes %}
- {% for quiz in course.get_quizzes %}
- <a href="{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/{{quiz.id}}/">{{ quiz.description }}</a><br>
- {% endfor %}
- {% else %}
- <p><b>No quiz </b></p>
- {% endif %}
- </div>
- <div class="col-md-4">
- <p><b><u>Question Paper(s)</u></b></p>
- {% for quiz in course.get_quizzes %}
- {% if quiz.questionpaper_set.get %}
- <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/">Question Paper for {{ quiz.description }}</a><br>
+ </li>
+ </ul>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %} <!-- end for modules -->
+ {% else %}
+ No learning modules
+ {% endif %}
+ </table>
+ </td>
+ {% endfor %} <!-- end for courses -->
+ </tr>
+ </table>
+</div>
+{% endif %}
+</div>
+{% endif %}
+<!-- End if all Courses -->
+
+<!-- Show all Quizzes -->
+<div id="all_quizzes" >
+<div class="col-md-offset-2 main">
+{% if type == "quiz" %}
+ <a href="{{URL_ROOT}}/exam/manage/addquiz/" class="btn btn-primary">Add new Quiz</a>
+ {% if not quizzes %}
+ <center><h4> No new Quiz Added</h4></center>
+ <br><br>
+ {% else %}
+ <center><h3> Quizzes </h3></center>
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th>Sr.No</th>
+ <th>Quiz</th>
+ <th>QuestionPaper</th>
+ </tr>
+
+ {% for quiz in quizzes %}
+ <tr>
+ <td>{{forloop.counter}}</td>
+ <td width="30%">
+ <ul class="list-group">
+ <a href="{{URL_ROOT}}/exam/manage/addquiz/{{quiz.id}}/">{{ quiz.description }}
+ </a>
+ {% if quiz.active %}
+ <span class="label label-success">Active</span>
+ {% else %}
+ <span class="label label-danger">Closed</span>
+ {% endif %}
+ </ul>
+ </td>
+ <td>
+ {% if quiz.questionpaper_set.get %}
+ <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/">
+ Question Paper for {{ quiz.description }}</a>
+ <br>
+ {% else %}
+ <p>No Question Paper
+ <a href="#" onClick='location.replace("{{URL_ROOT}}/exam/manage/quiz/designquestionpaper/{{ quiz.id }}/");'>Add</a>
+ </p>
+ {% endif %}
+ </td>
+ {% endfor %} <!-- end for quizzes -->
+ </tr>
+ </table>
+ </div>
+ {% endif %}
+{% endif %}
+</div>
+
+<!-- Show all lessons -->
+
+<div id="all_lessons">
+ <div class="col-md-offset-2 main">
+{% if type == "lesson" %}
+ <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/" class="btn btn-primary">Add new Lesson</a>
+ {% if not lessons %}
+ <center><h4> No new Lessons Added</h4></center>
+ <br><br>
+ {% else %}
+ <center><h3> Lessons </h3></center>
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th>Sr.No</th>
+ <th>Lesson</th>
+ </tr>
+
+ {% for lesson in lessons %}
+ <tr>
+ <td width="2%">{{forloop.counter}}</td>
+ <td width="30%">
+ <ul class="list-group">
+ <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{lesson.id}}/">
+ {{ lesson.name }}</a>
+ </ul>
+ </td>
+ {% endfor %} <!-- end for lessons -->
+ </tr>
+ </table>
+ </div>
+ {% endif %}
+{% endif %}
+</div>
+
+<!-- Show all learning modules -->
+ <div class="col-md-offset-2 main">
+<div id="all_lessons">
+{% if type == "learning_module" %}
+ <a href="{{URL_ROOT}}/exam/manage/courses/add_module/" class="btn btn-primary">
+ Add new Module</a>
+ {% if not learning_modules %}
+ <center><h4> No new learning modules Added</h4></center>
+ <br><br>
+ {% else %}
+ <center><h3> Learning Modules </h3></center>
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th>Sr.No</th>
+ <th>Learning Modules</th>
+ <th>Design Module</th>
+ <th>Learning Units</th>
+ </tr>
+ {% for module in learning_modules %}
+ <tr>
+ <td width="2%">{{forloop.counter}}</td>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/add_module/{{module.id}}/">
+ {{ module.name }}</a>
+ </td>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/courses/designmodule/{{module.id}}">
+ Add Quizzes/Lessons for {{module.name}}
+ </a>
+ </td>
+ <td>
+ {% if module.get_learning_units %}
+ {% for unit in module.get_learning_units %}
+ <ul class="list-group">
+ {% if unit.type == 'quiz' %}
+ {{unit.quiz.description}}
{% else %}
- <p>No Question Paper
- <a href="#" onClick='location.replace("{{URL_ROOT}}/exam/manage/quiz/designquestionpaper/{{ quiz.id }}/");'>Add</a>
- </p>
+ {{unit.lesson.name}}
{% endif %}
- {% endfor %}
- </div>
- </div>
- <br/>
- <button class="btn btn-primary pull-right"type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/");'>Add New Quiz</button>
- <p><a href="{{URL_ROOT}}/exam/manage/courses/download_course_csv/{{course.id}}">Download CSV</a></p>
- </div>
+ </ul>
+ {% endfor %} <!-- end for learning units -->
+ {% else %}
+ No Learning units
+ {% endif %}
+ </td>
+ {% endfor %} <!-- end for modules -->
+ </tr>
+ </table>
</div>
- <br><br>
- {% endfor %}
-{% else %}
- <center><h4> No new Courses allotted</h4></center>
- <br><br>
+ {% endif %}
{% endif %}
-<hr/>
-<center><button class="btn primary" type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/add_course");'>Add New Course</button></center>
+</div>
+
{% endblock %}
diff --git a/yaksh/templates/yaksh/design_course_session.html b/yaksh/templates/yaksh/design_course_session.html
new file mode 100644
index 0000000..ee530e0
--- /dev/null
+++ b/yaksh/templates/yaksh/design_course_session.html
@@ -0,0 +1,125 @@
+{% extends "manage.html" %}
+{% load custom_filters %}
+{% block title %}Design Course Session{% endblock %}
+
+{% block pagetitle %}Design Course Session{% endblock %}
+
+{% block script %}
+<script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.9.1.min.js"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/design_course.js"></script>
+{% endblock %}
+
+{% block css %}
+<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/design_course.css" />
+{% endblock %}
+
+{% block main %}
+<a href="{{URL_ROOT}}/exam/manage/courses/" class="btn btn-primary">
+ Back to Courses
+</a>
+<form action="{{URL_ROOT}}/exam/manage/courses/designcourse/{{course_id}}/" method="POST" id="design_course_form">
+{% csrf_token %}
+ <div class="tab-pane active" id="available-lesson-quiz">
+ <div class="row">
+ <div class="col-md-8 col-md-offset-2 available-list">
+ <div id="fixed-available-wrapper">
+ <p><u><b>Available Lessons and quizzes: (Add Lessons and Quizzes)</b></u></p>
+ <div id="fixed-available">
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th width="2%">Select</th>
+ <th>Modules</th>
+ <th>Lessons/Quizzes</th>
+ </tr>
+ {% for module in learning_modules %}
+ <ul class="inputs-list">
+ <tr>
+ <td><input type="checkbox" name="module_list" value="{{module.id}}"></td>
+ <td><span>{{ module.name }}</span></td>
+ <td>
+ {% for unit in module.get_learning_units %}
+ <ul class="inputs-list">
+ <li>
+ {% if unit.type == "quiz" %}
+ {{unit.quiz.description}}
+ {% else %}
+ {{unit.lesson.name}}
+ {% endif %}
+ </li>
+ </ul>
+ {% endfor %}
+ </td>
+ </li>
+ </tr>
+ </ul>
+ {% endfor %}
+ </table>
+ </div>
+ </div>
+ <br>
+ <center>
+ <button id="Add" name="Add" class="btn btn-success" type="submit">Add to course</button>
+ </center>
+ <br><br>
+ </div>
+ <div class="col-md-8 col-md-offset-2">
+ <div id="fixed-added-wrapper">
+ <p><u><b>Choosen Lessons and quizzes:</b></u></p>
+ <div id="fixed-added">
+ <table id="course-details" class="table table-bordered">
+ <tr>
+ <th width="5%">Select</th>
+ <th>Module</th>
+ <th width="20%">Order</th>
+ <th width="25%" colspan="2">Check Prerequisite
+ <br>
+ <a href="#" data-toggle="tooltip" id="prereq_msg">
+ <span class="glyphicon glyphicon-question-sign">
+ </span> What's This
+ </a>
+ </th>
+ </tr>
+ <tr>
+ <th scope="row">&nbsp;</th>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <th>Currently</th>
+ <th>Change</th>
+ </tr>
+ {% for module in added_learning_modules %}
+ <tr>
+ <ul class="inputs-list">
+ <td>
+ <input type="checkbox" name="delete_list" value="{{module.id}}">
+ </td>
+ <td><span>{{ module.name }}</span></td>
+ <td>
+ <input type="number" name="order" data-item-id="{{module.id}}" value="{{module.order}}" step="1">
+ </td>
+ <td>
+ {% if module.check_prerequisite %}
+ Yes
+ {% else %}
+ No
+ {% endif %}
+ </td>
+ <td>
+ <input type="checkbox" name="check_prereq" value="{{module.id}}">
+ </td>
+ </ul>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ </div>
+ <br>
+ <center>
+ <button id="Remove" name="Remove" class="btn btn-danger" type="submit">Remove from course</button>
+ <button id="Change" name="Change" class="btn btn-info" type="submit"> Change Order</button>
+ <button id="Change" name="Change_prerequisite" class="btn btn-primary" type="submit"> Change Prerequisite</button>
+ </center>
+ </div>
+ </div> <!-- /.row -->
+ </div>
+</form>
+{% endblock %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html
index facb579..93f00e0 100644
--- a/yaksh/templates/yaksh/grade_user.html
+++ b/yaksh/templates/yaksh/grade_user.html
@@ -33,7 +33,7 @@ $(document).ready(function()
{% if course.get_quizzes %}
<td>
{% for quiz in course.get_quizzes %}
- <li class="list-group-item"><a href = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz.id}}">
+ <li class="list-group-item"><a href = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz.id}}/{{course.id}}/">
{{quiz.description}}
</a></li>
{% endfor %}
@@ -51,7 +51,7 @@ $(document).ready(function()
{% if users %}
<div id = "student" class="col-md-2">
{% for user in users %}
- <p><a href = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user.user__id}}">
+ <p><a href = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user.user__id}}/{{course_id}}/">
{{user.user__first_name}} {{user.user__last_name}}</a></p>
{% endfor %}
</div>
@@ -94,7 +94,7 @@ Attempt Number: <b>{{paper.attempt_number}} </b>
<select id = "attempt" onchange="window.location.href=this.value">
<option selected="">Select attempt number</option>
{%for attempt in attempts %}
-<option value = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user_id}}/{{attempt.attempt_number}}/">
+<option value = "{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user_id}}/{{attempt.attempt_number}}/{{course_id}}/">
{{attempt.attempt_number}}
</option>
{% endfor %}
@@ -140,11 +140,7 @@ Status : <b style="color: red;"> Failed </b><br/>
<h3> Answers </h3><br>
<form name=frm id="q{{ paper.quiz.id }}_form"
- {% if data.questionpaperid %}
- action="{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user_id}}/{{paper.attempt_number}}/"
- {% else %}
- action="{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user_id}}/{{paper.attempt_number}}/"
- {% endif %}
+ action="{{URL_ROOT}}/exam/manage/gradeuser/{{quiz_id}}/{{user_id}}/{{paper.attempt_number}}/{{course_id}}/"
method="post">
{% csrf_token %}
diff --git a/yaksh/templates/yaksh/intro.html b/yaksh/templates/yaksh/intro.html
index 3b9ba82..6bd2fab 100644
--- a/yaksh/templates/yaksh/intro.html
+++ b/yaksh/templates/yaksh/intro.html
@@ -20,22 +20,27 @@
<br/>
</div>
{% endif %}
- <p> Welcome <strong>{{user.first_name.title}} {{user.last_name.title}}</strong>, to the programming quiz! </p>
+ <p> Welcome <strong>{{user.get_full_name|title}}</strong>, to the programming quiz! </p>
{{ questionpaper.quiz.instructions|safe }}
<div class="row">
<div class="col-md-6">
- {% if user == "moderator" %}
- <form action="{{URL_ROOT}}/exam/manage/" method="post" align="center">
- {%else%}
- <form action="{{URL_ROOT}}/exam/quizzes/" method="post" align="center">
- {% endif %}
- {% csrf_token %}
- <center><button class="btn btn-primary" name="home">Home</button></center>
- </form>
+ <center>
+ {% if status != "moderator" %}
+ <a href="{{URL_ROOT}}/exam/quizzes/view_module/{{module.id}}/{{course.id}}" class="btn btn-primary" name="home">
+ <span class=" glyphicon glyphicon-arrow-left">
+ </span>
+ Go Back</a>
+ {% else %}
+ <a href="{{URL_ROOT}}/exam" class="btn btn-primary" name="home">
+ <span class=" glyphicon glyphicon-arrow-left">
+ </span>
+ Go Back</a>
+ {% endif %}
+ </center>
</div>
<div class="col-md-6">
{% if not questionpaper.quiz.is_expired %}
- <form action="{{URL_ROOT}}/exam/start/{{ attempt_num }}/{{ questionpaper.id }}/" method="post" align="center">
+ <form action="{{URL_ROOT}}/exam/start/{{ attempt_num }}/{{module.id}}/{{ questionpaper.id }}/{{course.id}}/" method="post" align="center">
{% csrf_token %}
<center><button class="btn btn-success" type="submit" name="start"> Start Exam <span class="glyphicon glyphicon-chevron-right"></span></button></center>
</form>
diff --git a/yaksh/templates/yaksh/moderator_dashboard.html b/yaksh/templates/yaksh/moderator_dashboard.html
index 25bd580..17a4924 100644
--- a/yaksh/templates/yaksh/moderator_dashboard.html
+++ b/yaksh/templates/yaksh/moderator_dashboard.html
@@ -1,4 +1,5 @@
{% extends "manage.html" %}
+{% load custom_filters %}
{% block pagetitle %} Moderator's Dashboard {% endblock pagetitle %}
{% block script %}
@@ -9,31 +10,48 @@
<center><h4>List of quizzes! Click on the given links to have a look at answer papers for a quiz.</h4></center>
<table class="table table-bordered">
- <th>Course</th>
- <th>Quiz</th>
- <th>Taken By</th>
- <th>No. of users Passed</th>
- <th>No. of users Failed</th>
- {% for paper, answer_papers, users_passed, users_failed in users_per_paper %}
+ <th>Courses</th>
+ <th>Quizzes</th>
+ {% for course in courses %}
<tr>
<td>
- {{ paper.quiz.course.name }}
+ <a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}">
+ {{ course }}
+ </a>
</td>
<td>
- <a href="{{URL_ROOT}}/exam/manage/monitor/{{ paper.quiz.id }}/">{{ paper.quiz.description }}</a>
- </td>
- <td>
- {{ answer_papers|length }} user(s)
- </td>
- <td>
- {{ users_passed }}
- </td>
- <td>
- {{ users_failed }}
+ {% get_course_details course as course_details %}
+ {% if course_details %}
+ <table class="table table-bordered">
+ <th>Quiz</th>
+ <th>Taken By</th>
+ <th>No. of users Passed</th>
+ <th>No. of users Failed</th>
+ {% for quiz, users_no, passed, failed in course_details %}
+ <tr>
+ <td>
+ <a href="{{URL_ROOT}}/exam/manage/monitor/{{ quiz.id }}/{{course.id}}">{{ quiz.description }}</a>
+ </td>
+ <td>
+ {{users_no}} user(s)
+ </td>
+ <td>
+ {{passed}}
+ </td>
+ <td>
+ {{failed}}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ No Quizzes
+ {% endif %}
</td>
</tr>
{% endfor %}
</table>
+
<hr>
<center>
<a href="{{URL_ROOT}}/exam/manage/add_course" class="btn btn-default">
@@ -46,7 +64,7 @@
<span class="glyphicon glyphicon-question-sign"></span> What's This
</a>
<div id="help" class="collapse">
- <ul>
+ <ul class="list">
<li>A Demo Course and Demo Quiz will be created (Click Courses link on nav bar to view courses).</li>
<li>Some Demo Questions are also created for you (Click Questions link on nav bar to view questions).</li>
<li>In Courses you can view Demo Quiz.</li>
@@ -70,7 +88,7 @@
{% for paper in trial_paper %}
<tr>
<td> <input type = "checkbox" name="delete_paper" class="check" value = {{paper.id}}></input></td>
- <td> <a href="{{URL_ROOT}}/exam/manage/gradeuser/{{paper.question_paper.quiz.id}}">{{paper.question_paper.quiz.description}}</a></td>
+ <td> <a href="{{URL_ROOT}}/exam/manage/gradeuser/{{paper.question_paper.quiz.id}}/{{paper.course.id}}/">{{paper.question_paper.quiz.description}}</a></td>
</tr>
{% endfor %}
</table>
diff --git a/yaksh/templates/yaksh/monitor.html b/yaksh/templates/yaksh/monitor.html
index 8df2e7d..e40293b 100644
--- a/yaksh/templates/yaksh/monitor.html
+++ b/yaksh/templates/yaksh/monitor.html
@@ -46,7 +46,7 @@ $(document).ready(function()
{% if course.get_quizzes %}
<td>
{% for quiz in course.get_quizzes %}
- <li class="list-group-item"><a href = "{{URL_ROOT}}/exam/manage/monitor/{{quiz.id}}">
+ <li class="list-group-item"><a href = "{{URL_ROOT}}/exam/manage/monitor/{{quiz.id}}/{{course.id}}/">
{{quiz.description}}
</a></li>
{% endfor %}
@@ -64,7 +64,7 @@ $(document).ready(function()
{% if msg != "Monitor" %}
{% if quiz %}
{% if papers %}
-<p>Course Name: {{ quiz.course.name }}</p>
+<p>Course Name: {{ course.name }}</p>
<p>Quiz Name: {{ quiz.description }}</p>
<p>Number of papers: {{ papers|length }} </p>
{% completed papers as completed_papers %}
@@ -75,7 +75,7 @@ $(document).ready(function()
{# template tag used to get the count of inprogress papers #}
<p>Papers in progress:<b> {{ inprogress_papers }} </b></p>
-<p><a href="{{URL_ROOT}}/exam/manage/statistics/question/{{papers.0.question_paper.id}}">Question Statisitics</a></p>
+<p><a href="{{URL_ROOT}}/exam/manage/statistics/question/{{papers.0.question_paper.id}}/{{course.id}}">Question Statisitics</a></p>
<p>
<button type="button" class="btn btn-info btn-lg" data-toggle="modal" data-target="#csvModal">
Download CSV <span class="glyphicon glyphicon-save"></span>
@@ -97,7 +97,7 @@ $(document).ready(function()
<tbody>
{% for paper in latest_attempts %}
<tr>
- <td> <a href="{{URL_ROOT}}/exam/manage/user_data/{{paper.user.id}}/{{paper.question_paper.id}}">{{ paper.user.get_full_name.title }}</a> </td>
+ <td> <a href="{{URL_ROOT}}/exam/manage/user_data/{{paper.user.id}}/{{paper.question_paper.id}}/{{course.id}}">{{ paper.user.get_full_name.title }}</a> </td>
<td> {{ paper.user.username }} </td>
<td> {{ paper.user.profile.roll_number }} </td>
<td> {{ paper.user.profile.institute }} </td>
@@ -129,7 +129,7 @@ $(document).ready(function()
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h3 class="modal-title">Uncheck unwanted columns</h3>
</div>
- <form action="{{URL_ROOT}}/exam/manage/download_quiz_csv/{{ quiz.course.id }}/{{ quiz.id }}/" method="post">
+ <form action="{{URL_ROOT}}/exam/manage/download_quiz_csv/{{ course.id }}/{{ quiz.id }}/" method="post">
{% csrf_token %}
<div class="modal-body">
{% for field in csv_fields %}
diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html
index 35125f4..d65e513 100644
--- a/yaksh/templates/yaksh/question.html
+++ b/yaksh/templates/yaksh/question.html
@@ -64,7 +64,6 @@ function updateClock(){
clock.innerHTML = "<center><strong>" + hh + ":" + mm + ":" + ss + "</strong></center>";
}
}
-
var clock = document.getElementById("time_left");
updateClock();
var timeinterval = setInterval(updateClock,1000);
@@ -87,6 +86,8 @@ function call_skip(url)
}
init_val = '{{ last_attempt|escape_quotes|safe }}';
lang = "{{ question.language }}"
+course_id = "{{course.id}}"
+module_id = "{{module.id}}"
question_type = "{{ question.type }}"
</script>
@@ -95,6 +96,40 @@ question_type = "{{ question.type }}"
{% block onload %} onload="updateTime();" {% endblock %}
+{% block learning_units %}
+<center><h3>Lessons/Quizzes</h3></center>
+<ul class="list">
+{% for unit in module.get_learning_units %}
+<span>
+<li>
+
+{% get_unit_status course module unit user as status %}
+
+{% if unit.quiz.id == paper.question_paper.quiz.id %}
+ <span class=" glyphicon glyphicon-edit" data-toggle="tooltip" title="Currently on"></span>
+{% endif %}
+
+{% if unit.type == "quiz" %}
+ <a href="{{ URL_ROOT }}/exam/start/{{unit.quiz.questionpaper_set.get.id}}/{{module.id}}/{{course.id}}">
+ {{ unit.quiz.description }}
+ </a>
+{% else %}
+ <a href="{{ URL_ROOT }}/exam/show_lesson/{{unit.lesson.id}}/{{module.id}}/{{course.id}}">
+ {{ unit.lesson.name }}
+ </a>
+{% endif %}
+{% if status == "completed" %}
+ <span class="glyphicon glyphicon-ok" style="color: #7CFC00;"></span>
+{% else %}
+ <span class="glyphicon glyphicon-remove" style="color: #FF0000"></span>
+{% endif %}
+</li>
+</span>
+<br>
+{% endfor %}
+</ul>
+{% endblock %}
+
{% block main %}
<p id="status"></p>
{% if notification %}
@@ -111,7 +146,7 @@ question_type = "{{ question.type }}"
<div id="notification" role="alert">
</div>
{% endif %}
- <form id="code" action="{{URL_ROOT}}/exam/{{ question.id }}/check/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" enctype="multipart/form-data">
+ <form id="code" action="{{URL_ROOT}}/exam/{{ question.id }}/check/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type=hidden name="question_id" id="question_id" value={{ question.id }}></input>
<div class="panel panel-default">
@@ -225,7 +260,7 @@ question_type = "{{ question.type }}"
{% if paper.question_paper.quiz.allow_skip and not paper.get_questions_unanswered|length_is:"1" %}
{% if question in paper.get_questions_unanswered %}
- <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>
+ <button class="btn btn-primary" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')" name="skip" id="skip">Attempt Later <span class="glyphicon glyphicon-arrow-right"></span></button>
{% endif %}
{% endif %}
</div>
diff --git a/yaksh/templates/yaksh/quit.html b/yaksh/templates/yaksh/quit.html
index ef5e6a4..78a9b47 100644
--- a/yaksh/templates/yaksh/quit.html
+++ b/yaksh/templates/yaksh/quit.html
@@ -4,7 +4,7 @@
width="80" alt="YAKSH"></img> {% endblock %}
{% block content %}
<center><table class="table table-bordered" >
- <caption> Submission Status </caption>
+ <caption> <center><h3>Submission Status</h3> </center> </caption>
<thead>
<tr>
<th> Question</th>
@@ -31,8 +31,11 @@ width="80" alt="YAKSH"></img> {% endblock %}
<center><h4>Your current answers are saved.</h4></center>
<center><h4> Are you sure you wish to quit the exam?</h4></center>
<center><h4> Be sure, as you won't be able to restart this exam.</h4></center>
- <form action="{{URL_ROOT}}/exam/complete/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post">
+ <form action="{{URL_ROOT}}/exam/complete/{{ paper.attempt_number }}/{{module_id}}/{{ paper.question_paper.id }}/{{course_id}}/" method="post">
{% csrf_token %}
- <center><button class="btn" type="submit" name="yes">Yes!</button>&nbsp;<button class="btn" type="button" name="no" onClick="window.location='{{ URL_ROOT }}/exam/start/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/'">No!</button></center>
+ <center>
+ <button class="btn" type="submit" name="yes">Yes!</button>&nbsp;
+ <button class="btn" type="button" name="no" onClick="window.location='{{ URL_ROOT }}/exam/start/{{ paper.attempt_number }}/{{module_id}}/{{ paper.question_paper.id }}/{{course_id}}'">No!</button>
+ </center>
</form>
{% endblock content %}
diff --git a/yaksh/templates/yaksh/quizzes_user.html b/yaksh/templates/yaksh/quizzes_user.html
index b90db18..cf08752 100644
--- a/yaksh/templates/yaksh/quizzes_user.html
+++ b/yaksh/templates/yaksh/quizzes_user.html
@@ -1,9 +1,15 @@
{% extends "user.html" %}
+{% block title %} Student Dashboard {% endblock %}
{% block pagetitle %} {{ title }} {% endblock %}
{% block main %}
+ {% if msg %}
+ <div class="alert alert-warning" role="alert">
+ <center>{{ msg }}</center>
+ </div>
+ {% endif %}
{% if 'Enrolled Courses' not in title%}
<div class="row well">
- <form action="{{ URL_ROOT }}/exam/quizzes/" method="post" id="custom-search-form" class="form-search form-horizontal pull-right">
+ <form action="{{ URL_ROOT }}/exam/quizzes/" method="post" id="custom-search-form" class="form-search form-horizontal">
{% csrf_token %}
<div class="col-md-12">
<div class="input-group">
@@ -26,7 +32,11 @@ No Courses to display
<div class="col-md-12">
<div class="row">
<div class="col-md-4">
- <h4><b><u> {{ course.name }} by {{ course.creator.get_full_name }}</u></b></h4>
+ <h4><b>
+ <a href="{{URL_ROOT}}/exam/course_modules/{{course.id}}">
+ {{ course.name }} by {{ course.creator.get_full_name }}
+ </a>
+ </b></h4>
</div>
<div class="col-md-4">
{% if not course.active %}
@@ -49,45 +59,6 @@ No Courses to display
</div>
</div>
- <div class="row">
- {% 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 and course.active %}
- <td>
- <a href="{{ URL_ROOT }}/exam/start/{{quiz.questionpaper_set.get.id}}">{{ quiz.description }}</a><br>
- </td>
- {% else %}
- <td>
- {{ quiz.description }} <span class="label label-danger">Inactive</span><br>
- </td>
- {% 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>
-
{% if course.instructions %}
<div class="row">
<div class="panel-group">
diff --git a/yaksh/templates/yaksh/regrade.html b/yaksh/templates/yaksh/regrade.html
index 844c6ee..77e28df 100644
--- a/yaksh/templates/yaksh/regrade.html
+++ b/yaksh/templates/yaksh/regrade.html
@@ -33,7 +33,7 @@
<a href="#questions_quizzes{{ course.id }}" data-toggle="collapse">Course: {{ course }}</a>
</span></h4>
<div id="questions_quizzes{{ course.id }}" class="collapse">
- {% for quiz in course.quiz_set.all %}
+ {% for quiz in course.get_quizzes %}
<p><a href="#questions_questions{{ course.id }}{{ quiz.id }}" data-toggle="collapse">Quiz: {{ quiz }}</a></p>
<div id="questions_questions{{ course.id }}{{ quiz.id }}" class="collapse">
{% with questionpaper=quiz.questionpaper_set.get %}
@@ -69,7 +69,7 @@
<a href="#quizzes_quizzes{{ course.id }}" data-toggle="collapse">Course: {{ course }}</a>
</span></h4>
<div id="quizzes_quizzes{{ course.id }}" class="collapse">
- {% for quiz in course.quiz_set.all %}
+ {% for quiz in course.get_quizzes %}
<p><a href="#quizzes_papers{{ course.id }}{{ quiz.id }}" data-toggle="collapse">Quiz: {{ quiz }}</a></p>
<div id="quizzes_papers{{ course.id }}{{ quiz.id }}" class="collapse">
<ol class="list-group">
diff --git a/yaksh/templates/yaksh/show_video.html b/yaksh/templates/yaksh/show_video.html
new file mode 100644
index 0000000..0490697
--- /dev/null
+++ b/yaksh/templates/yaksh/show_video.html
@@ -0,0 +1,90 @@
+{% extends "user.html" %}
+{% load custom_filters %}
+
+{% block title %} {{ learning_module.name|title }} {% endblock %}
+
+{% block pagetitle %} {{ learning_module.name|title }} {% endblock %}
+
+{% block main %}
+<div class="col-sm-3 col-md-2 sidebar">
+ <center><h3>Lessons/Quizzes</h3></center>
+ <ul class="list">
+ {% for unit in learning_units %}
+ <span>
+ <li>
+ {% get_unit_status course learning_module unit user as status %}
+
+ {% if unit.id == current_unit.id %}
+ <span class="glyphicon glyphicon-edit" data-toggle="tooltip" title="Currently on">
+ </span>
+ {% endif %}
+ {% if unit.type == "quiz" %}
+ <a href="{{ URL_ROOT }}/exam/start/{{unit.quiz.questionpaper_set.get.id}}/{{learning_module.id}}/{{course.id}}">
+ {{ unit.quiz.description }}
+ </a>
+ {% else %}
+ <a href="{{ URL_ROOT }}/exam/show_lesson/{{unit.lesson.id}}/{{learning_module.id}}/{{course.id}}">
+ {{ unit.lesson.name }}
+ </a>
+ {% endif %}
+ {% if status == "completed" %}
+ <span class="glyphicon glyphicon-ok" style="color: #7CFC00;"></span>
+ {% else %}
+ <span class="glyphicon glyphicon-remove" style="color: #FF0000"></span>
+ {% endif %}
+ </li>
+ </span>
+ <br>
+ {% endfor %}
+ </ul>
+</div>
+{% if msg %}
+<center>
+<div class="col-md-12 col-md-offset-1 main">
+<div class="alert alert-warning">{{msg}}</div></div>
+</center>
+{% endif %}
+<div class="col-md-12 col-md-offset-1 main">
+{% if state == "module" %}
+<div class="panel panel-default" style="border: none; box-shadow: none;">
+ <div class="panel-body">
+ {{learning_module.html_data|safe}}
+ </div>
+</div>
+<div style="text-align: center;">
+ <a href="{{ URL_ROOT }}/exam/next_unit/{{course.id}}/{{learning_module.id}}/{{first_unit.id}}/1" class="btn btn-info">First Unit
+ <span class="glyphicon glyphicon-chevron-right">
+ </span>
+ </a>
+</div>
+{% else %}
+<center><h3>{{lesson.name}}</h3></center>
+<div class="panel panel-default" style="border: none; box-shadow: none;">
+ <div class="panel-body">
+ {{lesson.html_data|safe}}
+ </div>
+</div>
+{% if lesson.get_files %}
+ <div class="panel" style="border-width: 10px;">
+ <center>
+ <div class="alert alert-success">
+ <h4>Files for this lesson</h4>
+ </div>
+ {% for f in lesson.get_files %}
+ <h4>
+ <b>{{forloop.counter}}.</b>
+ <a href="{{f.file.url}}">{{ f.file.name|file_title }}</a>
+ </h4><br>
+ {% endfor %}
+ </center>
+ </div>
+{% endif %}
+<div style="text-align: center;">
+ <a href="{{ URL_ROOT }}/exam/next_unit/{{course.id}}/{{learning_module.id}}/{{current_unit.id}}" class="btn btn-info" style="display: inline-block;">Next Unit
+ <span class="glyphicon glyphicon-chevron-right">
+ </span>
+ </a>
+</div>
+{% endif %}
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/statistics_question.html b/yaksh/templates/yaksh/statistics_question.html
index 31e889b..c6f4e57 100644
--- a/yaksh/templates/yaksh/statistics_question.html
+++ b/yaksh/templates/yaksh/statistics_question.html
@@ -6,7 +6,7 @@
<div class="row">
<div class="col-md-2">
{% for attempt in attempts %}
- <p><a href="{{URL_ROOT}}/exam/manage/statistics/question/{{questionpaper_id}}/{{attempt}}">Attempt {{ attempt }}</a></p>
+ <p><a href="{{URL_ROOT}}/exam/manage/statistics/question/{{questionpaper_id}}/{{attempt}}/{{course_id}}">Attempt {{ attempt }}</a></p>
{% endfor %}
</div>
<div class="col-md-9">
diff --git a/yaksh/templates/yaksh/user_data.html b/yaksh/templates/yaksh/user_data.html
index 8eea3f3..45867d2 100644
--- a/yaksh/templates/yaksh/user_data.html
+++ b/yaksh/templates/yaksh/user_data.html
@@ -27,14 +27,9 @@ Last login: {{ data.user.last_login }}
</p>
{% if data.papers %}
-{% if data.questionpaperid %}
-<p><a href="{{URL_ROOT}}/exam/manage/gradeuser/{{data.papers.0.question_paper.quiz.id}}/{{ data.user.id }}">
+<p><a href="{{URL_ROOT}}/exam/manage/gradeuser/{{data.papers.0.question_paper.quiz.id}}/{{ data.user.id }}/{{course_id}}/">
Grade/correct paper</a>
</p>
-{% else %}
-<p><a href="{{URL_ROOT}}/exam/manage/gradeuser/{{data.papers.0.question_paper.quiz.id}}/{{ data.user.id }}">
- Grade/correct paper</a>
-{% endif %}
{% for paper in data.papers %}
{% if forloop.counter == 2 and data.questionpaperid %}
@@ -59,11 +54,11 @@ User IP address: {{ paper.user_ip }}
<div class="panel-heading">
<strong> Details: {{forloop.counter}}. {{ question.summary }}
- <a href="" onClick="grade_data('show_question{{question.id}}'); return false;"> Show Question </a>
+ <a href="" onClick="grade_data('show_question{{question.id}}{{paper.attempt_number}}'); return false;"> Show Question </a>
<span class="marks pull-right"> Mark(s): {{ question.points }} </span>
</strong>
</div>
- <div class="panel-body" id="show_question{{question.id}}" style="display: none;">
+ <div class="panel-body" id="show_question{{question.id}}{{paper.attempt_number}}" style="display: none;">
<h5><u>Question:</u></h5> <strong>{{ question.description|safe }}</strong>
{% if question.type == "mcq" or question.type == "mcc" %}
@@ -242,7 +237,7 @@ User IP address: {{ paper.user_ip }}
<hr />
{% with data.papers.0 as paper %}
-<a href="{{URL_ROOT}}/exam/manage/gradeuser/{{paper.question_paper.quiz.id}}/{{ data.user.id }}/">Grade/correct paper</a>
+<a href="{{URL_ROOT}}/exam/manage/gradeuser/{{paper.question_paper.quiz.id}}/{{ data.user.id }}/{{course_id}}/">Grade/correct paper</a>
{% endwith %}
<br />
diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py
index df6ecce..6ddd213 100644
--- a/yaksh/templatetags/custom_filters.py
+++ b/yaksh/templatetags/custom_filters.py
@@ -1,5 +1,6 @@
from django import template
from django.template.defaultfilters import stringfilter
+import os
try:
from itertools import zip_longest
except ImportError:
@@ -27,3 +28,23 @@ def inprogress(answerpaper):
@register.filter(name='zip')
def zip_longest_out(a, b):
return zip_longest(a, b)
+
+
+@register.filter(name="file_title")
+def file_title(name):
+ return os.path.basename(name)
+
+
+@register.simple_tag
+def get_unit_status(course, module, unit, user):
+ return course.get_unit_completion_status(module, user, unit)
+
+
+@register.simple_tag
+def get_module_status(user, module, course):
+ return module.get_status(user, course)
+
+
+@register.simple_tag
+def get_course_details(course):
+ return course.get_quiz_details()
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index 2eea631..59dea5b 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -1,7 +1,8 @@
import unittest
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
- StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload
+ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload,\
+ LearningModule, LearningUnit, Lesson, LessonFile
from yaksh.code_server import(ServerPool,
get_result as get_result_from_code_server
)
@@ -23,7 +24,7 @@ from yaksh import settings
def setUpModule():
# create user profile
- user = User.objects.create_user(username='demo_user',
+ user = User.objects.create_user(username='creator',
password='demo',
email='demo@test.com')
User.objects.create_user(username='demo_user2',
@@ -61,8 +62,7 @@ def setUpModule():
duration=30, active=True,
attempts_allowed=1, time_between_attempts=0,
description='demo quiz 1', pass_criteria=0,
- language='Python', prerequisite=None,
- course=course, instructions="Demo Instructions")
+ instructions="Demo Instructions")
Quiz.objects.create(start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0,
tzinfo=pytz.utc),
@@ -71,12 +71,35 @@ def setUpModule():
duration=30, active=False,
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz 2', pass_criteria=40,
- language='Python', prerequisite=quiz,
- course=course, instructions="Demo Instructions")
+ instructions="Demo Instructions")
tmp_file1 = os.path.join(tempfile.gettempdir(), "test.txt")
with open(tmp_file1, 'wb') as f:
f.write('2'.encode('ascii'))
+ # Learing module
+ learning_module_one = LearningModule.objects.create(name='LM1',
+ description='module one',
+ creator=user)
+ learning_module_two = LearningModule.objects.create(name='LM2',
+ description='module two',
+ creator=user,
+ order=1)
+ lesson = Lesson.objects.create(name='L1', description='Video Lesson',
+ creator=user)
+ learning_unit_lesson = LearningUnit.objects.create(order=1, lesson=lesson,
+ type='lesson')
+ learning_unit_quiz = LearningUnit.objects.create(order=2, quiz=quiz,
+ type='quiz')
+ learning_module_one.learning_unit.add(learning_unit_lesson)
+ learning_module_one.learning_unit.add(learning_unit_quiz)
+ learning_module_one.save()
+ course.learning_module.add(learning_module_one)
+ course.learning_module.add(learning_module_two)
+ course_user = User.objects.create(username='course_user')
+ course.students.add(course_user)
+ course.save()
+ LessonFile.objects.create(lesson=lesson)
+
def tearDownModule():
User.objects.all().delete()
@@ -84,19 +107,133 @@ def tearDownModule():
Quiz.objects.all().delete()
Course.objects.all().delete()
QuestionPaper.objects.all().delete()
+ LessonFile.objects.all().delete()
+ Lesson.objects.all().delete()
+ LearningUnit.objects.all().delete()
+ LearningModule.objects.all().delete()
###############################################################################
+class LessonTestCases(unittest.TestCase):
+ def setUp(self):
+ self.lesson = Lesson.objects.get(name='L1')
+ self.creator = User.objects.get(username='creator')
+
+ def test_lesson(self):
+ self.assertEqual(self.lesson.name, 'L1')
+ self.assertEqual(self.lesson.description, 'Video Lesson')
+ self.assertEqual(self.lesson.creator.username, self.creator.username)
+
+
+class LearningModuleTestCases(unittest.TestCase):
+ def setUp(self):
+ self.learning_module = LearningModule.objects.get(name='LM1')
+ self.learning_module_two = LearningModule.objects.get(name='LM2')
+ self.creator = User.objects.get(username='creator')
+ self.student = User.objects.get(username='course_user')
+ self.learning_unit_one = LearningUnit.objects.get(order=1)
+ self.learning_unit_two = LearningUnit.objects.get(order=2)
+ self.quiz = Quiz.objects.get(description='demo quiz 1')
+ self.lesson = Lesson.objects.get(name='L1')
+ self.course = Course.objects.get(name='Python Course')
+
+ def test_learning_module(self):
+ self.assertEqual(self.learning_module.description, 'module one')
+ self.assertEqual(self.learning_module.creator, self.creator)
+ self.assertTrue(self.learning_module.check_prerequisite)
+ self.assertEqual(self.learning_module.order, 0)
+
+ def test_get_quiz_units(self):
+ # Given
+ quizzes = [self.quiz]
+ # When
+ module_quizzes = self.learning_module.get_quiz_units()
+ # Then
+ self.assertSequenceEqual(module_quizzes, quizzes)
+
+ def test_get_learning_units(self):
+ # Given
+ learning_units = [self.learning_unit_one, self.learning_unit_two]
+ # When
+ module_units = self.learning_module.get_learning_units()
+ # Then
+ self.assertSequenceEqual(module_units, learning_units)
+
+ def test_get_added_quiz_lesson(self):
+ # Given
+ quiz_lessons = [('lesson', self.lesson), ('quiz', self.quiz)]
+ # When
+ module_quiz_lesson = self.learning_module.get_added_quiz_lesson()
+ # Then
+ self.assertEqual(module_quiz_lesson, quiz_lessons)
+
+ def test_toggle_check_prerequisite(self):
+ self.assertTrue(self.learning_module.check_prerequisite)
+ # When
+ self.learning_module.toggle_check_prerequisite()
+ # Then
+ self.assertFalse(self.learning_module.check_prerequisite)
+
+ # When
+ self.learning_module.toggle_check_prerequisite()
+ # Then
+ self.assertTrue(self.learning_module.check_prerequisite)
+
+ def test_get_next_unit(self):
+ # Given
+ current_unit_id = self.learning_unit_one.id
+ next_unit = self.learning_unit_two
+ # When
+ unit = self.learning_module.get_next_unit(current_unit_id)
+ # Then
+ self.assertEqual(unit, next_unit)
+
+ # Given
+ current_unit_id = self.learning_unit_two.id
+ next_unit = self.learning_unit_one
+ # When
+ unit = self.learning_module.get_next_unit(current_unit_id)
+ # Then
+ self.assertEqual(unit, next_unit)
+
+ def test_get_status(self):
+ # Given
+ module_status = 'not attempted'
+ # When
+ status = self.learning_module.get_status(self.student, self.course)
+ # Then
+ self.assertEqual(status, module_status)
+
+
+class LearningUnitTestCases(unittest.TestCase):
+ def setUp(self):
+ learning_module = LearningModule.objects.get(name='LM1')
+ self.learning_unit_one = learning_module.learning_unit.get(order=1)
+ self.learning_unit_two = learning_module.learning_unit.get(order=2)
+ self.lesson = Lesson.objects.get(name='L1')
+ self.quiz = Quiz.objects.get(description='demo quiz 1')
+
+ def test_learning_unit(self):
+ self.assertEqual(self.learning_unit_one.type, 'lesson')
+ self.assertEqual(self.learning_unit_two.type, 'quiz')
+ self.assertEqual(self.learning_unit_one.lesson, self.lesson)
+ self.assertEqual(self.learning_unit_two.quiz, self.quiz)
+ self.assertIsNone(self.learning_unit_one.quiz)
+ self.assertIsNone(self.learning_unit_two.lesson)
+ self.assertTrue(self.learning_unit_one.check_prerequisite)
+ self.assertTrue(self.learning_unit_two.check_prerequisite)
+
+
class ProfileTestCases(unittest.TestCase):
def setUp(self):
- self.user1 = User.objects.get(username="demo_user")
+ self.user1 = User.objects.get(username='creator')
self.profile = Profile.objects.get(user=self.user1)
self.user2 = User.objects.get(username='demo_user3')
def test_user_profile(self):
""" Test user profile"""
- self.assertEqual(self.user1.username, 'demo_user')
- self.assertEqual(self.profile.user.username, 'demo_user')
+ self.assertEqual(self.user1.username, 'creator')
+ self.assertEqual(self.profile.user.username, 'creator')
self.assertEqual(int(self.profile.roll_number), 1)
self.assertEqual(self.profile.institute, 'IIT')
self.assertEqual(self.profile.department, 'Chemical')
@@ -106,7 +243,7 @@ class ProfileTestCases(unittest.TestCase):
class QuestionTestCases(unittest.TestCase):
def setUp(self):
# Single question details
- self.user1 = User.objects.get(username="demo_user")
+ self.user1 = User.objects.get(username="creator")
self.user2 = User.objects.get(username="demo_user2")
self.question1 = Question.objects.create(summary='Demo Python 1',
language='Python',
@@ -289,7 +426,8 @@ class QuestionTestCases(unittest.TestCase):
###############################################################################
class QuizTestCases(unittest.TestCase):
def setUp(self):
- self.creator = User.objects.get(username="demo_user")
+ self.course = Course.objects.get(name="Python Course")
+ self.creator = User.objects.get(username="creator")
self.teacher = User.objects.get(username="demo_user2")
self.quiz1 = Quiz.objects.get(description='demo quiz 1')
self.quiz2 = Quiz.objects.get(description='demo quiz 2')
@@ -304,19 +442,13 @@ class QuizTestCases(unittest.TestCase):
self.assertEqual(self.quiz1.duration, 30)
self.assertTrue(self.quiz1.active)
self.assertEqual(self.quiz1.description, 'demo quiz 1')
- self.assertEqual(self.quiz1.language, 'Python')
self.assertEqual(self.quiz1.pass_criteria, 0)
- self.assertEqual(self.quiz1.prerequisite, None)
self.assertEqual(self.quiz1.instructions, "Demo Instructions")
def test_is_expired(self):
self.assertFalse(self.quiz1.is_expired())
self.assertTrue(self.quiz2.is_expired())
- def test_has_prerequisite(self):
- self.assertFalse(self.quiz1.has_prerequisite())
- self.assertTrue(self.quiz2.has_prerequisite())
-
def test_get_active_quizzes(self):
quizzes = Quiz.objects.get_active_quizzes()
for quiz in quizzes:
@@ -324,10 +456,7 @@ class QuizTestCases(unittest.TestCase):
def test_create_trial_quiz(self):
"""Test to check if trial quiz is created"""
- trial_quiz = Quiz.objects.create_trial_quiz(self.trial_course,
- self.creator
- )
- self.assertEqual(trial_quiz.course, self.trial_course)
+ trial_quiz = Quiz.objects.create_trial_quiz(self.creator)
self.assertEqual(trial_quiz.duration, 1000)
self.assertEqual(trial_quiz.description, "trial_questions")
self.assertTrue(trial_quiz.is_trial)
@@ -337,8 +466,8 @@ class QuizTestCases(unittest.TestCase):
"""Test to check if a copy of original quiz is created in godmode"""
trial_quiz = Quiz.objects.create_trial_from_quiz(self.quiz1.id,
self.creator,
- True
- )
+ True, self.course.id
+ )[0]
self.assertEqual(trial_quiz.description,
"Trial_orig_id_{}_godmode".format(self.quiz1.id)
)
@@ -354,8 +483,8 @@ class QuizTestCases(unittest.TestCase):
"""Test to check if a copy of original quiz is created in usermode"""
trial_quiz = Quiz.objects.create_trial_from_quiz(self.quiz2.id,
self.creator,
- False
- )
+ False, self.course.id
+ )[0]
self.assertEqual(trial_quiz.description,
"Trial_orig_id_{}_usermode".format(self.quiz2.id))
self.assertTrue(trial_quiz.is_trial)
@@ -386,6 +515,7 @@ class QuizTestCases(unittest.TestCase):
class QuestionPaperTestCases(unittest.TestCase):
@classmethod
def setUpClass(self):
+ self.course = Course.objects.get(name="Python Course")
# All active questions
self.questions = Question.objects.filter(active=True)
self.quiz = Quiz.objects.get(description="demo quiz 1")
@@ -455,7 +585,7 @@ class QuestionPaperTestCases(unittest.TestCase):
# ip address for AnswerPaper
self.ip = '127.0.0.1'
- self.user = User.objects.get(username="demo_user")
+ self.user = User.objects.get(username="creator")
self.attempted_papers = AnswerPaper.objects.filter(
question_paper=self.question_paper,
@@ -465,7 +595,7 @@ class QuestionPaperTestCases(unittest.TestCase):
# For Trial case
self.questions_list = [self.questions[3].id, self.questions[5].id]
self.trial_course = Course.objects.create_trial_course(self.user)
- self.trial_quiz = Quiz.objects.create_trial_quiz(self.trial_course, self.user)
+ self.trial_quiz = Quiz.objects.create_trial_quiz(self.user)
def test_get_question_bank(self):
@@ -533,7 +663,8 @@ class QuestionPaperTestCases(unittest.TestCase):
already_attempted = self.attempted_papers.count()
attempt_num = already_attempted + 1
answerpaper = self.question_paper.make_answerpaper(self.user, self.ip,
- attempt_num)
+ attempt_num,
+ self.course.id)
self.assertIsInstance(answerpaper, AnswerPaper)
paper_questions = answerpaper.questions.all()
self.assertEqual(len(paper_questions), 7)
@@ -541,12 +672,13 @@ class QuestionPaperTestCases(unittest.TestCase):
self.assertTrue(fixed_questions.issubset(set(paper_questions)))
answerpaper.passed = True
answerpaper.save()
- self.assertFalse(self.question_paper.is_prerequisite_passed(self.user))
# test can_attempt_now(self):
- self.assertFalse(self.question_paper.can_attempt_now(self.user))
+ self.assertFalse(self.question_paper.can_attempt_now(self.user,
+ self.course.id))
# trying to create an answerpaper with same parameters passed.
answerpaper2 = self.question_paper.make_answerpaper(self.user, self.ip,
- attempt_num)
+ attempt_num,
+ self.course.id)
# check if make_answerpaper returned an object instead of creating one.
self.assertEqual(answerpaper, answerpaper2)
@@ -593,8 +725,9 @@ class QuestionPaperTestCases(unittest.TestCase):
class AnswerPaperTestCases(unittest.TestCase):
@classmethod
def setUpClass(self):
+ self.course = Course.objects.get(name="Python Course")
self.ip = '101.0.0.1'
- self.user = User.objects.get(username='demo_user')
+ self.user = User.objects.get(username='creator')
self.user2 = User.objects.get(username='demo_user2')
self.profile = self.user.profile
self.quiz = Quiz.objects.get(description='demo quiz 1')
@@ -732,14 +865,14 @@ class AnswerPaperTestCases(unittest.TestCase):
# Create AnswerPaper for user1 and user2
self.user1_answerpaper = self.question_paper2.make_answerpaper(
- self.user, self.ip, 1
+ self.user, self.ip, 1, self.course.id
)
self.user2_answerpaper = self.question_paper2.make_answerpaper(
- self.user2, self.ip, 1
+ self.user2, self.ip, 1, self.course.id
)
self.user2_answerpaper2 = self.question_paper.make_answerpaper(
- self.user2, self.ip, 1
+ self.user2, self.ip, 1, self.course.id
)
settings.code_evaluators['python']['standardtestcase'] = \
"yaksh.python_assertion_evaluator.PythonAssertionEvaluator"
@@ -992,7 +1125,7 @@ class AnswerPaperTestCases(unittest.TestCase):
def test_answerpaper(self):
""" Test Answer Paper"""
- self.assertEqual(self.answerpaper.user.username, 'demo_user')
+ self.assertEqual(self.answerpaper.user.username, 'creator')
self.assertEqual(self.answerpaper.user_ip, self.ip)
questions = self.answerpaper.get_questions()
num_questions = len(questions)
@@ -1165,13 +1298,15 @@ class AnswerPaperTestCases(unittest.TestCase):
class CourseTestCases(unittest.TestCase):
def setUp(self):
self.course = Course.objects.get(name="Python Course")
- self.creator = User.objects.get(username="demo_user")
+ self.creator = User.objects.get(username="creator")
self.template_course_user = User.objects.get(username="demo_user4")
+ self.student = User.objects.get(username="course_user")
self.student1 = User.objects.get(username="demo_user2")
self.student2 = User.objects.get(username="demo_user3")
self.quiz1 = Quiz.objects.get(description='demo quiz 1')
self.quiz2 = Quiz.objects.get(description='demo quiz 2')
self.questions = Question.objects.filter(active=True)
+ self.modules = LearningModule.objects.filter(creator=self.creator)
# create courses with disabled enrollment
self.enroll_request_course = Course.objects.create(
@@ -1223,8 +1358,6 @@ class CourseTestCases(unittest.TestCase):
time_between_attempts=0,
description='template quiz 1',
pass_criteria=40,
- language='Python',
- course=self.template_course,
instructions="Demo Instructions"
)
@@ -1246,11 +1379,7 @@ class CourseTestCases(unittest.TestCase):
active=True,
attempts_allowed=1,
time_between_attempts=0,
- description='template quiz 2',
pass_criteria=0,
- language='Python',
- prerequisite=self.template_quiz,
- course=self.template_course,
instructions="Demo Instructions"
)
@@ -1265,140 +1394,43 @@ class CourseTestCases(unittest.TestCase):
self.questions[3]
)
- def test_create_duplicate_course(self):
- """ Test create_duplicate_course method of course """
- # create a duplicate course
- cloned_course = self.template_course.create_duplicate_course(
- self.template_course_user
- )
- self.assertEqual(cloned_course.name,
- 'Copy Of Template Course to clone')
- self.assertEqual(cloned_course.enrollment,
- self.template_course.enrollment
- )
- self.assertEqual(cloned_course.creator,
- self.template_course_user
- )
- self.assertEqual(cloned_course.start_enroll_time,
- self.template_course.start_enroll_time
- )
- self.assertEqual(cloned_course.end_enroll_time,
- self.template_course.end_enroll_time
- )
-
- # check if attributes are same
- cloned_course_dict = model_to_dict(cloned_course,
- fields=[field.name for field in cloned_course._meta.fields \
- if field.name != 'id']
- )
- template_course_dict = model_to_dict(self.template_course,
- fields=[field.name for field in self.template_course._meta.fields \
- if field.name != 'id']
- )
- self.assertEqual(cloned_course_dict, template_course_dict)
-
- # get duplicate quiz associated with duplicate course
- cloned_quiz = cloned_course.quiz_set.all()[0]
-
- self.assertEqual(cloned_quiz.start_date_time,
- self.template_quiz.start_date_time
- )
- self.assertEqual(cloned_quiz.end_date_time,
- self.template_quiz.end_date_time
- )
- self.assertEqual(cloned_quiz.duration,
- self.template_quiz.duration
- )
- self.assertEqual(cloned_quiz.active,
- self.template_quiz.active
- )
- self.assertEqual(cloned_quiz.attempts_allowed,
- self.template_quiz.attempts_allowed
- )
- self.assertEqual(cloned_quiz.time_between_attempts,
- self.template_quiz.time_between_attempts
- )
- self.assertEqual(cloned_quiz.description,
- 'Copy Of template quiz 1'
- )
- self.assertEqual(cloned_quiz.pass_criteria,
- self.template_quiz.pass_criteria
- )
- self.assertEqual(cloned_quiz.language,
- self.template_quiz.language
- )
- self.assertEqual(cloned_quiz.course,
- cloned_course
- )
- self.assertEqual(cloned_quiz.instructions,
- self.template_quiz.instructions
- )
-
- # Get duplicate questionpaper associated with duplicate quiz
- cloned_qp = cloned_quiz.questionpaper_set.all()[0]
-
- self.assertEqual(cloned_qp.quiz, cloned_quiz)
- self.assertEqual(cloned_qp.total_marks,
- self.template_question_paper.total_marks
- )
- self.assertEqual(cloned_qp.shuffle_questions,
- self.template_question_paper.shuffle_questions
- )
-
- for q in cloned_qp.fixed_questions.all():
- self.assertIn(q, self.template_question_paper.fixed_questions.all())
-
- # get second duplicate quiz associated with duplicate course
- cloned_quiz = cloned_course.quiz_set.all()[1]
-
- self.assertEqual(cloned_quiz.start_date_time,
- self.template_quiz2.start_date_time
- )
- self.assertEqual(cloned_quiz.end_date_time,
- self.template_quiz2.end_date_time
- )
- self.assertEqual(cloned_quiz.duration,
- self.template_quiz2.duration
- )
- self.assertEqual(cloned_quiz.active,
- self.template_quiz2.active
- )
- self.assertEqual(cloned_quiz.attempts_allowed,
- self.template_quiz2.attempts_allowed
- )
- self.assertEqual(cloned_quiz.time_between_attempts,
- self.template_quiz2.time_between_attempts
- )
- self.assertEqual(cloned_quiz.description,
- 'Copy Of template quiz 2'
- )
- self.assertEqual(cloned_quiz.pass_criteria,
- self.template_quiz2.pass_criteria
- )
- self.assertEqual(cloned_quiz.language,
- self.template_quiz2.language
- )
- self.assertEqual(cloned_quiz.course,
- cloned_course
- )
- self.assertEqual(cloned_quiz.instructions,
- self.template_quiz2.instructions
- )
-
- # Get second duplicate questionpaper associated with duplicate quiz
- cloned_qp = cloned_quiz.questionpaper_set.all()[0]
+ def test_get_learning_modules(self):
+ # Given
+ modules = list(self.modules)
+ # When
+ course_modules = self.course.get_learning_modules()
+ # Then
+ self.assertSequenceEqual(list(course_modules), modules)
- self.assertEqual(cloned_qp.quiz, cloned_quiz)
- self.assertEqual(cloned_qp.total_marks,
- self.template_question_paper2.total_marks
- )
- self.assertEqual(cloned_qp.shuffle_questions,
- self.template_question_paper2.shuffle_questions
- )
+ # Given
+ modules = list(self.modules.filter(name='LM1'))
+ module_to_remove = self.modules.get(name='LM2')
+ # When
+ self.course.learning_module.remove(module_to_remove)
+ course_modules = self.course.get_learning_modules()
+ # Then
+ self.assertSequenceEqual(list(course_modules), modules)
- for q in cloned_qp.fixed_questions.all():
- self.assertIn(q, self.template_question_paper2.fixed_questions.all())
+ def test_get_quizzes(self):
+ # Given
+ quizzes = [self.quiz1]
+ # When
+ course_quizzes = self.course.get_quizzes()
+ # Then
+ self.assertSequenceEqual(course_quizzes, quizzes)
+ def test_get_learning_units(self):
+ # Given
+ lesson = Lesson.objects.get(name='L1')
+ self.learning_unit_one = LearningUnit.objects.get(order=1,
+ lesson=lesson)
+ self.learning_unit_two = LearningUnit.objects.get(order=2,
+ quiz=self.quiz1)
+ learning_units = [self.learning_unit_one, self.learning_unit_two]
+ # When
+ course_learning_units = self.course.get_learning_units()
+ # Then
+ self.assertSequenceEqual(course_learning_units, learning_units)
def test_is_creator(self):
""" Test is_creator method of Course"""
@@ -1426,10 +1458,11 @@ class CourseTestCases(unittest.TestCase):
def test_enroll_reject(self):
""" Test enroll, reject, get_enrolled and get_rejected methods"""
- self.assertSequenceEqual(self.course.get_enrolled(), [])
+ self.assertSequenceEqual(self.course.get_enrolled(), [self.student])
was_rejected = False
self.course.enroll(was_rejected, self.student1)
- self.assertSequenceEqual(self.course.get_enrolled(), [self.student1])
+ self.assertSequenceEqual(self.course.get_enrolled(),
+ [self.student1, self.student])
self.assertSequenceEqual(self.course.get_rejected(), [])
was_enrolled = False
@@ -1439,21 +1472,17 @@ class CourseTestCases(unittest.TestCase):
was_rejected = True
self.course.enroll(was_rejected, self.student2)
self.assertSequenceEqual(self.course.get_enrolled(),
- [self.student1, self.student2])
+ [self.student1, self.student2, self.student])
self.assertSequenceEqual(self.course.get_rejected(), [])
was_enrolled = True
self.course.reject(was_enrolled, self.student2)
self.assertSequenceEqual(self.course.get_rejected(), [self.student2])
- self.assertSequenceEqual(self.course.get_enrolled(), [self.student1])
+ self.assertSequenceEqual(self.course.get_enrolled(),
+ [self.student1, self.student])
self.assertTrue(self.course.is_enrolled(self.student1))
- def test_get_quizzes(self):
- """ Test get_quizzes method of Courses"""
- self.assertSequenceEqual(self.course.get_quizzes(),
- [self.quiz1, self.quiz2])
-
def test_add_teachers(self):
""" Test to add teachers to a course"""
self.course.add_teachers(self.student1, self.student2)
@@ -1499,7 +1528,7 @@ class CourseTestCases(unittest.TestCase):
###############################################################################
class TestCaseTestCases(unittest.TestCase):
def setUp(self):
- self.user = User.objects.get(username="demo_user")
+ self.user = User.objects.get(username="creator")
self.question1 = Question(summary='Demo question 1',
language='Python',
type='Code',
@@ -1571,7 +1600,7 @@ class TestCaseTestCases(unittest.TestCase):
class AssignmentUploadTestCases(unittest.TestCase):
def setUp(self):
- self.user1 = User.objects.get(username="demo_user")
+ self.user1 = User.objects.get(username="creator")
self.user1.first_name = "demo"
self.user1.last_name = "user"
self.user1.save()
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 2dddcef..ad5b714 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -9,6 +9,7 @@ except ImportError:
import zipfile
import shutil
from textwrap import dedent
+from markdown import Markdown
from django.contrib.auth.models import Group
from django.contrib.auth import authenticate
@@ -23,7 +24,8 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
AssignmentUpload, FileUpload, McqTestCase, IntegerTestCase, StringTestCase,\
- FloatTestCase, FIXTURES_DIR_PATH
+ FloatTestCase, FIXTURES_DIR_PATH, LearningModule, LearningUnit, Lesson,\
+ LessonFile
from yaksh.decorators import user_has_profile
@@ -492,8 +494,15 @@ class TestMonitor(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
+ self.learning_unit = LearningUnit.objects.create(
+ order=1, type="quiz", quiz=self.quiz)
+ self.learning_module = LearningModule.objects.create(
+ order=1, name="test module", description="test",
+ creator=self.user, check_prerequisite=False)
+ self.learning_module.learning_unit.add(self.learning_unit.id)
+ self.course.learning_module.add(self.learning_module)
self.question = Question.objects.create(
summary="Test_question", description="Add two numbers",
@@ -514,7 +523,7 @@ class TestMonitor(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- percent=1, marks_obtained=1
+ percent=1, marks_obtained=1, course=self.course
)
self.answerpaper.answers.add(self.new_answer)
self.answerpaper.questions_answered.add(self.question)
@@ -529,6 +538,8 @@ class TestMonitor(TestCase):
self.question.delete()
self.question_paper.delete()
self.new_answer.delete()
+ self.learning_module.delete()
+ self.learning_unit.delete()
def test_monitor_denies_student(self):
"""
@@ -568,7 +579,8 @@ class TestMonitor(TestCase):
password=self.user_plaintext_pass
)
response = self.client.get(reverse('yaksh:monitor',
- kwargs={'quiz_id': self.quiz.id}),
+ kwargs={'quiz_id': self.quiz.id,
+ 'course_id': self.course.id}),
follow=True
)
@@ -588,7 +600,8 @@ class TestMonitor(TestCase):
)
response = self.client.get(reverse('yaksh:user_data',
kwargs={'user_id':self.student.id,
- 'questionpaper_id': self.question_paper.id}),
+ 'questionpaper_id': self.question_paper.id,
+ 'course_id': self.course.id}),
follow=True
)
self.assertEqual(response.status_code, 200)
@@ -655,8 +668,15 @@ class TestGradeUser(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
+ self.learning_unit = LearningUnit.objects.create(
+ order=1, type="quiz", quiz=self.quiz)
+ self.learning_module = LearningModule.objects.create(
+ order=1, name="test module", description="test",
+ creator=self.user, check_prerequisite=False)
+ self.learning_module.learning_unit.add(self.learning_unit.id)
+ self.course.learning_module.add(self.learning_module)
self.question = Question.objects.create(
summary="Test_question", description="Add two numbers",
@@ -677,7 +697,7 @@ class TestGradeUser(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- marks_obtained=0.5
+ marks_obtained=0.5, course=self.course
)
self.answerpaper.answers.add(self.new_answer)
self.answerpaper.questions_answered.add(self.question)
@@ -693,6 +713,8 @@ class TestGradeUser(TestCase):
self.question.delete()
self.question_paper.delete()
self.new_answer.delete()
+ self.learning_module.delete()
+ self.learning_unit.delete()
def test_grade_user_denies_student(self):
"""
@@ -731,7 +753,8 @@ class TestGradeUser(TestCase):
password=self.user_plaintext_pass
)
response = self.client.get(reverse('yaksh:grade_user',
- kwargs={"quiz_id": self.quiz.id}),
+ kwargs={"quiz_id": self.quiz.id,
+ 'course_id': self.course.id}),
follow=True
)
self.assertEqual(response.status_code, 200)
@@ -751,6 +774,7 @@ class TestGradeUser(TestCase):
)
response = self.client.get(reverse('yaksh:grade_user',
kwargs={"quiz_id": self.quiz.id,
+ "course_id": self.course.id,
"user_id": self.student.id}),
follow=True
)
@@ -771,6 +795,7 @@ class TestGradeUser(TestCase):
)
self.client.get(reverse('yaksh:grade_user',
kwargs={"quiz_id": self.quiz.id,
+ "course_id": self.course.id,
"user_id": self.student.id}),
follow=True
)
@@ -778,6 +803,7 @@ class TestGradeUser(TestCase):
response = self.client.post(reverse('yaksh:grade_user',
kwargs={"quiz_id": self.quiz.id,
"user_id": self.student.id,
+ "course_id": self.course.id,
"attempt_number": self.answerpaper.attempt_number}),
data={question_marks: 1.0}
)
@@ -848,9 +874,15 @@ class TestDownloadAssignment(TestCase):
end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
- description='demo_quiz', pass_criteria=40,
- language='Python', course=self.course
+ description='demo_quiz', pass_criteria=40
)
+ self.learning_unit = LearningUnit.objects.create(
+ order=1, type="quiz", quiz=self.quiz)
+ self.learning_module = LearningModule.objects.create(
+ order=1, name="test module", description="test",
+ creator=self.user, check_prerequisite=False)
+ self.learning_module.learning_unit.add(self.learning_unit.id)
+ self.course.learning_module.add(self.learning_module)
self.question = Question.objects.create(
summary="Test_question", description="Assignment Upload",
@@ -886,6 +918,8 @@ class TestDownloadAssignment(TestCase):
self.assignment2.delete()
self.quiz.delete()
self.course.delete()
+ self.learning_module.delete()
+ self.learning_unit.delete()
dir_name = self.quiz.description.replace(" ", "_")
file_path = os.sep.join((settings.MEDIA_ROOT, dir_name))
if os.path.exists(file_path):
@@ -999,24 +1033,13 @@ class TestAddQuiz(TestCase):
self.course = Course.objects.create(name="Python Course",
enrollment="Enroll Request", creator=self.user)
- self.pre_req_quiz = Quiz.objects.create(
- start_date_time=datetime(2014, 2, 1, 5, 8, 15, 0, tzone),
- end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
- duration=30, active=True, instructions="Demo Instructions",
- attempts_allowed=-1, time_between_attempts=0,
- description='pre requisite quiz', pass_criteria=40,
- language='Python', prerequisite=None,
- course=self.course
- )
-
self.quiz = Quiz.objects.create(
start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', prerequisite=self.pre_req_quiz,
- course=self.course
+ creator=self.user
)
def tearDown(self):
@@ -1024,18 +1047,16 @@ class TestAddQuiz(TestCase):
self.user.delete()
self.student.delete()
self.quiz.delete()
- self.pre_req_quiz.delete()
self.course.delete()
def test_add_quiz_denies_anonymous(self):
"""
If not logged in redirect to login page
"""
- response = self.client.get(reverse('yaksh:add_quiz',
- kwargs={'course_id': self.course.id}),
+ response = self.client.get(reverse('yaksh:add_quiz'),
follow=True
)
- redirect_destination = '/exam/login/?next=/exam/manage/addquiz/{0}/'.format(self.course.id)
+ redirect_destination = '/exam/login/?next=/exam/manage/addquiz/'
self.assertRedirects(response, redirect_destination)
def test_add_quiz_denies_non_moderator(self):
@@ -1047,8 +1068,7 @@ class TestAddQuiz(TestCase):
password=self.student_plaintext_pass
)
course_id = self.course.id
- response = self.client.get(reverse('yaksh:add_quiz',
- kwargs={'course_id': self.course.id}),
+ response = self.client.get(reverse('yaksh:add_quiz'),
follow=True
)
self.assertEqual(response.status_code, 404)
@@ -1061,8 +1081,7 @@ class TestAddQuiz(TestCase):
username=self.user.username,
password=self.user_plaintext_pass
)
- response = self.client.get(reverse('yaksh:add_quiz',
- kwargs={'course_id': self.course.id})
+ response = self.client.get(reverse('yaksh:add_quiz')
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'yaksh/add_quiz.html')
@@ -1078,7 +1097,7 @@ class TestAddQuiz(TestCase):
)
tzone = pytz.timezone('UTC')
response = self.client.post(reverse('yaksh:edit_quiz',
- kwargs={'course_id':self.course.id, 'quiz_id': self.quiz.id}),
+ kwargs={'quiz_id': self.quiz.id}),
data={
'start_date_time': '2016-01-10 09:00:15',
'end_date_time': '2016-01-15 09:00:15',
@@ -1088,10 +1107,8 @@ class TestAddQuiz(TestCase):
'time_between_attempts': 1,
'description': 'updated demo quiz',
'pass_criteria': 40,
- 'language': 'java',
'instructions': "Demo Instructions",
- 'prerequisite': self.pre_req_quiz.id,
- 'course': self.course.id
+ 'weightage': 1.0
}
)
@@ -1108,12 +1125,9 @@ class TestAddQuiz(TestCase):
self.assertEqual(updated_quiz.time_between_attempts, 1)
self.assertEqual(updated_quiz.description, 'updated demo quiz')
self.assertEqual(updated_quiz.pass_criteria, 40)
- self.assertEqual(updated_quiz.language, 'java')
- self.assertEqual(updated_quiz.prerequisite, self.pre_req_quiz)
- self.assertEqual(updated_quiz.course, self.course)
self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, '/exam/manage/courses/')
+ self.assertRedirects(response, '/exam/manage/courses/all_quizzes/')
def test_add_quiz_post_new_quiz(self):
"""
@@ -1125,8 +1139,7 @@ class TestAddQuiz(TestCase):
)
tzone = pytz.timezone('UTC')
- response = self.client.post(reverse('yaksh:add_quiz',
- kwargs={"course_id": self.course.id}),
+ response = self.client.post(reverse('yaksh:add_quiz'),
data={
'start_date_time': '2016-01-10 09:00:15',
'end_date_time': '2016-01-15 09:00:15',
@@ -1136,10 +1149,8 @@ class TestAddQuiz(TestCase):
'time_between_attempts': 2,
'description': 'new demo quiz',
'pass_criteria': 50,
- 'language': 'python',
'instructions': "Demo Instructions",
- 'prerequisite': self.pre_req_quiz.id,
- 'course': self.course.id
+ 'weightage': 1.0
}
)
quiz_list = Quiz.objects.all().order_by('-id')
@@ -1156,12 +1167,23 @@ class TestAddQuiz(TestCase):
self.assertEqual(new_quiz.time_between_attempts, 2)
self.assertEqual(new_quiz.description, 'new demo quiz')
self.assertEqual(new_quiz.pass_criteria, 50)
- self.assertEqual(new_quiz.language, 'python')
- self.assertEqual(new_quiz.prerequisite, self.pre_req_quiz)
- self.assertEqual(new_quiz.course, self.course)
self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, '/exam/manage/courses/')
+ self.assertRedirects(response, '/exam/manage/courses/all_quizzes/')
+
+ def test_show_all_quizzes(self):
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:show_all_quizzes'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['type'], "quiz")
+ self.assertEqual(response.context['quizzes'][0], self.quiz)
+ self.assertTemplateUsed(response, "yaksh/courses.html")
class TestAddTeacher(TestCase):
@@ -1212,8 +1234,7 @@ class TestAddTeacher(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='pre requisite quiz', pass_criteria=40,
- language='Python', prerequisite=None,
- course=self.course
+ creator=self.user
)
self.quiz = Quiz.objects.create(
@@ -1222,8 +1243,7 @@ class TestAddTeacher(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', prerequisite=self.pre_req_quiz,
- course=self.course
+ creator=self.user
)
def tearDown(self):
@@ -1372,8 +1392,7 @@ class TestRemoveTeacher(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='pre requisite quiz', pass_criteria=40,
- language='Python', prerequisite=None,
- course=self.course
+ creator=self.user
)
self.quiz = Quiz.objects.create(
@@ -1382,8 +1401,7 @@ class TestRemoveTeacher(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', prerequisite=self.pre_req_quiz,
- course=self.course
+ creator=self.user
)
def tearDown(self):
self.client.logout()
@@ -1520,16 +1538,43 @@ class TestCourses(TestCase):
last_name='student_last_name',
email='demo_student@test.com'
)
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Aeronautical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ self.teacher_plaintext_pass = 'teacher'
+ self.teacher = User.objects.create_user(
+ username='teacher',
+ password=self.teacher_plaintext_pass,
+ first_name='teacher_first_name',
+ last_name='teacher_last_name',
+ email='demo_teacher@test.com'
+ )
# Add to moderator group
self.mod_group.user_set.add(self.user1)
self.mod_group.user_set.add(self.user2)
+ self.mod_group.user_set.add(self.teacher)
+
+ # Create a learning module to add to course
+ self.learning_module = LearningModule.objects.create(
+ order=0, name="test module", description="module",
+ check_prerequisite=False, creator=self.teacher)
self.user1_course = Course.objects.create(name="Python Course",
enrollment="Enroll Request", creator=self.user1)
+ # Add teacher to user1 course
+ self.user1_course.teachers.add(self.teacher)
+
self.user2_course = Course.objects.create(name="Java Course",
enrollment="Enroll Request", creator=self.user2)
+ self.user2_course.learning_module.add(self.learning_module)
def tearDown(self):
self.client.logout()
@@ -1538,8 +1583,8 @@ class TestCourses(TestCase):
self.student.delete()
self.user1_course.delete()
self.user2_course.delete()
-
-
+ self.teacher.delete()
+ self.learning_module.delete()
def test_courses_denies_anonymous(self):
"""
@@ -1583,6 +1628,167 @@ class TestCourses(TestCase):
self.assertIn(self.user1_course, response.context['courses'])
self.assertNotIn(self.user2_course, response.context['courses'])
+ def test_teacher_can_design_course(self):
+ """ Check if teacher can design the course i.e add learning modules """
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:design_course',
+ kwargs={"course_id": self.user1_course.id}),
+ data={"Add": "Add",
+ "module_list": [str(self.learning_module.id)]
+ })
+
+ # Test add learning module
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/design_course_session.html')
+ added_learning_module = self.user1_course.learning_module.all().first()
+ self.assertEqual(self.learning_module, added_learning_module)
+ self.assertEqual(added_learning_module.order, 1)
+
+ # Test change order of learning module
+ response = self.client.post(
+ reverse('yaksh:design_course',
+ kwargs={"course_id": self.user1_course.id}),
+ data={"Change": "Change", "ordered_list": ",".join(
+ [str(self.learning_module.id)+":"+"0"]
+ )}
+ )
+ updated_learning_module = LearningModule.objects.get(
+ id=self.learning_module.id)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/design_course_session.html')
+ self.assertEqual(updated_learning_module.order, 0)
+
+ # Test change check prerequisite
+ response = self.client.post(
+ reverse('yaksh:design_course',
+ kwargs={"course_id": self.user1_course.id}),
+ data={"Change_prerequisite": "Change_prerequisite",
+ "check_prereq": [str(self.learning_module.id)]
+ })
+ updated_learning_module = LearningModule.objects.get(
+ id=self.learning_module.id)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/design_course_session.html')
+ self.assertTrue(updated_learning_module.check_prerequisite)
+
+ # Test to remove learning module from course
+ response = self.client.post(
+ reverse('yaksh:design_course',
+ kwargs={"course_id": self.user1_course.id}),
+ data={"Remove": "Remove",
+ "delete_list": [str(self.learning_module.id)]
+ })
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/design_course_session.html')
+ self.assertFalse(self.user1_course.learning_module.all().exists())
+
+ def test_get_course_modules(self):
+ """ Test to check if student gets course modules """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:course_modules',
+ kwargs={"course_id": self.user1_course.id}),
+ follow=True
+ )
+ # Student is not allowed if not enrolled in the course
+ err_msg = "You are not enrolled for this course!"
+ self.assertEqual(response.context['msg'], err_msg)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/quizzes_user.html")
+
+ # enroll student in the course
+ self.user1_course.students.add(self.student)
+ # deactivate the course and check if student is able to view course
+ self.user1_course.active = False
+ self.user1_course.save()
+ response = self.client.get(
+ reverse('yaksh:course_modules',
+ kwargs={"course_id": self.user1_course.id}),
+ follow=True
+ )
+ err_msg = "{0} is either expired or not active".format(
+ self.user1_course.name)
+ self.assertEqual(response.context['msg'], err_msg)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/quizzes_user.html")
+
+ # activate the course and check if students gets course modules
+ self.user1_course.active = True
+ self.user1_course.save()
+ self.user1_course.learning_module.add(self.learning_module)
+ response = self.client.get(
+ reverse('yaksh:course_modules',
+ kwargs={"course_id": self.user1_course.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/course_modules.html")
+ self.assertEqual(response.context['course'], self.user1_course)
+ self.assertEqual(response.context['learning_modules'][0],
+ self.learning_module)
+
+ def test_duplicate_course(self):
+ """ Test To clone/duplicate course """
+
+ # Student Login
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+
+ response = self.client.get(
+ reverse('yaksh:duplicate_course',
+ kwargs={"course_id": self.user2_course.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ # Teacher Login
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+
+ # Denies teacher not added in the course
+ response = self.client.get(
+ reverse('yaksh:duplicate_course',
+ kwargs={"course_id": self.user2_course.id}),
+ follow=True
+ )
+ err_msg = "You do not have permissions"
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/complete.html")
+ self.assertIn(err_msg, response.context['message'])
+
+ # Moderator/Course creator login
+ self.client.login(
+ username=self.user2.username,
+ password=self.user2_plaintext_pass
+ )
+
+ # Allows creator to duplicate the course
+ response = self.client.get(
+ reverse('yaksh:duplicate_course',
+ kwargs={"course_id": self.user2_course.id}),
+ follow=True
+ )
+
+ self.assertEqual(response.status_code, 200)
+ courses = Course.objects.filter(
+ creator=self.user2).order_by("id")
+ self.assertEqual(courses.count(), 2)
+ self.assertEqual(courses.last().creator, self.user2)
+ self.assertEqual(courses.last().name, "Copy Of Java Course")
+ self.assertEqual(courses.last().get_learning_modules()[0].id,
+ self.user2_course.get_learning_modules()[0].id)
+
class TestAddCourse(TestCase):
def setUp(self):
@@ -1632,8 +1838,7 @@ class TestAddCourse(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='pre requisite quiz', pass_criteria=40,
- language='Python', prerequisite=None,
- course=self.course
+ creator=self.user
)
self.quiz = Quiz.objects.create(
@@ -1642,8 +1847,7 @@ class TestAddCourse(TestCase):
duration=30, active=True,
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', prerequisite=self.pre_req_quiz,
- course=self.course
+ creator=self.user
)
def tearDown(self):
@@ -1714,7 +1918,8 @@ class TestAddCourse(TestCase):
self.assertEqual(new_course.enrollment, 'open')
self.assertEqual(new_course.active, True)
self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, '/exam/manage/')
+ self.assertRedirects(response, '/exam/manage/courses',
+ target_status_code=301)
class TestCourseDetail(TestCase):
@@ -2082,12 +2287,11 @@ class TestCourseDetail(TestCase):
response = self.client.post(reverse('yaksh:toggle_course_status',
kwargs={'course_id': self.user1_course.id})
)
- self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.status_code, 302)
course = Course.objects.get(name="Python Course")
self.assertFalse(course.active)
- self.assertEqual(self.user1_course, response.context['course'])
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+ self.assertRedirects(response, '/exam/manage/courses',
+ target_status_code=301)
def test_send_mail_to_course_students(self):
""" Check if bulk mail is sent to multiple students enrolled in a course
@@ -2150,6 +2354,27 @@ class TestCourseDetail(TestCase):
self.assertEqual(get_response.context['course'], self.user1_course)
self.assertEqual(get_response.context['state'], 'mail')
+ def test_download_users_template(self):
+ """ Test to check download users template """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+
+ # Denies student to download users upload template
+ response = self.client.get(reverse('yaksh:download_sample_csv'))
+ self.assertEqual(response.status_code, 404)
+
+ # Moderator Login
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_sample_csv'))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get('Content-Disposition'),
+ 'attachment; filename="sample_user_upload"')
+
class TestEnrollRequest(TestCase):
def setUp(self):
@@ -2281,8 +2506,8 @@ class TestViewAnswerPaper(TestCase):
self.question = Question.objects.create(summary='Dummy', points=1,
type='code', user=self.user1)
- self.quiz = Quiz.objects.create(time_between_attempts=0, course=self.course,
- description='demo quiz', language='Python')
+ self.quiz = Quiz.objects.create(time_between_attempts=0,
+ description='demo quiz')
self.user3 = User.objects.get(username="demo_user3")
self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
total_marks=1.0)
@@ -2292,7 +2517,8 @@ class TestViewAnswerPaper(TestCase):
self.ans_paper = AnswerPaper.objects.create(user=self.user3,
attempt_number=1, question_paper=self.question_paper,
start_time=timezone.now(), user_ip='101.0.0.1',
- end_time=timezone.now()+timezone.timedelta(minutes=20))
+ end_time=timezone.now()+timezone.timedelta(minutes=20),
+ course=self.course)
def tearDown(self):
User.objects.all().delete()
@@ -2305,11 +2531,13 @@ class TestViewAnswerPaper(TestCase):
def test_anonymous_user(self):
# Given, user not logged in
redirect_destination = ('/exam/login/?next=/exam'
- '/view_answerpaper/{0}/'.format(self.question_paper.id))
+ '/view_answerpaper/{0}/{1}'.format(
+ self.question_paper.id, self.course.id))
# When
response = self.client.get(reverse('yaksh:view_answerpaper',
- kwargs={'questionpaper_id': self.question_paper.id}
+ kwargs={'questionpaper_id': self.question_paper.id,
+ 'course_id': self.course.id}
),
follow=True
)
@@ -2331,7 +2559,8 @@ class TestViewAnswerPaper(TestCase):
# When
response = self.client.get(reverse('yaksh:view_answerpaper',
- kwargs={'questionpaper_id': self.question_paper.id}
+ kwargs={'questionpaper_id': self.question_paper.id,
+ 'course_id': self.course.id}
),
follow=True
)
@@ -2354,7 +2583,8 @@ class TestViewAnswerPaper(TestCase):
# When
response = self.client.get(reverse('yaksh:view_answerpaper',
- kwargs={'questionpaper_id': self.question_paper.id}
+ kwargs={'questionpaper_id': self.question_paper.id,
+ 'course_id': self.course.id}
),
follow=True
)
@@ -2368,7 +2598,8 @@ class TestViewAnswerPaper(TestCase):
# When, wrong question paper id
response = self.client.get(reverse('yaksh:view_answerpaper',
- kwargs={'questionpaper_id': 190}
+ kwargs={'questionpaper_id': 190,
+ 'course_id': self.course.id}
),
follow=True
)
@@ -2391,7 +2622,8 @@ class TestViewAnswerPaper(TestCase):
# When
response = self.client.get(reverse('yaksh:view_answerpaper',
- kwargs={'questionpaper_id': self.question_paper.id}
+ kwargs={'questionpaper_id': self.question_paper.id,
+ 'course_id': self.course.id}
),
follow=True
)
@@ -2565,8 +2797,8 @@ class TestGrader(TestCase):
self.question = Question.objects.create(summary='Dummy', points=1,
type='code', user=self.user1)
- self.quiz = Quiz.objects.create(time_between_attempts=0, course=self.course,
- description='demo quiz', language='Python')
+ self.quiz = Quiz.objects.create(time_between_attempts=0,
+ description='demo quiz')
self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
total_marks=1.0)
@@ -2576,7 +2808,8 @@ class TestGrader(TestCase):
self.answerpaper = AnswerPaper.objects.create(user=self.user2,
attempt_number=1, question_paper=self.question_paper,
start_time=timezone.now(), user_ip='101.0.0.1',
- end_time=timezone.now()+timezone.timedelta(minutes=20))
+ end_time=timezone.now()+timezone.timedelta(minutes=20),
+ course=self.course)
def tearDown(self):
User.objects.all().delete()
@@ -2832,7 +3065,7 @@ class TestModeratorDashboard(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
self.question = Question.objects.create(
@@ -2856,20 +3089,22 @@ class TestModeratorDashboard(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- marks_obtained=0.5
+ marks_obtained=0.5, course=self.course
)
self.answerpaper.answers.add(self.new_answer)
self.answerpaper.questions_answered.add(self.question)
self.answerpaper.questions.add(self.question)
# moderator trial answerpaper
+ self.trial_course = Course.objects.create(name="Trial Course",
+ enrollment="Enroll Request", creator=self.user, is_trial=True)
self.trial_quiz = Quiz.objects.create(
start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='trial quiz', pass_criteria=40,
- language='Python', course=self.course, is_trial=True
+ is_trial=True
)
self.trial_question_paper = QuestionPaper.objects.create(
@@ -2887,7 +3122,7 @@ class TestModeratorDashboard(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- marks_obtained=0.5
+ marks_obtained=0.5, course=self.trial_course
)
self.trial_answerpaper.answers.add(self.new_answer1)
self.trial_answerpaper.questions_answered.add(self.question)
@@ -2913,7 +3148,6 @@ class TestModeratorDashboard(TestCase):
follow=True
)
self.assertEqual(response.status_code, 200)
- self.assertRedirects(response, '/exam/quizzes/')
def test_moderator_dashboard_get_for_user_without_profile(self):
"""
@@ -2954,12 +3188,7 @@ class TestModeratorDashboard(TestCase):
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "yaksh/moderator_dashboard.html")
self.assertEqual(response.context['trial_paper'][0], self.trial_answerpaper)
- paper, answer_papers, users_passed, users_failed =\
- response.context['users_per_paper'][0]
- self.assertEqual(paper, self.question_paper)
- self.assertEqual(answer_papers[0], self.answerpaper)
- self.assertEqual(users_passed, 1)
- self.assertEqual(users_failed, 0)
+ self.assertEqual(response.context['courses'][0], self.course)
def test_moderator_dashboard_delete_trial_papers(self):
"""
@@ -2982,7 +3211,7 @@ class TestModeratorDashboard(TestCase):
description=self.trial_question_paper.quiz.description
)
updated_course = Course.objects.filter(
- name=self.trial_question_paper.quiz.course.name)
+ name=self.trial_course.name)
self.assertSequenceEqual(updated_answerpaper, [])
self.assertSequenceEqual(updated_quiz, [])
self.assertSequenceEqual(updated_course, [])
@@ -3103,7 +3332,7 @@ class TestDownloadcsv(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
self.question = Question.objects.create(
@@ -3127,7 +3356,7 @@ class TestDownloadcsv(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- marks_obtained=0.5
+ marks_obtained=0.5, course=self.course
)
self.answerpaper.answers.add(self.new_answer)
self.answerpaper.questions_answered.add(self.question)
@@ -3405,7 +3634,11 @@ class TestShowQuestions(TestCase):
trial_que_paper = QuestionPaper.objects.get(
quiz__description="trial_questions"
)
- redirection_url = "/exam/start/1/{}".format(trial_que_paper.id)
+ trial_course = Course.objects.get(name="trial_course")
+ trial_module = trial_course.learning_module.all()[0]
+ redirection_url = "/exam/start/1/{0}/{1}/{2}".format(
+ trial_module.id, trial_que_paper.id, trial_course.id
+ )
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirection_url, target_status_code=301)
@@ -3427,6 +3660,27 @@ class TestShowQuestions(TestCase):
self.assertTemplateUsed(response, 'yaksh/ajax_question_filter.html')
self.assertEqual(response.context['questions'][0], self.question1)
+ def test_download_question_yaml_template(self):
+ """ Test to check download question yaml template """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+
+ # Denies student to download question yaml template
+ response = self.client.get(reverse('yaksh:download_yaml_template'))
+ self.assertEqual(response.status_code, 404)
+
+ # Moderator Login
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_yaml_template'))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get('Content-Disposition'),
+ 'attachment; filename="questions_dump.yaml"')
+
class TestShowStatistics(TestCase):
def setUp(self):
@@ -3484,7 +3738,7 @@ class TestShowStatistics(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
self.question = Question.objects.create(
@@ -3506,7 +3760,7 @@ class TestShowStatistics(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="completed", passed=True,
- percent=1, marks_obtained=1
+ percent=1, marks_obtained=1, course=self.course
)
self.answerpaper.answers.add(self.new_answer)
self.answerpaper.questions_answered.add(self.question)
@@ -3532,7 +3786,8 @@ class TestShowStatistics(TestCase):
password=self.student_plaintext_pass
)
response = self.client.get(reverse('yaksh:show_statistics',
- kwargs={"questionpaper_id": self.question_paper.id}),
+ kwargs={"questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id}),
follow=True
)
self.assertEqual(response.status_code, 404)
@@ -3546,7 +3801,8 @@ class TestShowStatistics(TestCase):
password=self.user_plaintext_pass
)
response = self.client.get(reverse('yaksh:show_statistics',
- kwargs={'questionpaper_id': self.question_paper.id}),
+ kwargs={'questionpaper_id': self.question_paper.id,
+ "course_id": self.course.id}),
follow=True
)
self.assertEqual(response.status_code, 200)
@@ -3567,7 +3823,8 @@ class TestShowStatistics(TestCase):
)
response = self.client.get(reverse('yaksh:show_statistics',
kwargs={'questionpaper_id': self.question_paper.id,
- 'attempt_number': self.answerpaper.attempt_number}),
+ 'attempt_number': self.answerpaper.attempt_number,
+ "course_id": self.course.id}),
follow=True
)
self.assertEqual(response.status_code, 200)
@@ -3616,9 +3873,17 @@ class TestQuestionPaper(TestCase):
duration=30, active=True, instructions="Demo Instructions",
attempts_allowed=-1, time_between_attempts=0,
description='demo quiz', pass_criteria=40,
- language='Python', course=self.course
+ creator=self.user
)
+ self.learning_unit = LearningUnit.objects.create(
+ order=1, type="quiz", quiz=self.quiz)
+ self.learning_module = LearningModule.objects.create(
+ order=1, name="test module", description="module",
+ check_prerequisite=False, creator=self.user)
+ self.learning_module.learning_unit.add(self.learning_unit.id)
+ self.course.learning_module.add(self.learning_module)
+
# Mcq Question
self.question_mcq = Question.objects.create(
summary="Test_mcq_question", description="Test MCQ",
@@ -3706,7 +3971,7 @@ class TestQuestionPaper(TestCase):
start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
user_ip="127.0.0.1", status="inprogress", passed=False,
- percent=0, marks_obtained=0
+ percent=0, marks_obtained=0, course=self.course
)
self.answerpaper.questions.add(*questions_list)
@@ -3720,6 +3985,8 @@ class TestQuestionPaper(TestCase):
self.question_mcc.delete()
self.question_int.delete()
self.question_paper.delete()
+ self.learning_module.delete()
+ self.learning_unit.delete()
def test_mcq_attempt_right_after_wrong(self):
""" Case:- Check if answerpaper and answer marks are updated after
@@ -3738,7 +4005,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
@@ -3753,7 +4022,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3778,7 +4049,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3793,7 +4066,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
@@ -3818,7 +4093,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcc.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3833,7 +4110,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_mcc.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
@@ -3858,7 +4137,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_int.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3873,7 +4154,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_int.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
@@ -3898,7 +4181,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_str.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3913,7 +4198,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_str.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
@@ -3938,7 +4225,9 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_float.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": right_user_answer}
)
@@ -3953,10 +4242,566 @@ class TestQuestionPaper(TestCase):
self.client.post(
reverse('yaksh:check',
kwargs={"q_id": self.question_float.id, "attempt_num": 1,
- "questionpaper_id": self.question_paper.id}),
+ "questionpaper_id": self.question_paper.id,
+ "course_id": self.course.id,
+ "module_id": self.learning_module.id}),
data={"answer": wrong_user_answer}
)
# Then
wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+
+class TestLearningModule(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Create a student
+ self.student_plaintext_pass = 'demo_student'
+ self.student = User.objects.create_user(
+ username='demo_student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@student.com'
+ )
+
+ # Create a teacher to add to the course
+ self.teacher_plaintext_pass = 'demo_teacher'
+ self.teacher = User.objects.create_user(
+ username='demo_teacher',
+ password=self.teacher_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@student.com'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+ self.mod_group.user_set.add(self.teacher)
+
+ self.course = Course.objects.create(
+ name="Python Course",
+ enrollment="Open Enrollment", creator=self.user)
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+ self.quiz = Quiz.objects.create(
+ start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_date_time=datetime(2199, 10, 9, 10, 8, 15, 0, tzone),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ creator=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(
+ quiz=self.quiz,
+ total_marks=5.0, fixed_question_order=str(self.question.id)
+ )
+
+ self.question_paper.fixed_questions.add(self.question)
+
+ self.lesson = Lesson.objects.create(
+ name="test lesson", description="test description",
+ creator=self.user)
+ # create quiz learning unit
+ self.learning_unit = LearningUnit.objects.create(
+ order=0, type="quiz", quiz=self.quiz)
+ # create lesson learning unit
+ self.learning_unit1 = LearningUnit.objects.create(
+ order=1, type="lesson", lesson=self.lesson)
+ # create learning module
+ self.learning_module = LearningModule.objects.create(
+ order=0, name="test module", description="module",
+ check_prerequisite=False, creator=self.user)
+ self.learning_module.learning_unit.add(self.learning_unit)
+ self.learning_module.learning_unit.add(self.learning_unit1)
+ self.learning_module1 = LearningModule.objects.create(
+ order=0, name="my module", description="my description",
+ check_prerequisite=False, creator=self.user)
+ self.course.teachers.add(self.teacher)
+ self.course.learning_module.add(self.learning_module)
+
+ self.expected_url = "/exam/manage/courses/all_learning_module/"
+
+ def tearDown(self):
+ self.user.delete()
+ self.student.delete()
+ self.teacher.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.learning_unit.delete()
+ self.learning_module.delete()
+
+ def test_add_new_module_denies_non_moderator(self):
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+
+ # Student tries to add learning module
+ response = self.client.post(reverse('yaksh:add_module'),
+ data={"name": "test module1",
+ "description": "my test1",
+ "Save": "Save"})
+ self.assertEqual(response.status_code, 404)
+
+ # Student tries to view learning modules
+ response = self.client.get(reverse('yaksh:show_all_modules'))
+ self.assertEqual(response.status_code, 404)
+
+ def test_add_new_module(self):
+ """ Check if new module is created """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Test add new module
+ response = self.client.post(reverse('yaksh:add_module'),
+ data={"name": "test module1",
+ "description": "my test1",
+ "Save": "Save"})
+
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, self.expected_url)
+ learning_module = LearningModule.objects.get(name="test module1")
+ self.assertEqual(learning_module.description, "my test1")
+ self.assertEqual(learning_module.creator, self.user)
+ self.assertTrue(learning_module.check_prerequisite)
+ self.assertEqual(learning_module.html_data,
+ Markdown().convert("my test1"))
+
+ def test_edit_module(self):
+ """ Check if existing module is editable """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Test add new module
+ response = self.client.post(
+ reverse('yaksh:edit_module',
+ kwargs={"module_id": self.learning_module.id}),
+ data={"name": "test module2",
+ "description": "my test2",
+ "Save": "Save"})
+
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, self.expected_url)
+ learning_module = LearningModule.objects.get(name="test module2")
+ self.assertEqual(learning_module.description, "my test2")
+ self.assertEqual(learning_module.creator, self.user)
+ self.assertFalse(learning_module.check_prerequisite)
+ self.assertEqual(learning_module.html_data,
+ Markdown().convert("my test2"))
+
+ def test_show_all_modules(self):
+ """Try to get all learning modules"""
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_all_modules'))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/courses.html')
+ self.assertEqual(response.context['type'], "learning_module")
+ self.assertEqual(response.context['learning_modules'][0],
+ self.learning_module)
+
+ def test_teacher_can_edit_module(self):
+ """ Check if teacher can edit the module """
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:edit_module',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id}),
+ data={"name": "teacher module 2",
+ "description": "teacher module 2",
+ "Save": "Save"})
+
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, "/exam/manage/courses/")
+ learning_module = LearningModule.objects.get(name="teacher module 2")
+ self.assertEqual(learning_module.description, "teacher module 2")
+ self.assertEqual(learning_module.creator, self.user)
+
+ def test_teacher_can_design_module(self):
+ """ Check if teacher can design the module i.e add learning units """
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:design_module',
+ kwargs={"module_id": self.learning_module1.id,
+ "course_id": self.course.id}),
+ data={"Add": "Add",
+ "choosen_list": ",".join([str(self.quiz.id)+":"+"quiz"])
+ })
+
+ # Test add learning unit
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/add_module.html')
+ learning_unit = self.learning_module1.learning_unit.all().first()
+ self.assertEqual(self.quiz, learning_unit.quiz)
+ self.assertEqual(learning_unit.type, "quiz")
+ self.assertEqual(learning_unit.order, 1)
+
+ # Test change order of learning unit
+ response = self.client.post(
+ reverse('yaksh:design_module',
+ kwargs={"module_id": self.learning_module1.id,
+ "course_id": self.course.id}),
+ data={"Change": "Change",
+ "ordered_list": ",".join([str(learning_unit.id)+":"+"0"])
+ })
+ updated_learning_unit = LearningUnit.objects.get(id=learning_unit.id)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/add_module.html')
+ self.assertEqual(updated_learning_unit.order, 0)
+
+ # Test change check prerequisite
+ response = self.client.post(
+ reverse('yaksh:design_module',
+ kwargs={"module_id": self.learning_module1.id,
+ "course_id": self.course.id}),
+ data={"Change_prerequisite": "Change_prerequisite",
+ "check_prereq": [str(learning_unit.id)]
+ })
+ updated_learning_unit = LearningUnit.objects.get(id=learning_unit.id)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/add_module.html')
+ self.assertFalse(updated_learning_unit.check_prerequisite)
+
+ # Test to remove learning unit from learning module
+ response = self.client.post(
+ reverse('yaksh:design_module',
+ kwargs={"module_id": self.learning_module1.id,
+ "course_id": self.course.id}),
+ data={"Remove": "Remove",
+ "delete_list": [str(learning_unit.id)]
+ })
+ updated_learning_unit = LearningUnit.objects.filter(
+ id=learning_unit.id).exists()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/add_module.html')
+ self.assertFalse(updated_learning_unit)
+ self.assertFalse(self.learning_module1.learning_unit.all().exists())
+
+ def test_get_learning_units_for_design(self):
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:design_module',
+ kwargs={"module_id": self.learning_module1.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/add_module.html')
+ self.assertEqual(response.context['quiz_les_list'], set())
+ self.assertEqual(len(response.context['learning_units']), 0)
+ self.assertEqual(response.context['status'], "design")
+ self.assertEqual(response.context['module_id'],
+ str(self.learning_module1.id))
+ self.assertEqual(response.context['course_id'], str(self.course.id))
+
+ def test_view_module(self):
+ """ Student tries to view a module containing learning units """
+
+ # Student is not enrolled in the course is thrown out
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:view_module',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 404)
+
+ # add student to the course
+ self.course.students.add(self.student.id)
+
+ # check if enrolled student can view module
+ response = self.client.get(
+ reverse('yaksh:view_module',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/show_video.html")
+ self.assertEqual(response.context['learning_units'][0],
+ self.learning_unit)
+ self.assertEqual(response.context["state"], "module")
+ self.assertEqual(response.context["first_unit"], self.learning_unit)
+ self.assertEqual(response.context["user"], self.student)
+
+ def test_get_next_unit(self):
+ """ Check if we get correct next unit """
+
+ # Student who is not enrolled is thrown out
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:view_module',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 404)
+
+ # Enroll student in the course
+ self.course.students.add(self.student.id)
+
+ # Get First unit as next unit
+ response = self.client.get(
+ reverse('yaksh:next_unit',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id,
+ "current_unit_id": self.learning_unit.id,
+ "first_unit": "1"}),
+ follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['attempt_num'], 1)
+ self.assertEqual(response.context["questionpaper"],
+ self.question_paper)
+ self.assertEqual(response.context["course"], self.course)
+ self.assertEqual(response.context["module"], self.learning_module)
+ self.assertEqual(response.context["user"], self.student)
+
+ # Get next unit after first unit
+ response = self.client.get(
+ reverse('yaksh:next_unit',
+ kwargs={"module_id": self.learning_module.id,
+ "course_id": self.course.id,
+ "current_unit_id": self.learning_unit.id}),
+ follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context["state"], "lesson")
+ self.assertEqual(response.context["current_unit"].id,
+ self.learning_unit1.id)
+
+
+class TestLessons(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Create a student
+ self.student_plaintext_pass = 'demo_student'
+ self.student = User.objects.create_user(
+ username='demo_student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@student.com'
+ )
+
+ # Create a teacher to add to the course
+ self.teacher_plaintext_pass = 'demo_teacher'
+ self.teacher = User.objects.create_user(
+ username='demo_teacher',
+ password=self.teacher_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@student.com'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+ self.mod_group.user_set.add(self.teacher)
+
+ self.course = Course.objects.create(
+ name="Python Course",
+ enrollment="Open Enrollment", creator=self.user)
+
+ self.quiz = Quiz.objects.create(
+ start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ creator=self.user
+ )
+
+ self.lesson = Lesson.objects.create(
+ name="test lesson", description="test description",
+ creator=self.user)
+ self.learning_unit = LearningUnit.objects.create(
+ order=0, type="lesson", lesson=self.lesson)
+ self.learning_module = LearningModule.objects.create(
+ order=0, name="test module", description="module",
+ check_prerequisite=False, creator=self.user)
+ self.learning_module.learning_unit.add(self.learning_unit.id)
+ self.course.learning_module.add(self.learning_module.id)
+ self.course.teachers.add(self.teacher.id)
+
+ self.expected_url = "/exam/manage/courses/"
+
+ def tearDown(self):
+ self.user.delete()
+ self.student.delete()
+ self.teacher.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.learning_unit.delete()
+ self.learning_module.delete()
+ self.lesson.delete()
+
+ def test_edit_lesson_denies_non_moderator(self):
+ """ Student should not be allowed to edit lesson """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+
+ # Student tries to edit lesson
+ response = self.client.get(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 404)
+
+ def test_teacher_can_edit_lesson(self):
+ """ Teacher should be allowed to edit lesson """
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ dummy_file = SimpleUploadedFile("test.txt", b"test")
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson.id,
+ "course_id": self.course.id}),
+ data={"name": "updated lesson",
+ "description": "updated description",
+ "Lesson_files": dummy_file,
+ "Save": "Save"}
+ )
+
+ # Teacher edits existing lesson and adds file
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, self.expected_url)
+ updated_lesson = Lesson.objects.get(name="updated lesson")
+ self.assertEqual(updated_lesson.description, "updated description")
+ self.assertEqual(updated_lesson.creator, self.user)
+ self.assertEqual(updated_lesson.html_data,
+ Markdown().convert("updated description"))
+ lesson_files = LessonFile.objects.filter(
+ lesson=self.lesson).first()
+ self.assertIn("test.txt", lesson_files.file.name)
+ lesson_file_path = lesson_files.file.path
+ # Teacher removes the lesson file
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson.id,
+ "course_id": self.course.id}),
+ data={"delete_files": [str(lesson_files.id)],
+ "Delete": "Delete"}
+ )
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, self.expected_url)
+ lesson_file_exists = LessonFile.objects.filter(
+ lesson=self.lesson).exists()
+ self.assertFalse(lesson_file_exists)
+ self.assertFalse(os.path.exists(lesson_file_path))
+
+ def test_show_lesson(self):
+ """ Student should be able to view lessons """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:show_lesson',
+ kwargs={"lesson_id": self.lesson.id,
+ "module_id": self.learning_module.id,
+ "course_id": self.course.id}))
+ # Student is unable to view lesson
+ self.assertEqual(response.status_code, 404)
+
+ # Add student to course
+ self.course.students.add(self.student.id)
+ response = self.client.get(
+ reverse('yaksh:show_lesson',
+ kwargs={"lesson_id": self.lesson.id,
+ "module_id": self.learning_module.id,
+ "course_id": self.course.id}))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context["state"], "lesson")
+ self.assertEqual(response.context["current_unit"], self.learning_unit)
+
+ def test_show_all_lessons(self):
+ """ Moderator should be able to see all created lessons"""
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_all_lessons'))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/courses.html")
+ self.assertEqual(response.context["type"], "lesson")
+ self.assertEqual(response.context["lessons"][0], self.lesson)
+
+ def test_preview_lesson_description(self):
+ """ Test preview lesson description converted from md to html"""
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+ lesson = json.dumps({'description': self.lesson.description})
+ response = self.client.post(
+ reverse('yaksh:preview_html_text'),
+ data=lesson, content_type="application/json"
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()['data'], '<p>test description</p>')
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 7f484b9..716a7d0 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -11,50 +11,69 @@ urlpatterns = [
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),
- url(r'^start/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$', views.start),
- url(r'^quit/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$', views.quit),
+ url(r'^start/(?P<questionpaper_id>\d+)/(?P<module_id>\d+)/(?P<course_id>\d+)/$',
+ views.start),
+ url(r'^start/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
+ views.start),
+ url(r'^quit/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
+ views.quit),
url(r'^complete/$', views.complete),
- url(r'^complete/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
+ url(r'^complete/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',\
views.complete),
url(r'^register/$', views.user_register, name="register"),
url(r'^(?P<q_id>\d+)/check/$', views.check, name="check"),
- url(r'^get_result/(?P<uid>\d+)/$', views.get_result),
- url(r'^(?P<q_id>\d+)/check/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
+ url(r'^get_result/(?P<uid>\d+)/(?P<course_id>\d+)/(?P<module_id>\d+)/$',
+ views.get_result),
+ url(r'^(?P<q_id>\d+)/check/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',\
views.check, name="check"),
- url(r'^(?P<q_id>\d+)/skip/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',
+ url(r'^(?P<q_id>\d+)/skip/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
views.skip),
- url(r'^(?P<q_id>\d+)/skip/(?P<next_q>\d+)/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',
+ url(r'^(?P<q_id>\d+)/skip/(?P<next_q>\d+)/(?P<attempt_num>\d+)/(?P<module_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
views.skip),
url(r'^enroll_request/(?P<course_id>\d+)/$', views.enroll_request, name='enroll_request'),
url(r'^self_enroll/(?P<course_id>\d+)/$', views.self_enroll, name='self_enroll'),
- url(r'^view_answerpaper/(?P<questionpaper_id>\d+)/$', views.view_answerpaper, name='view_answerpaper'),
+ url(r'^view_answerpaper/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)$',
+ views.view_answerpaper, name='view_answerpaper'),
+ url(r'^show_lesson/(?P<lesson_id>\d+)/(?P<module_id>\d+)/(?P<course_id>\d+)/$',
+ views.show_lesson, name='show_lesson'),
+ url(r'^quizzes/view_module/(?P<module_id>\d+)/(?P<course_id>\d+)/$',
+ views.view_module, name='view_module'),
+ url(r'^next_unit/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<current_unit_id>\d+)/$',
+ views.get_next_unit, name='next_unit'),
+ url(r'^next_unit/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<current_unit_id>\d+)/(?P<first_unit>\d+)/$',
+ views.get_next_unit, name='next_unit'),
+ url(r'^course_modules/(?P<course_id>\d+)/$',
+ views.course_modules, name='course_modules'),
url(r'^manage/$', views.prof_manage, name='manage'),
url(r'^manage/addquestion/$', views.add_question, name="add_question"),
url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.add_question,
name="add_question"),
- url(r'^manage/addquiz/(?P<course_id>\d+)/$', views.add_quiz, name='add_quiz'),
- url(r'^manage/addquiz/(?P<course_id>\d+)/(?P<quiz_id>\d+)/$', views.add_quiz, name='edit_quiz'),
+ url(r'^manage/addquiz/$', views.add_quiz, name='add_quiz'),
+ url(r'^manage/addquiz/(?P<quiz_id>\d+)/$',
+ views.add_quiz, name='edit_quiz'),
+ url(r'^manage/addquiz/(?P<quiz_id>\d+)/(?P<course_id>\d+)$',
+ views.add_quiz, name='edit_quiz'),
url(r'^manage/gradeuser/$', views.grade_user, name="grade_user"),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/$',views.grade_user, name="grade_user"),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/$',
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$',
views.grade_user, name="grade_user"),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/(?P<attempt_number>\d+)/$',
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/(?P<course_id>\d+)/$',
+ views.grade_user, name="grade_user"),
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/(?P<attempt_number>\d+)/(?P<course_id>\d+)/$',
views.grade_user, name="grade_user"),
url(r'^manage/questions/$', views.show_all_questions, name="show_questions"),
url(r'^manage/monitor/$', views.monitor, name="monitor"),
- url(r'^manage/monitor/(?P<quiz_id>\d+)/$', views.monitor, name="monitor"),
- url(r'^manage/user_data/(?P<user_id>\d+)/(?P<questionpaper_id>\d+)/$',
+ url(r'^manage/monitor/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$',
+ views.monitor, name="monitor"),
+ url(r'^manage/user_data/(?P<user_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
views.user_data, name="user_data"),
url(r'^manage/user_data/(?P<user_id>\d+)/$', views.user_data),
url(r'^manage/quiz/designquestionpaper/(?P<quiz_id>\d+)/$', views.design_questionpaper,
name='design_questionpaper'),
url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/$',
views.design_questionpaper, name='designquestionpaper'),
- url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/$',
+ url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
views.show_statistics, name="show_statistics"),
- url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/$',
+ url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/(?P<course_id>\d+)/$',
views.show_statistics, name="show_statistics"),
url(r'^manage/download_quiz_csv/(?P<course_id>\d+)/(?P<quiz_id>\d+)/$',
views.download_quiz_csv, name="download_quiz_csv"),
@@ -101,7 +120,8 @@ urlpatterns = [
views.regrade, name='regrade'),
url(r'^manage/regrade/paper/(?P<course_id>\d+)/(?P<answerpaper_id>\d+)/$',
views.regrade, name='regrade'),
- url(r'^manage/(?P<mode>godmode|usermode)/(?P<quiz_id>\d+)/$', views.test_quiz),
+ url(r'^manage/(?P<mode>godmode|usermode)/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$',
+ views.test_quiz),
url(r'^manage/create_demo_course/$', views.create_demo_course),
url(r'^manage/courses/download_course_csv/(?P<course_id>\d+)/$',
views.download_course_csv, name="download_course_csv"),
@@ -113,4 +133,32 @@ urlpatterns = [
views.download_yaml_template, name="download_yaml_template"),
url(r'^manage/download_sample_csv/',
views.download_sample_csv, name="download_sample_csv"),
+ url(r'^manage/courses/edit_lesson/$',
+ views.edit_lesson, name="edit_lesson"),
+ url(r'^manage/courses/edit_lesson/(?P<lesson_id>\d+)/$',
+ views.edit_lesson, name="edit_lesson"),
+ url(r'^manage/courses/edit_lesson/(?P<lesson_id>\d+)/(?P<course_id>\d+)/$',
+ views.edit_lesson, name="edit_lesson"),
+ url(r'^manage/courses/designmodule/(?P<module_id>\d+)/$',
+ views.design_module, name="design_module"),
+ url(r'^manage/courses/designmodule/(?P<module_id>\d+)/(?P<course_id>\d+)/$',
+ views.design_module, name="design_module"),
+ url(r'^manage/courses/all_quizzes/$',
+ views.show_all_quizzes, name="show_all_quizzes"),
+ url(r'^manage/courses/all_lessons/$',
+ views.show_all_lessons, name="show_all_lessons"),
+ url(r'^manage/courses/lesson/preview/$',
+ views.preview_html_text, name="preview_html_text"),
+ url(r'^manage/courses/all_learning_module/$',
+ views.show_all_modules, name="show_all_modules"),
+ url(r'^manage/courses/add_module/$',
+ views.add_module, name="add_module"),
+ url(r'^manage/courses/add_module/(?P<module_id>\d+)/$',
+ views.add_module, name="edit_module"),
+ url(r'^manage/courses/add_module/(?P<module_id>\d+)/(?P<course_id>\d+)/$',
+ views.add_module, name="edit_module"),
+ url(r'^manage/courses/designcourse/(?P<course_id>\d+)/$',
+ views.design_course, name="design_course"),
+ url(r'^manage/courses/designcourse/(?P<course_id>\d+)/$',
+ views.design_course, name="design_course"),
]
diff --git a/yaksh/views.py b/yaksh/views.py
index 333b77f..295b983 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -16,7 +16,10 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Group
from django.forms.models import inlineformset_factory
from django.utils import timezone
-from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
+from django.core.validators import URLValidator
+from django.core.exceptions import (
+ MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
+)
from django.conf import settings
import pytz
from taggit.models import Tag
@@ -25,6 +28,7 @@ import json
import six
from textwrap import dedent
import zipfile
+from markdown import Markdown
try:
from StringIO import StringIO as string_io
except ImportError:
@@ -37,12 +41,15 @@ from yaksh.models import (
HookTestCase, IntegerTestCase, McqTestCase, Profile,
QuestionPaper, QuestionSet, Quiz, Question, StandardTestCase,
StdIOBasedTestCase, StringTestCase, TestCase, User,
- get_model_class, FIXTURES_DIR_PATH
+ get_model_class, FIXTURES_DIR_PATH, Lesson, LessonFile,
+ LearningUnit, LearningModule,
+ CourseStatus
)
from yaksh.forms import (
UserRegisterForm, UserLoginForm, QuizForm, QuestionForm,
RandomQuestionForm, QuestionFilterForm, CourseForm, ProfileForm,
- UploadFileForm, get_object_form, FileForm, QuestionPaperForm
+ UploadFileForm, get_object_form, FileForm, QuestionPaperForm, LessonForm,
+ LessonFileForm, LearningModuleForm
)
from .settings import URL_ROOT
from .file_utils import extract_files, is_csv
@@ -79,11 +86,15 @@ def add_to_group(users):
if not is_moderator(user):
user.groups.add(group)
-
CSV_FIELDS = ['name', 'username', 'roll_number', 'institute', 'department',
'questions', 'marks_obtained', 'out_of', 'percentage', 'status']
+def get_html_text(md_text):
+ """Takes markdown text and converts it to html"""
+ return Markdown().convert(md_text)
+
+
@email_verified
def index(request, next_url=None):
"""The start page.
@@ -140,7 +151,7 @@ def user_logout(request):
@login_required
@has_profile
@email_verified
-def quizlist_user(request, enrolled=None):
+def quizlist_user(request, enrolled=None, msg=None):
"""Show All Quizzes that is available to logged-in user."""
user = request.user
ci = RequestContext(request)
@@ -162,7 +173,8 @@ def quizlist_user(request, enrolled=None):
)
title = 'All Courses'
- context = {'user': user, 'courses': courses, 'title': title}
+ context = {'user': user, 'courses': courses, 'title': title,
+ 'msg': msg}
return my_render_to_response(
"yaksh/quizzes_user.html", context, context_instance=ci
@@ -276,35 +288,41 @@ def add_question(request, question_id=None):
@login_required
@email_verified
-def add_quiz(request, course_id, quiz_id=None):
+def add_quiz(request, quiz_id=None, course_id=None):
"""To add a new quiz in the database.
Create a new quiz and store it."""
user = request.user
- course = get_object_or_404(Course, pk=course_id)
ci = RequestContext(request)
- if not is_moderator(user) or (user != course.creator and user not in course.teachers.all()):
+ if not is_moderator(user):
raise Http404('You are not allowed to view this course !')
+ if quiz_id:
+ quiz = get_object_or_404(Quiz, pk=quiz_id)
+ if quiz.creator != user and not course_id:
+ raise Http404('This quiz does not belong to you')
+ else:
+ quiz = None
+ if course_id:
+ course = get_object_or_404(Course, pk=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This quiz does not belong to you')
+
context = {}
if request.method == "POST":
- if quiz_id is None:
- form = QuizForm(request.POST, user=user, course=course_id)
- if form.is_valid():
- form.save()
- return my_redirect("/exam/manage/courses/")
-
- else:
- quiz = Quiz.objects.get(id=quiz_id)
- form = QuizForm(request.POST, user=user, course=course_id,
- instance=quiz
- )
- if form.is_valid():
- form.save()
+ form = QuizForm(request.POST, instance=quiz)
+ if form.is_valid():
+ if quiz is None:
+ form.instance.creator = user
+ form.save()
+ if not course_id:
+ return my_redirect("/exam/manage/courses/all_quizzes/")
+ else:
return my_redirect("/exam/manage/courses/")
else:
quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None
- form = QuizForm(user=user,course=course_id, instance=quiz)
+ form = QuizForm(instance=quiz)
context["quiz_id"] = quiz_id
+ context["course_id"] = course_id
context["form"] = form
return my_render_to_response(
'yaksh/add_quiz.html', context, context_instance=ci
@@ -319,46 +337,36 @@ def prof_manage(request, msg=None):
rights/permissions and log in."""
user = request.user
ci = RequestContext(request)
- if user.is_authenticated() and is_moderator(user):
- question_papers = QuestionPaper.objects.filter(
- Q(quiz__course__creator=user) |
- Q(quiz__course__teachers=user),
- quiz__is_trial=False
- ).distinct()
- trial_paper = AnswerPaper.objects.filter(
- user=user, question_paper__quiz__is_trial=True
- )
- if request.method == "POST":
- delete_paper = request.POST.getlist('delete_paper')
- for answerpaper_id in delete_paper:
- answerpaper = AnswerPaper.objects.get(id=answerpaper_id)
- qpaper = answerpaper.question_paper
- if qpaper.quiz.course.is_trial:
- qpaper.quiz.course.delete()
+ if not user.is_authenticated():
+ return my_redirect('/exam/login')
+ if not is_moderator(user):
+ return my_redirect('/exam/')
+ courses = Course.objects.filter(creator=user, is_trial=False)
+
+ trial_paper = AnswerPaper.objects.filter(
+ user=user, question_paper__quiz__is_trial=True,
+ course__is_trial=True
+ )
+ if request.method == "POST":
+ delete_paper = request.POST.getlist('delete_paper')
+ for answerpaper_id in delete_paper:
+ answerpaper = AnswerPaper.objects.get(id=answerpaper_id)
+ qpaper = answerpaper.question_paper
+ answerpaper.course.remove_trial_modules()
+ answerpaper.course.delete()
+ if qpaper.quiz.is_trial:
+ qpaper.quiz.delete()
+ else:
+ if qpaper.answerpaper_set.count() == 1:
+ qpaper.quiz.delete()
else:
- if qpaper.answerpaper_set.count() == 1:
- qpaper.quiz.delete()
- else:
- answerpaper.delete()
- users_per_paper = []
- for paper in question_papers:
- answer_papers = AnswerPaper.objects.filter(question_paper=paper)
- users_passed = AnswerPaper.objects.filter(
- question_paper=paper, passed=True
- ).count()
- users_failed = AnswerPaper.objects.filter(
- question_paper=paper,
- passed=False
- ).count()
- 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, 'msg': msg
- }
- return my_render_to_response(
- 'yaksh/moderator_dashboard.html', context, context_instance=ci
- )
- return my_redirect('/exam/login/')
+ answerpaper.delete()
+ context = {'user': user, 'courses': courses,
+ 'trial_paper': trial_paper, 'msg': msg
+ }
+ return my_render_to_response(
+ 'yaksh/moderator_dashboard.html', context, context_instance=ci
+ )
def user_login(request):
@@ -391,7 +399,8 @@ def user_login(request):
@login_required
@email_verified
-def start(request, questionpaper_id=None, attempt_num=None):
+def start(request, questionpaper_id=None, attempt_num=None, course_id=None,
+ module_id=None):
"""Check the user cedentials and if any quiz is available,
start the exam."""
user = request.user
@@ -402,34 +411,70 @@ def start(request, questionpaper_id=None, attempt_num=None):
except QuestionPaper.DoesNotExist:
msg = 'Quiz not found, please contact your '\
'instructor/administrator.'
- return complete(request, msg, attempt_num, questionpaper_id=None)
+ if is_moderator(user):
+ return prof_manage(request, msg=msg)
+ return view_module(request, module_id=module_id, course_id=course_id,
+ msg=msg)
if not quest_paper.has_questions():
msg = 'Quiz does not have Questions, please contact your '\
'instructor/administrator.'
- return complete(request, msg, attempt_num, questionpaper_id=None)
- if not quest_paper.quiz.course.is_enrolled(user):
- raise Http404('You are not allowed to view this page!')
- # prerequisite check and passing criteria
- if quest_paper.quiz.is_expired() or not quest_paper.quiz.course.active:
if is_moderator(user):
- return redirect("/exam/manage")
- return redirect("/exam/quizzes")
- if quest_paper.quiz.has_prerequisite() and not quest_paper.is_prerequisite_passed(user):
+ return prof_manage(request, msg=msg)
+ return view_module(request, module_id=module_id, course_id=course_id,
+ msg=msg)
+ course = Course.objects.get(id=course_id)
+ learning_module = course.learning_module.get(id=module_id)
+ learning_unit = learning_module.learning_unit.get(quiz=quest_paper.quiz.id)
+
+ # is user enrolled in the course
+ if not course.is_enrolled(user):
+ msg = 'You are not enrolled in {0} course'.format(course.name)
if is_moderator(user):
- return redirect("/exam/manage")
- return redirect("/exam/quizzes")
+ return prof_manage(request, msg=msg)
+ return quizlist_user(request, msg=msg)
+
+ # if course is active and is not expired
+ if not course.active or not course.is_active_enrollment():
+ msg = "{0} is either expired or not active".format(course.name)
+ if is_moderator(user):
+ return prof_manage(request, msg=msg)
+ return quizlist_user(request, msg=msg)
+
+ # is quiz is active and is not expired
+ if quest_paper.quiz.is_expired() or not quest_paper.quiz.active:
+ msg = "{0} is either expired or not active".format(
+ quest_paper.quiz.description)
+ if is_moderator(user):
+ return prof_manage(request, msg=msg)
+ return view_module(request, module_id=module_id, course_id=course_id,
+ msg=msg)
+
+ # prerequisite check and passing criteria for quiz
+ if learning_unit.has_prerequisite():
+ if not learning_unit.is_prerequisite_passed(
+ user, learning_module, course):
+ msg = "You have not completed the prerequisite"
+ if is_moderator(user):
+ return prof_manage(request, msg=msg)
+ return view_module(request, module_id=module_id,
+ course_id=course_id, msg=msg)
+
# if any previous attempt
last_attempt = AnswerPaper.objects.get_user_last_attempt(
- questionpaper=quest_paper, user=user)
+ quest_paper, user, course_id)
if last_attempt and last_attempt.is_attempt_inprogress():
return show_question(
- request, last_attempt.current_question(), last_attempt
+ request, last_attempt.current_question(), last_attempt,
+ course_id=course_id, module_id=module_id
)
# allowed to start
- if not quest_paper.can_attempt_now(user):
+ if not quest_paper.can_attempt_now(user, course_id):
+ msg = "You cannot attempt {0} quiz more than {1} time(s)".format(
+ quest_paper.quiz.description, quest_paper.quiz.attempts_allowed)
if is_moderator(user):
- return redirect("/exam/manage")
- return redirect("/exam/quizzes")
+ return prof_manage(request, msg=msg)
+ return view_module(request, module_id=module_id, course_id=course_id,
+ msg=msg)
if not last_attempt:
attempt_number = 1
else:
@@ -438,10 +483,12 @@ def start(request, questionpaper_id=None, attempt_num=None):
context = {
'user': user,
'questionpaper': quest_paper,
- 'attempt_num': attempt_number
+ 'attempt_num': attempt_number,
+ 'course': course,
+ 'module': learning_module,
}
if is_moderator(user):
- context["user"] = "moderator"
+ context["status"] = "moderator"
return my_render_to_response('yaksh/intro.html', context,
context_instance=ci)
else:
@@ -449,10 +496,12 @@ def start(request, questionpaper_id=None, attempt_num=None):
if not hasattr(user, 'profile'):
msg = 'You do not have a profile and cannot take the quiz!'
raise Http404(msg)
- new_paper = quest_paper.make_answerpaper(user, ip, attempt_number)
+ new_paper = quest_paper.make_answerpaper(user, ip, attempt_number,
+ course_id)
if new_paper.status == 'inprogress':
return show_question(request, new_paper.current_question(),
- new_paper
+ new_paper, course_id=course_id,
+ module_id=module_id
)
else:
msg = 'You have already finished the quiz!'
@@ -460,23 +509,27 @@ def start(request, questionpaper_id=None, attempt_num=None):
@login_required
@email_verified
-def show_question(request, question, paper, error_message=None, notification=None):
+def show_question(request, question, paper, error_message=None, notification=None,
+ course_id=None, module_id=None):
"""Show a question if possible."""
user = request.user
if not question:
msg = 'Congratulations! You have successfully completed the quiz.'
return complete(
- request, msg, paper.attempt_number, paper.question_paper.id
+ request, msg, paper.attempt_number, paper.question_paper.id,
+ course_id=course_id, module_id=module_id
)
if not paper.question_paper.quiz.active:
reason = 'The quiz has been deactivated!'
return complete(
- request, reason, paper.attempt_number, paper.question_paper.id
+ request, reason, paper.attempt_number, paper.question_paper.id,
+ module_id=module_id
)
if paper.time_left() <= 0:
reason = 'Your time is up!'
return complete(
- request, reason, paper.attempt_number, paper.question_paper.id
+ request, reason, paper.attempt_number, paper.question_paper.id,
+ course_id, module_id=module_id
)
if question in paper.questions_answered.all():
notification = (
@@ -486,6 +539,8 @@ def show_question(request, question, paper, error_message=None, notification=Non
)
test_cases = question.get_test_cases()
files = FileUpload.objects.filter(question_id=question.id, hide=False)
+ course = Course.objects.get(id=course_id)
+ module = course.learning_module.get(id=module_id)
context = {
'question': question,
'paper': paper,
@@ -493,7 +548,9 @@ def show_question(request, question, paper, error_message=None, notification=Non
'test_cases': test_cases,
'files': files,
'notification': notification,
- 'last_attempt': question.snippet.encode('unicode-escape')
+ 'last_attempt': question.snippet.encode('unicode-escape'),
+ 'course': course,
+ 'module': module
}
answers = paper.get_previous_answers(question)
if answers:
@@ -507,11 +564,12 @@ def show_question(request, question, paper, error_message=None, notification=Non
@login_required
@email_verified
-def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None):
+def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None,
+ course_id=None, module_id=None):
user = request.user
paper = get_object_or_404(
AnswerPaper, user=request.user, attempt_number=attempt_num,
- question_paper=questionpaper_id
+ question_paper=questionpaper_id, course_id=course_id
)
question = get_object_or_404(Question, pk=q_id)
@@ -529,19 +587,22 @@ def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None):
next_q = get_object_or_404(Question, pk=next_q)
else:
next_q = paper.next_question(q_id)
- return show_question(request, next_q, paper)
+ return show_question(request, next_q, paper, course_id=course_id,
+ module_id=module_id)
@login_required
@email_verified
-def check(request, q_id, attempt_num=None, questionpaper_id=None):
+def check(request, q_id, attempt_num=None, questionpaper_id=None,
+ course_id=None, module_id=None):
"""Checks the answers of the user for particular question"""
user = request.user
paper = get_object_or_404(
AnswerPaper,
user=request.user,
attempt_number=attempt_num,
- question_paper=questionpaper_id
+ question_paper=questionpaper_id,
+ course_id=course_id
)
current_question = get_object_or_404(Question, pk=q_id)
@@ -555,7 +616,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
except ValueError:
msg = "Please enter an Integer Value"
return show_question(
- request, current_question, paper, notification=msg
+ request, current_question, paper, notification=msg,
+ course_id=course_id, module_id=module_id
)
elif current_question.type == 'float':
try:
@@ -563,7 +625,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
except ValueError:
msg = "Please enter a Float Value"
return show_question(request, current_question,
- paper, notification=msg)
+ paper, notification=msg,
+ course_id=course_id, module_id=module_id)
elif current_question.type == 'string':
user_answer = str(request.POST.get('answer'))
@@ -576,7 +639,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
if not assignment_filename:
msg = "Please upload assignment file"
return show_question(
- request, current_question, paper, notification=msg
+ request, current_question, paper, notification=msg,
+ course_id=course_id, module_id=module_id
)
for fname in assignment_filename:
fname._name = fname._name.replace(" ","_")
@@ -605,13 +669,15 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
new_answer.save()
paper.answers.add(new_answer)
next_q = paper.add_completed_question(current_question.id)
- return show_question(request, next_q, paper)
+ return show_question(request, next_q, paper,
+ course_id=course_id, module_id=module_id)
else:
user_answer = request.POST.get('answer')
if not user_answer:
msg = "Please submit a valid answer."
return show_question(
- request, current_question, paper, notification=msg
+ request, current_question, paper, notification=msg,
+ course_id=course_id, module_id=module_id
)
if current_question in paper.get_questions_answered()\
and current_question.type not in ['code', 'upload']:
@@ -642,18 +708,22 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
result = json.loads(result_details.get('result'))
next_question, error_message, paper = _update_paper(request, uid,
result)
- return show_question(request, next_question, paper, error_message)
+ return show_question(request, next_question, paper,
+ error_message, course_id=course_id,
+ module_id=module_id)
else:
return JsonResponse(result)
else:
next_question, error_message, paper = _update_paper(request, uid, result)
- return show_question(request, next_question, paper, error_message)
+ return show_question(request, next_question, paper, error_message,
+ course_id=course_id, module_id=module_id)
else:
- return show_question(request, current_question, paper)
+ return show_question(request, current_question, paper,
+ course_id=course_id, module_id=module_id)
@csrf_exempt
-def get_result(request, uid):
+def get_result(request, uid, course_id, module_id):
result = {}
url = 'http://localhost:%s' % SERVER_POOL_PORT
result_state = get_result_from_code_server(url, uid)
@@ -661,15 +731,16 @@ def get_result(request, uid):
if result['status'] == 'done':
result = json.loads(result_state.get('result'))
template_path = os.path.join(*[os.path.dirname(__file__),
- 'templates','yaksh',
+ 'templates', 'yaksh',
'error_template.html'
]
)
- next_question, error_message, paper = _update_paper(request,uid,
+ next_question, error_message, paper = _update_paper(request, uid,
result
)
if result.get('success'):
- return show_question(request, next_question, paper, error_message)
+ return show_question(request, next_question, paper, error_message,
+ course_id=course_id, module_id=module_id)
else:
with open(template_path) as f:
template_data = f.read()
@@ -677,7 +748,6 @@ def get_result(request, uid):
context = Context({"error_message": result.get('error')})
render_error = template.render(context)
result["error"] = render_error
-
return JsonResponse(result)
@@ -716,19 +786,23 @@ def _update_paper(request, uid, result):
@login_required
@email_verified
-def quit(request, reason=None, attempt_num=None, questionpaper_id=None):
+def quit(request, reason=None, attempt_num=None, questionpaper_id=None,
+ course_id=None, module_id=None):
"""Show the quit page when the user logs out."""
paper = AnswerPaper.objects.get(user=request.user,
attempt_number=attempt_num,
- question_paper=questionpaper_id)
- context = {'paper': paper, 'message': reason}
+ question_paper=questionpaper_id,
+ course_id=course_id)
+ context = {'paper': paper, 'message': reason, 'course_id': course_id,
+ 'module_id': module_id}
return my_render_to_response('yaksh/quit.html', context,
context_instance=RequestContext(request))
@login_required
@email_verified
-def complete(request, reason=None, attempt_num=None, questionpaper_id=None):
+def complete(request, reason=None, attempt_num=None, questionpaper_id=None,
+ course_id=None, module_id=None):
"""Show a page to inform user that the quiz has been compeleted."""
user = request.user
if questionpaper_id is None:
@@ -740,12 +814,21 @@ def complete(request, reason=None, attempt_num=None, questionpaper_id=None):
q_paper = QuestionPaper.objects.get(id=questionpaper_id)
paper = AnswerPaper.objects.get(
user=user, question_paper=q_paper,
- attempt_number=attempt_num
+ attempt_number=attempt_num,
+ course_id=course_id
)
+ course = Course.objects.get(id=course_id)
+ learning_module = course.learning_module.get(id=module_id)
+ learning_unit = learning_module.learning_unit.get(quiz=q_paper.quiz)
+
paper.update_marks()
paper.set_end_time(timezone.now())
message = reason or "Quiz has been submitted"
- context = {'message': message, 'paper': paper}
+ context = {'message': message, 'paper': paper,
+ 'module_id': learning_module.id,
+ 'course_id': course_id, 'learning_unit': learning_unit}
+ if is_moderator(user):
+ context['user'] = "moderator"
return my_render_to_response('yaksh/complete.html', context)
@@ -756,6 +839,8 @@ def add_course(request, course_id=None):
ci = RequestContext(request)
if course_id:
course = Course.objects.get(id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404("You are not allowed to view this course")
else:
course = None
@@ -767,7 +852,7 @@ def add_course(request, course_id=None):
new_course = form.save(commit=False)
new_course.creator = user
new_course.save()
- return my_redirect('/exam/manage/')
+ return my_redirect('/exam/manage/courses')
else:
return my_render_to_response(
'yaksh/add_course.html', {'form': form}, context_instance=ci
@@ -825,7 +910,8 @@ def courses(request):
creator=user, is_trial=False).order_by('-active', '-id')
allotted_courses = Course.objects.filter(
teachers=user, is_trial=False).order_by('-active', '-id')
- context = {'courses': courses, "allotted_courses": allotted_courses}
+ context = {'courses': courses, "allotted_courses": allotted_courses,
+ "type": "courses"}
return my_render_to_response('yaksh/courses.html', context,
context_instance=ci)
@@ -957,39 +1043,45 @@ def toggle_course_status(request, course_id):
else:
course.activate()
course.save()
- return course_detail(request, course_id)
+ return my_redirect("/exam/manage/courses")
@login_required
@email_verified
-def show_statistics(request, questionpaper_id, attempt_number=None):
+def show_statistics(request, questionpaper_id, attempt_number=None,
+ course_id=None):
user = request.user
if not is_moderator(user):
raise Http404('You are not allowed to view this page')
- attempt_numbers = AnswerPaper.objects.get_attempt_numbers(questionpaper_id)
+ attempt_numbers = AnswerPaper.objects.get_attempt_numbers(
+ questionpaper_id, course_id)
quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz
if attempt_number is None:
context = {'quiz': quiz, 'attempts': attempt_numbers,
- 'questionpaper_id': questionpaper_id}
+ 'questionpaper_id': questionpaper_id,
+ 'course_id': course_id}
return my_render_to_response('yaksh/statistics_question.html', context,
context_instance=RequestContext(request))
total_attempt = AnswerPaper.objects.get_count(questionpaper_id,
- attempt_number)
- if not AnswerPaper.objects.has_attempt(questionpaper_id, attempt_number):
+ attempt_number,
+ course_id)
+ if not AnswerPaper.objects.has_attempt(questionpaper_id, attempt_number,
+ course_id):
return my_redirect('/exam/manage/')
question_stats = AnswerPaper.objects.get_question_statistics(
questionpaper_id, attempt_number
)
context = {'question_stats': question_stats, 'quiz': quiz,
'questionpaper_id': questionpaper_id,
- 'attempts': attempt_numbers, 'total': total_attempt}
+ 'attempts': attempt_numbers, 'total': total_attempt,
+ 'course_id': course_id}
return my_render_to_response('yaksh/statistics_question.html', context,
context_instance=RequestContext(request))
@login_required
@email_verified
-def monitor(request, quiz_id=None):
+def monitor(request, quiz_id=None, course_id=None):
"""Monitor the progress of the papers taken so far."""
user = request.user
@@ -1012,24 +1104,25 @@ def monitor(request, quiz_id=None):
# quiz_id is not None.
try:
quiz = get_object_or_404(Quiz, id=quiz_id)
- if not quiz.course.is_creator(user) and not quiz.course.is_teacher(user):
+ course = get_object_or_404(Course, id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
raise Http404('This course does not belong to you')
- q_paper = QuestionPaper.objects.filter(Q(quiz__course__creator=user) |
- Q(quiz__course__teachers=user),
- quiz__is_trial=False,
+ q_paper = QuestionPaper.objects.filter(quiz__is_trial=False,
quiz_id=quiz_id).distinct()
- except QuestionPaper.DoesNotExist:
+ except (QuestionPaper.DoesNotExist, Course.DoesNotExist):
papers = []
q_paper = None
latest_attempts = []
attempt_numbers = []
else:
if q_paper:
- attempt_numbers = AnswerPaper.objects.get_attempt_numbers(q_paper.last().id)
+ attempt_numbers = AnswerPaper.objects.get_attempt_numbers(
+ q_paper.last().id, course.id)
else:
attempt_numbers = []
latest_attempts = []
- papers = AnswerPaper.objects.filter(question_paper=q_paper).order_by(
+ papers = AnswerPaper.objects.filter(question_paper=q_paper,
+ course_id=course_id).order_by(
'user__profile__roll_number'
)
users = papers.values_list('user').distinct()
@@ -1050,7 +1143,8 @@ def monitor(request, quiz_id=None):
"msg": "Quiz Results",
"latest_attempts": latest_attempts,
"csv_fields": csv_fields,
- "attempt_numbers": attempt_numbers
+ "attempt_numbers": attempt_numbers,
+ "course": course
}
return my_render_to_response('yaksh/monitor.html', context,
context_instance=ci)
@@ -1115,7 +1209,7 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None):
if not is_moderator(user):
raise Http404('You are not allowed to view this page!')
quiz = Quiz.objects.get(id=quiz_id)
- if not quiz.course.is_creator(user) and not quiz.course.is_teacher(user):
+ if not quiz.creator == user:
raise Http404('This course does not belong to you')
filter_form = QuestionFilterForm(user=user)
questions = None
@@ -1177,7 +1271,7 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None):
if 'save' in request.POST or 'back' in request.POST:
qpaper_form.save()
- return my_redirect('/exam/manage/courses/')
+ return my_redirect('/exam/manage/courses/all_quizzes/')
if marks:
questions = _get_questions(user, question_type, marks)
@@ -1268,10 +1362,12 @@ def show_all_questions(request):
if request.POST.get('test') == 'test':
question_ids = request.POST.getlist("question")
if question_ids:
- trial_paper = test_mode(user, False, question_ids, None)
+ trial_paper, trial_course, trial_module = test_mode(
+ user, False, question_ids, None)
trial_paper.update_total_marks()
trial_paper.save()
- return my_redirect("/exam/start/1/{0}".format(trial_paper.id))
+ return my_redirect("/exam/start/1/{0}/{1}/{2}".format(
+ trial_module.id, trial_paper.id, trial_course.id))
else:
context["msg"] = "Please select atleast one question to test"
@@ -1290,15 +1386,15 @@ def show_all_questions(request):
@login_required
@email_verified
-def user_data(request, user_id, questionpaper_id=None):
+def user_data(request, user_id, questionpaper_id=None, course_id=None):
"""Render user data."""
current_user = request.user
if not current_user.is_authenticated() or not is_moderator(current_user):
raise Http404('You are not allowed to view this page!')
user = User.objects.get(id=user_id)
- data = AnswerPaper.objects.get_user_data(user, questionpaper_id)
+ data = AnswerPaper.objects.get_user_data(user, questionpaper_id, course_id)
- context = {'data': data}
+ context = {'data': data, 'course_id': course_id}
return my_render_to_response('yaksh/user_data.html', context,
context_instance=RequestContext(request))
@@ -1327,7 +1423,8 @@ def download_quiz_csv(request, course_id, quiz_id):
csv_fields = []
attempt_number = None
question_paper = quiz.questionpaper_set.last()
- last_attempt_number =AnswerPaper.objects.get_attempt_numbers(question_paper.id).last()
+ last_attempt_number = AnswerPaper.objects.get_attempt_numbers(
+ question_paper.id, course.id).last()
if request.method == 'POST':
csv_fields = request.POST.getlist('csv_fields')
attempt_number = request.POST.get('attempt_number', last_attempt_number)
@@ -1341,7 +1438,6 @@ def download_quiz_csv(request, course_id, quiz_id):
attempt_number=attempt_number)
if not answerpapers:
return monitor(request, quiz_id)
-
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format(
course.name.replace('.', ''), quiz.description.replace('.', ''),
@@ -1383,7 +1479,8 @@ def download_quiz_csv(request, course_id, quiz_id):
@login_required
@email_verified
-def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
+def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
+ course_id=None):
"""Present an interface with which we can easily grade a user's papers
and update all their marks and also give comments for each paper.
"""
@@ -1400,11 +1497,12 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
quiz_id=quiz_id
).values("id")
user_details = AnswerPaper.objects.get_users_for_questionpaper(
- questionpaper_id
+ questionpaper_id, course_id
)
quiz = get_object_or_404(Quiz, id=quiz_id)
- if not quiz.course.is_creator(current_user) and not \
- quiz.course.is_teacher(current_user):
+ course = get_object_or_404(Course, id=course_id)
+ if not course.is_creator(current_user) and not \
+ course.is_teacher(current_user):
raise Http404('This course does not belong to you')
has_quiz_assignments = AssignmentUpload.objects.filter(
@@ -1414,11 +1512,12 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
"users": user_details,
"quiz_id": quiz_id,
"quiz": quiz,
- "has_quiz_assignments": has_quiz_assignments
+ "has_quiz_assignments": has_quiz_assignments,
+ "course_id": course_id
}
if user_id is not None:
attempts = AnswerPaper.objects.get_user_all_attempts(
- questionpaper_id, user_id
+ questionpaper_id, user_id, course_id
)
try:
if attempt_number is None:
@@ -1431,7 +1530,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
).exists()
user = User.objects.get(id=user_id)
data = AnswerPaper.objects.get_user_data(
- user, questionpaper_id, attempt_number
+ user, questionpaper_id, course_id, attempt_number
)
context = {
"data": data,
@@ -1440,7 +1539,8 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
"attempts": attempts,
"user_id": user_id,
"has_user_assignments": has_user_assignments,
- "has_quiz_assignments": has_quiz_assignments
+ "has_quiz_assignments": has_quiz_assignments,
+ "course_id": course_id
}
if request.method == "POST":
papers = data['papers']
@@ -1602,28 +1702,37 @@ def remove_teachers(request, course_id):
return my_redirect('/exam/manage/courses')
-def test_mode(user, godmode=False, questions_list=None, quiz_id=None):
+def test_mode(user, godmode=False, questions_list=None, quiz_id=None,
+ course_id=None):
"""creates a trial question paper for the moderators"""
if questions_list is not None:
trial_course = Course.objects.create_trial_course(user)
- trial_quiz = Quiz.objects.create_trial_quiz(trial_course, user)
+ trial_quiz = Quiz.objects.create_trial_quiz(user)
trial_questionpaper = QuestionPaper.objects.create_trial_paper_to_test_questions(
trial_quiz, questions_list
)
+ trial_unit, created = LearningUnit.objects.get_or_create(
+ order=1, type="quiz", quiz=trial_quiz,
+ check_prerequisite=False)
+ module, created = LearningModule.objects.get_or_create(
+ order=1, creator=user, check_prerequisite=False,
+ name="Trial for {0}".format(trial_course.name))
+ module.learning_unit.add(trial_unit)
+ trial_course.learning_module.add(module.id)
else:
- trial_quiz = Quiz.objects.create_trial_from_quiz(
- quiz_id, user, godmode
+ trial_quiz, trial_course, module = Quiz.objects.create_trial_from_quiz(
+ quiz_id, user, godmode, course_id
)
trial_questionpaper = QuestionPaper.objects.create_trial_paper_to_test_quiz(
trial_quiz, quiz_id
)
- return trial_questionpaper
+ return trial_questionpaper, trial_course, module
@login_required
@email_verified
-def test_quiz(request, mode, quiz_id):
+def test_quiz(request, mode, quiz_id, course_id=None):
"""creates a trial quiz for the moderators"""
godmode = True if mode == "godmode" else False
current_user = request.user
@@ -1631,17 +1740,21 @@ def test_quiz(request, mode, quiz_id):
if (quiz.is_expired() or not quiz.active) and not godmode:
return my_redirect('/exam/manage')
- trial_questionpaper = test_mode(current_user, godmode, None, quiz_id)
- return my_redirect("/exam/start/{0}".format(trial_questionpaper.id))
+ trial_questionpaper, trial_course, trial_module = test_mode(
+ current_user, godmode, None, quiz_id, course_id)
+ return my_redirect("/exam/start/{0}/{1}/{2}".format(
+ trial_questionpaper.id, trial_module.id, trial_course.id))
@login_required
@email_verified
-def view_answerpaper(request, questionpaper_id):
+def view_answerpaper(request, questionpaper_id, course_id):
user = request.user
quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz
- if quiz.view_answerpaper and user in quiz.course.students.all():
- data = AnswerPaper.objects.get_user_data(user, questionpaper_id)
+ course = get_object_or_404(Course, pk=course_id)
+ if quiz.view_answerpaper and user in course.students.all():
+ data = AnswerPaper.objects.get_user_data(user, questionpaper_id,
+ course_id)
has_user_assignment = AssignmentUpload.objects.filter(
user=user,
question_paper_id=questionpaper_id
@@ -1686,7 +1799,8 @@ def grader(request, extra_context=None):
@login_required
@email_verified
-def regrade(request, course_id, question_id=None, answerpaper_id=None, questionpaper_id=None):
+def regrade(request, course_id, question_id=None, answerpaper_id=None,
+ questionpaper_id=None):
user = request.user
course = get_object_or_404(Course, pk=course_id)
if not is_moderator(user) or (user != course.creator and user not in course.teachers.all()):
@@ -1698,7 +1812,7 @@ def regrade(request, course_id, question_id=None, answerpaper_id=None, questionp
details.append(answerpaper.regrade(question.id))
if questionpaper_id is not None and question_id is not None:
answerpapers = AnswerPaper.objects.filter(questions=question_id,
- question_paper_id=questionpaper_id)
+ question_paper_id=questionpaper_id, course_id=course_id)
for answerpaper in answerpapers:
details.append(answerpaper.regrade(question_id))
if answerpaper_id is not None and question_id is not None:
@@ -1713,7 +1827,8 @@ def download_course_csv(request, course_id):
user = request.user
if not is_moderator(user):
raise Http404('You are not allowed to view this page!')
- course = get_object_or_404(Course, pk=course_id)
+ course = Course.objects.prefetch_related("learning_module").get(
+ id=course_id)
if not course.is_creator(user) and not course.is_teacher(user):
raise Http404('The question paper does not belong to your course')
students = course.get_only_students().annotate(
@@ -1723,14 +1838,14 @@ def download_course_csv(request, course_id):
"id", "first_name", "last_name",
"email", "institute", "roll_number"
)
- quizzes = Quiz.objects.filter(course=course, is_trial=False)
+ quizzes = course.get_quizzes()
for student in students:
total_course_marks = 0.0
user_course_marks = 0.0
for quiz in quizzes:
quiz_best_marks = AnswerPaper.objects.get_user_best_of_attempts_marks\
- (quiz, student["id"])
+ (quiz, student["id"], course_id)
user_course_marks += quiz_best_marks
total_course_marks += quiz.questionpaper_set.values_list(
"total_marks", flat=True)[0]
@@ -2013,8 +2128,9 @@ def download_sample_csv(request):
"sample_user_upload.csv")
with open(csv_file_path, 'rb') as csv_file:
response = HttpResponse(csv_file.read(), content_type='text/csv')
- response['Content-Disposition'] = 'attachment;\
- filename="sample_user_upload"'
+ response['Content-Disposition'] = (
+ 'attachment; filename="sample_user_upload"'
+ )
return response
@@ -2022,7 +2138,7 @@ def download_sample_csv(request):
@email_verified
def duplicate_course(request, course_id):
user = request.user
- course = get_object_or_404(Course, pk=course_id)
+ course = Course.objects.get(id=course_id)
if not is_moderator(user):
raise Http404('You are not allowed to view this page!')
@@ -2034,6 +2150,7 @@ def duplicate_course(request, course_id):
return complete(request, msg, attempt_num=None, questionpaper_id=None)
return my_redirect('/exam/manage/courses/')
+
@login_required
@email_verified
def download_yaml_template(request):
@@ -2046,8 +2163,405 @@ def download_yaml_template(request):
yaml_file = zipfile.ZipFile(template_path, 'r')
template_yaml = yaml_file.open('questions_dump.yaml', 'r')
response = HttpResponse(template_yaml, content_type='text/yaml')
- response['Content-Disposition'] = 'attachment;\
- filename="questions_dump.yaml"'
-
+ response['Content-Disposition'] = (
+ 'attachment; filename="questions_dump.yaml"'
+ )
return response
+
+@login_required
+@email_verified
+def edit_lesson(request, lesson_id=None, course_id=None):
+ user = request.user
+ ci = RequestContext(request)
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ if lesson_id:
+ lesson = Lesson.objects.get(id=lesson_id)
+ if not lesson.creator == user and not course_id:
+ raise Http404('This Lesson does not belong to you')
+ else:
+ lesson = None
+ if course_id:
+ course = get_object_or_404(Course, id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This Lesson does not belong to you')
+ redirect_url = "/exam/manage/courses/"
+ else:
+ redirect_url = "/exam/manage/courses/all_lessons/"
+ context = {}
+ if request.method == "POST":
+ if "Save" in request.POST:
+ lesson_form = LessonForm(request.POST, instance=lesson)
+ lesson_file_form = LessonFileForm(request.POST, request.FILES)
+ lessonfiles = request.FILES.getlist('Lesson_files')
+ if lesson_form.is_valid():
+ if lesson is None:
+ lesson_form.instance.creator = user
+ lesson = lesson_form.save()
+ lesson.html_data = get_html_text(lesson.description)
+ lesson.save()
+ if lessonfiles:
+ for les_file in lessonfiles:
+ LessonFile.objects.get_or_create(
+ lesson=lesson, file=les_file
+ )
+ return my_redirect(redirect_url)
+ else:
+ context['lesson_form'] = lesson_form
+ context['lesson_file_form'] = lesson_file_form
+
+ if 'Delete' in request.POST:
+ remove_files_id = request.POST.getlist('delete_files')
+ if remove_files_id:
+ files = LessonFile.objects.filter(id__in=remove_files_id)
+ for file in files:
+ file.remove()
+ return my_redirect(redirect_url)
+
+ lesson_files = LessonFile.objects.filter(lesson=lesson)
+ lesson_files_form = LessonFileForm()
+ lesson_form = LessonForm(instance=lesson)
+ context['lesson_form'] = lesson_form
+ context['lesson_file_form'] = lesson_files_form
+ context['lesson_files'] = lesson_files
+ context['course_id'] = course_id
+ return my_render_to_response(
+ 'yaksh/add_lesson.html', context, context_instance=ci
+ )
+
+
+@login_required
+@email_verified
+def show_lesson(request, lesson_id, module_id, course_id):
+ user = request.user
+ course = Course.objects.get(id=course_id)
+ if user not in course.students.all():
+ raise Http404('This course does not belong to you')
+ if not course.active or not course.is_active_enrollment():
+ msg = "{0} is either expired or not active".format(course.name)
+ return quizlist_user(request, msg=msg)
+ learn_module = course.learning_module.get(id=module_id)
+ learn_unit = learn_module.learning_unit.get(lesson_id=lesson_id)
+ learning_units = learn_module.get_learning_units()
+ if learn_unit.has_prerequisite():
+ if not learn_unit.is_prerequisite_passed(user, learn_module, course):
+ msg = "You have not completed the prerequisite"
+ return view_module(request, learn_module.id, course_id, msg=msg)
+ context = {'lesson': learn_unit.lesson, 'user': user,
+ 'course': course, 'state': "lesson",
+ 'learning_units': learning_units, "current_unit": learn_unit,
+ 'learning_module': learn_module}
+ return my_render_to_response('yaksh/show_video.html', context)
+
+
+@login_required
+@email_verified
+def design_module(request, module_id, course_id=None):
+ user = request.user
+ ci = RequestContext(request)
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ context = {}
+ if course_id:
+ course = Course.objects.get(id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ learning_module = LearningModule.objects.get(id=module_id)
+ if request.method == "POST":
+ if "Add" in request.POST:
+ add_values = request.POST.get("choosen_list").split(',')
+ to_add_list = []
+ if add_values:
+ ordered_units = learning_module.get_learning_units()
+ if ordered_units.exists():
+ start_val = ordered_units.last().order + 1
+ else:
+ start_val = 1
+ for order, value in enumerate(add_values, start_val):
+ learning_id, type = value.split(":")
+ if type == "quiz":
+ learning_unit = LearningUnit.objects.create(
+ order=order, quiz_id=learning_id,
+ type=type)
+ else:
+ learning_unit = LearningUnit.objects.create(
+ order=order, lesson_id=learning_id,
+ type=type)
+ to_add_list.append(learning_unit)
+ learning_module.learning_unit.add(*to_add_list)
+
+ if "Change" in request.POST:
+ order_list = request.POST.get("ordered_list").split(",")
+ for order in order_list:
+ learning_unit, learning_order = order.split(":")
+ if learning_order:
+ learning_unit = learning_module.learning_unit.get(
+ id=learning_unit)
+ learning_unit.order = learning_order
+ learning_unit.save()
+
+ if "Remove" in request.POST:
+ remove_values = request.POST.getlist("delete_list")
+ if remove_values:
+ learning_module.learning_unit.remove(*remove_values)
+ LearningUnit.objects.filter(id__in=remove_values).delete()
+
+ if "Change_prerequisite" in request.POST:
+ unit_list = request.POST.getlist("check_prereq")
+ for unit in unit_list:
+ learning_unit = learning_module.learning_unit.get(id=unit)
+ learning_unit.toggle_check_prerequisite()
+ learning_unit.save()
+
+ added_quiz_lesson = learning_module.get_added_quiz_lesson()
+ quizzes = [("quiz", quiz) for quiz in Quiz.objects.filter(
+ creator=user, is_trial=False)]
+ lessons = [("lesson", lesson)
+ for lesson in Lesson.objects.filter(creator=user)]
+ quiz_les_list = set(quizzes + lessons) - set(added_quiz_lesson)
+ context['quiz_les_list'] = quiz_les_list
+ context['learning_units'] = learning_module.get_learning_units()
+ context['status'] = 'design'
+ context['module_id'] = module_id
+ context['course_id'] = course_id
+ return my_render_to_response('yaksh/add_module.html', context,
+ context_instance=ci)
+
+
+@login_required
+@email_verified
+def add_module(request, module_id=None, course_id=None):
+ user = request.user
+ ci = RequestContext(request)
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ redirect_url = "/exam/manage/courses/all_learning_module/"
+ if course_id:
+ course = Course.objects.get(id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ redirect_url = "/exam/manage/courses/"
+ if module_id:
+ module = LearningModule.objects.get(id=module_id)
+ if not module.creator == user and not course_id:
+ raise Http404('This Learning Module does not belong to you')
+ else:
+ module = None
+ context = {}
+ if request.method == "POST":
+ if "Save" in request.POST:
+ module_form = LearningModuleForm(request.POST, instance=module)
+ if module_form.is_valid():
+ if module is None:
+ module_form.instance.creator = user
+ module = module_form.save()
+ module.html_data = get_html_text(module.description)
+ module.save()
+ return my_redirect(redirect_url)
+ else:
+ context['module_form'] = module_form
+
+ module_form = LearningModuleForm(instance=module)
+ context['module_form'] = module_form
+ context['course_id'] = course_id
+ context['status'] = "add"
+ return my_render_to_response("yaksh/add_module.html",
+ context, context_instance=ci)
+
+
+@login_required
+@email_verified
+def show_all_quizzes(request):
+ user = request.user
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ quizzes = Quiz.objects.filter(creator=user, is_trial=False)
+ context = {"quizzes": quizzes, "type": "quiz"}
+ return my_render_to_response('yaksh/courses.html', context)
+
+
+@login_required
+@email_verified
+def show_all_lessons(request):
+ user = request.user
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ lessons = Lesson.objects.filter(creator=user)
+ context = {"lessons": lessons, "type": "lesson"}
+ return my_render_to_response('yaksh/courses.html', context)
+
+
+@login_required
+@email_verified
+def show_all_modules(request):
+ user = request.user
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ learning_modules = LearningModule.objects.filter(
+ creator=user, is_trial=False)
+ context = {"learning_modules": learning_modules, "type": "learning_module"}
+ return my_render_to_response('yaksh/courses.html', context)
+
+
+@login_required
+@email_verified
+def preview_html_text(request):
+ user = request.user
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ response_kwargs = {}
+ response_kwargs['content_type'] = 'application/json'
+ request_data = json.loads(request.body.decode("utf-8"))
+ html_text = get_html_text(request_data['description'])
+ return HttpResponse(json.dumps({"data": html_text}), **response_kwargs)
+
+
+@login_required
+@email_verified
+def get_next_unit(request, course_id, module_id, current_unit_id,
+ first_unit=None):
+ user = request.user
+ course = Course.objects.prefetch_related("learning_module").get(
+ id=course_id)
+ if user not in course.students.all():
+ raise Http404('You are not enrolled for this course!')
+ learning_module = course.learning_module.prefetch_related(
+ "learning_unit").get(id=module_id)
+ current_learning_unit = learning_module.learning_unit.get(
+ id=current_unit_id)
+
+ course_status = CourseStatus.objects.filter(
+ user=user, course_id=course_id,
+ )
+ if first_unit:
+ next_unit = current_learning_unit
+ else:
+ next_unit = learning_module.get_next_unit(current_learning_unit.id)
+ if not course_status.exists():
+ course_status = CourseStatus.objects.create(
+ user=user, course_id=course_id
+ )
+ else:
+ course_status = course_status.first()
+
+ # Add learning unit to completed units list
+ if not first_unit:
+ course_status.completed_units.add(current_learning_unit.id)
+
+ # make next available unit as current unit
+ course_status.current_unit = next_unit
+ course_status.save()
+ if next_unit.type == "quiz":
+ return my_redirect("/exam/start/{0}/{1}/{2}".format(
+ next_unit.quiz.questionpaper_set.get().id, module_id, course_id))
+ else:
+ return my_redirect("/exam/show_lesson/{0}/{1}/{2}".format(
+ next_unit.lesson.id, module_id, course_id))
+
+
+@login_required
+@email_verified
+def design_course(request, course_id):
+ user = request.user
+ ci = RequestContext(request)
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page!')
+ course = Course.objects.get(id=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ context = {}
+ if request.method == "POST":
+ if "Add" in request.POST:
+ add_values = request.POST.getlist("module_list")
+ to_add_list = []
+ if add_values:
+ ordered_modules = course.get_learning_modules()
+ if ordered_modules.exists():
+ start_val = ordered_modules.last().order + 1
+ else:
+ start_val = 1
+ for order, value in enumerate(add_values, start_val):
+ learning_module = LearningModule.objects.get(id=int(value))
+ learning_module.order = order
+ learning_module.save()
+ to_add_list.append(learning_module)
+ course.learning_module.add(*to_add_list)
+
+ if "Change" in request.POST:
+ order_list = request.POST.get("ordered_list").split(",")
+ for order in order_list:
+ learning_unit, learning_order = order.split(":")
+ if learning_order:
+ learning_module = course.learning_module.get(
+ id=learning_unit)
+ learning_module.order = learning_order
+ learning_module.save()
+
+ if "Remove" in request.POST:
+ remove_values = request.POST.getlist("delete_list")
+ if remove_values:
+ course.learning_module.remove(*remove_values)
+
+ if "Change_prerequisite" in request.POST:
+ unit_list = request.POST.getlist("check_prereq")
+ for unit in unit_list:
+ learning_module = course.learning_module.get(id=unit)
+ learning_module.toggle_check_prerequisite()
+ learning_module.save()
+
+ added_learning_modules = course.get_learning_modules()
+ all_learning_modules = LearningModule.objects.filter(
+ creator=user, is_trial=False)
+
+ learning_modules = set(all_learning_modules) - set(added_learning_modules)
+ context['added_learning_modules'] = added_learning_modules
+ context['learning_modules'] = learning_modules
+ context['course_id'] = course_id
+ return my_render_to_response('yaksh/design_course_session.html', context,
+ context_instance=ci)
+
+
+@login_required
+@email_verified
+def view_module(request, module_id, course_id, msg=None):
+ user = request.user
+ course = Course.objects.get(id=course_id)
+ if user not in course.students.all():
+ raise Http404('You are not enrolled for this course!')
+ context = {}
+ if not course.active or not course.is_active_enrollment():
+ msg = "{0} is either expired or not active".format(course.name)
+ return course_modules(request, course_id, msg)
+ learning_module = course.learning_module.get(id=module_id)
+ if learning_module.has_prerequisite():
+ if not learning_module.is_prerequisite_passed(user, course):
+ msg = "You have not completed the previous learning module"
+ return course_modules(request, course_id, msg)
+ learning_units = learning_module.get_learning_units()
+ context['learning_units'] = learning_units
+ context['learning_module'] = learning_module
+ context['first_unit'] = learning_units[0]
+ context['user'] = user
+ context['course'] = course
+ context['state'] = "module"
+ context['msg'] = msg
+ return my_render_to_response('yaksh/show_video.html', context)
+
+
+@login_required
+@email_verified
+def course_modules(request, course_id, msg=None):
+ user = request.user
+ course = Course.objects.get(id=course_id)
+ if user not in course.students.all():
+ msg = 'You are not enrolled for this course!'
+ return quizlist_user(request, msg=msg)
+
+ if not course.active or not course.is_active_enrollment():
+ msg = "{0} is either expired or not active".format(course.name)
+ return quizlist_user(request, msg=msg)
+ learning_modules = course.get_learning_modules()
+ context = {"course": course, "learning_modules": learning_modules,
+ "user": user, "msg": msg}
+ return my_render_to_response('yaksh/course_modules.html', context)