diff options
author | Prabhu Ramachandran | 2018-01-02 18:07:39 +0530 |
---|---|---|
committer | GitHub | 2018-01-02 18:07:39 +0530 |
commit | e566d54239efcb46f253e324b7295a676378f656 (patch) | |
tree | 3a93f3305c8b72dc2052ca33837b9b3dadb5d7af /yaksh | |
parent | 29a06f7372690796a05262fac6c428580e1f3155 (diff) | |
parent | 9bfdc506c3a54835ba9a1cd1fb70e9c2f825f0fb (diff) | |
download | online_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')
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;"> + </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}}"> Delete</input> + <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"> </th> + <td> </td> + <td> </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 }}"> {{ 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 }}"> {{ 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 }}"> + {{ 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 }}"> {{ 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"> </th> + <td> </td> + <td> </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">×</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> <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> + <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) |