diff options
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | yaksh/forms.py | 2 | ||||
-rw-r--r-- | yaksh/live_server_tests/selenium_test.py | 2 | ||||
-rw-r--r-- | yaksh/models.py | 52 | ||||
-rw-r--r-- | yaksh/templates/404.html | 8 | ||||
-rw-r--r-- | yaksh/templates/yaksh/complete.html | 7 | ||||
-rw-r--r-- | yaksh/templates/yaksh/courses.html | 10 | ||||
-rw-r--r-- | yaksh/templates/yaksh/design_questionpaper.html | 9 | ||||
-rw-r--r-- | yaksh/test_models.py | 123 | ||||
-rw-r--r-- | yaksh/test_views.py | 24 | ||||
-rw-r--r-- | yaksh/views.py | 18 |
11 files changed, 216 insertions, 41 deletions
@@ -6,7 +6,7 @@ Yaksh To get an overview of the Yaksh interface please refer to the user documentation at `Yaksh Docs <http://yaksh.readthedocs.io>`_ -This is a Quickstart guide to help users setup a trial instance. If you wish to deploy Yaksh in a production environment here is a `Production Deployment Guide <https://github.com/FOSSEE/online\_test/blob/master/README\_production.md>`_ +This is a Quickstart guide to help users setup a trial instance. If you wish to deploy Yaksh in a production environment here is a `Production Deployment Guide <https://github.com/FOSSEE/online\_test/blob/master/README\_production.rst>`_ Introduction ^^^^^^^^^^^^ diff --git a/yaksh/forms.py b/yaksh/forms.py index 9fd2eaa..258a1ee 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -339,4 +339,4 @@ class LearningModuleForm(forms.ModelForm): class Meta: model = LearningModule - fields = ['name', 'description'] + fields = ['name', 'description', 'active'] diff --git a/yaksh/live_server_tests/selenium_test.py b/yaksh/live_server_tests/selenium_test.py index 5635add..6351f9e 100644 --- a/yaksh/live_server_tests/selenium_test.py +++ b/yaksh/live_server_tests/selenium_test.py @@ -152,7 +152,7 @@ class SeleniumTest(): def close_quiz(self): quit_link_elem = WebDriverWait(self.driver, 5).until( - EC.presence_of_element_located((By.ID, "home")) + EC.presence_of_element_located((By.ID, "Next")) ) quit_link_elem.click() diff --git a/yaksh/models.py b/yaksh/models.py index d5b2923..252bde6 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -78,7 +78,6 @@ string_check_type = ( attempts = [(i, i) for i in range(1, 6)] attempts.append((-1, 'Infinite')) -days_between_attempts = [(j, j) for j in range(401)] test_status = ( ('inprogress', 'Inprogress'), @@ -155,6 +154,9 @@ class Lesson(models.Model): # Creator of the lesson creator = models.ForeignKey(User) + # Activate/Deactivate Lesson + active = models.BooleanField(default=True) + def __str__(self): return "{0}".format(self.name) @@ -311,8 +313,8 @@ class Quiz(models.Model): # 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 + time_between_attempts = models.FloatField( + "Time Between Quiz Attempts in hours" ) is_trial = models.BooleanField(default=False) @@ -352,20 +354,32 @@ class Quiz(models.Model): return demo_quiz def get_total_students(self, course): + try: + qp = self.questionpaper_set.get().id + except QuestionPaper.DoesNotExist: + qp = None return AnswerPaper.objects.filter( - question_paper=self.questionpaper_set.get().id, + question_paper=qp, course=course ).values_list("user", flat=True).distinct().count() def get_passed_students(self, course): + try: + qp = self.questionpaper_set.get().id + except QuestionPaper.DoesNotExist: + qp = None return AnswerPaper.objects.filter( - question_paper=self.questionpaper_set.get().id, + question_paper=qp, course=course, passed=True ).values_list("user", flat=True).distinct().count() def get_failed_students(self, course): + try: + qp = self.questionpaper_set.get().id + except QuestionPaper.DoesNotExist: + qp = None return AnswerPaper.objects.filter( - question_paper=self.questionpaper_set.get().id, + question_paper=qp, course=course, passed=False ).values_list("user", flat=True).distinct().count() @@ -453,6 +467,7 @@ class LearningModule(models.Model): creator = models.ForeignKey(User, related_name="module_creator") check_prerequisite = models.BooleanField(default=True) html_data = models.TextField(null=True, blank=True) + active = models.BooleanField(default=True) is_trial = models.BooleanField(default=False) def get_quiz_units(self): @@ -486,7 +501,7 @@ class LearningModule(models.Model): 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""" + """ Get module status if 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") @@ -497,10 +512,10 @@ class LearningModule(models.Model): default_status = "no units" elif all([status == "completed" for status in status_list]): default_status = "completed" - elif "inprogress" in status_list: - default_status = "inprogress" - else: + elif all([status == "not attempted" for status in status_list]): default_status = "not attempted" + else: + default_status = "inprogress" return default_status def is_prerequisite_passed(self, user, course): @@ -1221,7 +1236,7 @@ class QuestionPaper(models.Model): return all_questions def make_answerpaper(self, user, ip, attempt_num, course_id): - """Creates an answer paper for the user to attempt the quiz""" + """Creates an answer paper for the user to attempt the quiz""" try: ans_paper = AnswerPaper.objects.get(user=user, attempt_number=attempt_num, @@ -1246,14 +1261,6 @@ class QuestionPaper(models.Model): ans_paper.questions_order = ",".join(question_ids) ans_paper.save() ans_paper.questions_unanswered.add(*questions) - except AnswerPaper.MultipleObjectsReturned: - ans_paper = AnswerPaper.objects.get(user=user, - attempt_number=attempt_num, - question_paper=self, - course_id=course_id - ).order_by('-id') - ans_paper = ans_paper[0] - return ans_paper def _is_attempt_allowed(self, user, course_id): @@ -1268,7 +1275,7 @@ class QuestionPaper(models.Model): user=user, questionpaper=self, course_id=course_id ) if last_attempt: - time_lag = (timezone.now() - last_attempt.start_time).days + time_lag = (timezone.now() - last_attempt.start_time).total_seconds() / 3600 return time_lag >= self.quiz.time_between_attempts else: return True @@ -1563,6 +1570,11 @@ class AnswerPaper(models.Model): objects = AnswerPaperManager() + class Meta: + unique_together = ('user', 'question_paper', + 'attempt_number', "course" + ) + def get_per_question_score(self, question_id): if question_id not in self.get_questions().values_list('id', flat=True): return 'NA' diff --git a/yaksh/templates/404.html b/yaksh/templates/404.html index e9d99de..d4777f2 100644 --- a/yaksh/templates/404.html +++ b/yaksh/templates/404.html @@ -1,7 +1,13 @@ {% extends "base.html" %} +{% block pagetitle %} <h2>Yaksh</h2> {% endblock %} + {% block content %} +<center> It seems that you have encountered an error -Type of Error - {{ exception }} +<br> Please contact your administrator +<br><br> +<div class="alert alert-danger">Error Message:- {{exception}}</div> +</center> {% endblock %} diff --git a/yaksh/templates/yaksh/complete.html b/yaksh/templates/yaksh/complete.html index 19013ca..0881bfe 100644 --- a/yaksh/templates/yaksh/complete.html +++ b/yaksh/templates/yaksh/complete.html @@ -33,19 +33,20 @@ width="80" alt="YAKSH"></img>{% endblock %} <center><h3>{{message}}</h3></center> <center> <br> - <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 + <a href="{{URL_ROOT}}/exam/next_unit/{{course_id}}/{{module_id}}/{{learning_unit.id}}/1" class="btn btn-info" id="Next"> Next <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 + <a href="{{URL_ROOT}}/exam/next_unit/{{course_id}}/{{module_id}}/{{learning_unit.id}}" class="btn btn-info" id="Next"> Next <span class="glyphicon glyphicon-chevron-right"> </span> </a> {% endif %} + {% else %} + <a href="{{URL_ROOT}}/exam/" id="home" class="btn btn-success"> Home </a> {% endif %} </center> {% endblock content %} diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index afd53d7..9c00957 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -412,6 +412,11 @@ <ul class="list-group"> <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{lesson.id}}/"> {{ lesson.name }}</a> + {% if lesson.active %} + <span class="label label-success">Active</span> + {% else %} + <span class="label label-danger">Closed</span> + {% endif %} </ul> </td> {% endfor %} <!-- end for lessons --> @@ -446,6 +451,11 @@ <td> <a href="{{URL_ROOT}}/exam/manage/courses/add_module/{{module.id}}/"> {{ module.name }}</a> + {% if module.active %} + <span class="label label-success">Active</span> + {% else %} + <span class="label label-danger">Closed</span> + {% endif %} </td> <td> <a href="{{URL_ROOT}}/exam/manage/courses/designmodule/{{module.id}}"> diff --git a/yaksh/templates/yaksh/design_questionpaper.html b/yaksh/templates/yaksh/design_questionpaper.html index 829e27f..1656e2b 100644 --- a/yaksh/templates/yaksh/design_questionpaper.html +++ b/yaksh/templates/yaksh/design_questionpaper.html @@ -23,8 +23,13 @@ select {% block content %} <input type=hidden id="url_root" value={{ URL_ROOT }}> -<form action="{{ URL_ROOT }}/exam/manage/designquestionpaper/{{ qpaper.quiz.id }}/{{ qpaper.id }}/" method="POST" id="design_q"> -<input class ="btn primary small" type="submit" name="back" id="back" value="Cancel"> +{% if course_id %} + <form action="{{ URL_ROOT }}/exam/manage/designquestionpaper/{{ qpaper.quiz.id }}/{{ qpaper.id }}/{{course_id}}/" method="POST" id="design_q"> + <a href="{{URL_ROOT}}/exam/manage/courses" class="btn btn-danger">Cancel</a> +{% else %} + <form action="{{ URL_ROOT }}/exam/manage/designquestionpaper/{{ qpaper.quiz.id }}/{{ qpaper.id }}/" method="POST" id="design_q"> + <a href="{{URL_ROOT}}/exam/manage/courses/all_quizzes" class="btn btn-danger">Cancel</a> +{% endif %} {% csrf_token %} <input type=hidden name="is_active" id="is_active" value="{{ state }}"> <center><b>Manual mode to design the {{lang}} Question Paper</center><br> diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 7086a1e..cd4279b 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -12,6 +12,7 @@ from datetime import datetime, timedelta from django.utils import timezone import pytz from django.contrib.auth.models import Group +from django.db import IntegrityError from django.core.files import File from django.forms.models import model_to_dict from textwrap import dedent @@ -99,6 +100,7 @@ def setUpModule(): course.students.add(course_user) course.save() LessonFile.objects.create(lesson=lesson) + CourseStatus.objects.create(course=course, user=course_user) def tearDownModule(): @@ -136,6 +138,13 @@ class LearningModuleTestCases(unittest.TestCase): self.quiz = Quiz.objects.get(description='demo quiz 1') self.lesson = Lesson.objects.get(name='L1') self.course = Course.objects.get(name='Python Course') + self.course_status = CourseStatus.objects.get( + course=self.course, user=self.student) + + def tearDown(self): + # Remove unit from course status completed units + self.course_status.completed_units.remove(self.learning_unit_one) + self.course_status.completed_units.remove(self.learning_unit_two) def test_learning_module(self): self.assertEqual(self.learning_module.description, 'module one') @@ -196,7 +205,7 @@ class LearningModuleTestCases(unittest.TestCase): # Then self.assertEqual(unit, next_unit) - def test_get_status(self): + def test_get_module_status(self): # Given module_status = 'not attempted' # When @@ -204,6 +213,29 @@ class LearningModuleTestCases(unittest.TestCase): # Then self.assertEqual(status, module_status) + # Module in progress + + # Given + self.course_status.completed_units.add(self.learning_unit_one) + # When + status = self.learning_module.get_status(self.student, self.course) + # Then + self.assertEqual("inprogress", status) + + # Module is completed + + # Given + self.course_status.completed_units.add(self.learning_unit_two) + # When + status = self.learning_module.get_status(self.student, self.course) + # Then + self.assertEqual("completed", status) + + # Module with no units + self.course.learning_module.add(self.learning_module_two) + status = self.learning_module_two.get_status(self.student, self.course) + self.assertEqual("no units", status) + def test_module_completion_percent(self): # for module without learning units percent = self.learning_module_two.get_module_complete_percent( @@ -212,17 +244,12 @@ class LearningModuleTestCases(unittest.TestCase): self.assertEqual(percent, 0) # for module with learning units - lesson = Lesson.objects.get(name='L1') - self.completed_unit = LearningUnit.objects.get(lesson=lesson) - - course_status = CourseStatus.objects.create( - course=self.course, user=self.student) - course_status.completed_units.add(self.completed_unit) - + self.course_status.completed_units.add(self.learning_unit_one) + self.course_status.completed_units.add(self.learning_unit_two) percent = self.learning_module.get_module_complete_percent( self.course, self.student ) - self.assertEqual(percent, 50) + self.assertEqual(percent, 100) class LearningUnitTestCases(unittest.TestCase): @@ -449,10 +476,73 @@ class QuizTestCases(unittest.TestCase): self.course = Course.objects.get(name="Python Course") self.creator = User.objects.get(username="creator") self.teacher = User.objects.get(username="demo_user2") + self.student1 = User.objects.get(username='demo_user3') + self.student2 = User.objects.get(username='demo_user4') self.quiz1 = Quiz.objects.get(description='demo quiz 1') self.quiz2 = Quiz.objects.get(description='demo quiz 2') + self.quiz3 = Quiz.objects.create( + start_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + end_date_time=datetime(2199, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + duration=30, active=True, + attempts_allowed=1, time_between_attempts=0, + description='demo quiz 3', pass_criteria=0, + instructions="Demo Instructions" + ) + self.question_paper3 = QuestionPaper.objects.create(quiz=self.quiz3) + self.quiz4 = Quiz.objects.create( + start_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + end_date_time=datetime(2199, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + duration=30, active=True, + attempts_allowed=1, time_between_attempts=0, + description='demo quiz 4', pass_criteria=0, + instructions="Demo Instructions" + ) + self.answerpaper1 = AnswerPaper.objects.create( + user=self.student1, + question_paper=self.question_paper3, + course=self.course, + attempt_number=1, + start_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + end_time=datetime(2015, 10, 9, 10, 28, 15, 0, tzinfo=pytz.utc), + passed=True + ) + self.answerpaper2 = AnswerPaper.objects.create( + user=self.student2, + question_paper=self.question_paper3, + course=self.course, + attempt_number=1, + start_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc), + end_time=datetime(2015, 10, 9, 10, 28, 15, 0, tzinfo=pytz.utc), + passed=False + ) self.trial_course = Course.objects.create_trial_course(self.creator) + def tearDown(self): + self.answerpaper1.delete() + self.answerpaper2.delete() + self.trial_course.delete() + self.quiz3.delete() + self.quiz4.delete() + self.question_paper3.delete() + + def test_get_total_students(self): + self.assertEqual(self.quiz3.get_total_students(self.course), 2) + + def test_get_total_students_without_questionpaper(self): + self.assertEqual(self.quiz4.get_total_students(self.course), 0) + + def test_get_passed_students(self): + self.assertEqual(self.quiz3.get_passed_students(self.course), 1) + + def test_get_passed_students_without_questionpaper(self): + self.assertEqual(self.quiz4.get_passed_students(self.course), 0) + + def test_get_failed_students(self): + self.assertEqual(self.quiz3.get_failed_students(self.course), 1) + + def test_get_failed_students_without_questionpaper(self): + self.assertEqual(self.quiz4.get_failed_students(self.course), 0) + def test_quiz(self): """ Test Quiz""" self.assertEqual((self.quiz1.start_date_time).strftime('%Y-%m-%d'), @@ -530,7 +620,6 @@ class QuizTestCases(unittest.TestCase): self.assertTrue(self.quiz1.view_answerpaper) - ############################################################################### class QuestionPaperTestCases(unittest.TestCase): @classmethod @@ -773,7 +862,8 @@ class AnswerPaperTestCases(unittest.TestCase): question_paper=self.question_paper, start_time=self.start_time, end_time=self.end_time, - user_ip=self.ip + user_ip=self.ip, + course=self.course ) self.attempted_papers = AnswerPaper.objects.filter( question_paper=self.question_paper, @@ -1313,6 +1403,17 @@ class AnswerPaperTestCases(unittest.TestCase): self.assertEqual(self.user2_answerpaper2.current_question(), self.question1) + def test_duplicate_attempt_answerpaper(self): + with self.assertRaises(IntegrityError): + new_answerpaper = AnswerPaper.objects.create( + user=self.answerpaper.user, + question_paper=self.answerpaper.question_paper, + attempt_number=self.answerpaper.attempt_number, + start_time=self.answerpaper.start_time, + end_time=self.answerpaper.end_time, + course=self.answerpaper.course + ) + ############################################################################### class CourseTestCases(unittest.TestCase): diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 04aff8b..fb33114 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -5165,6 +5165,30 @@ class TestLessons(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.context["msg"], err_msg) + # Check if lesson is active + self.lesson.active = False + self.lesson.save() + 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})) + err_msg = "{0} is not active".format(self.lesson.name) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["msg"], err_msg) + + # Check if module is active + self.learning_module2.active = False + self.learning_module2.save() + response = self.client.get( + reverse('yaksh:show_lesson', + kwargs={"lesson_id": self.lesson2.id, + "module_id": self.learning_module2.id, + "course_id": self.course.id})) + err_msg = "{0} is not active".format(self.learning_module2.name) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["msg"], err_msg) + def test_show_all_lessons(self): """ Moderator should be able to see all created lessons""" self.client.login( diff --git a/yaksh/views.py b/yaksh/views.py index 1055bda..62a448c 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -476,6 +476,10 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, learning_module = course.learning_module.get(id=module_id) learning_unit = learning_module.learning_unit.get(quiz=quest_paper.quiz.id) + # unit module active status + if not learning_module.active: + return view_module(request, module_id, course_id) + # unit module prerequiste check if learning_module.has_prerequisite(): if not learning_module.is_prerequisite_passed(user, course): @@ -1393,7 +1397,8 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None, 'questions': questions, 'fixed_questions': fixed_questions, 'state': state, - 'random_sets': random_sets + 'random_sets': random_sets, + 'course_id': course_id } return my_render_to_response( 'yaksh/design_questionpaper.html', @@ -2358,6 +2363,13 @@ def show_lesson(request, lesson_id, module_id, course_id): 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 not learn_module.active: + return view_module(request, module_id, course_id) + + if not learn_unit.lesson.active: + msg = "{0} is not active".format(learn_unit.lesson.name) + return view_module(request, module_id, course_id, msg) if learn_module.has_prerequisite(): if not learn_module.is_prerequisite_passed(user, course): msg = "You have not completed the module previous to {0}".format( @@ -2670,6 +2682,10 @@ def view_module(request, module_id, course_id, msg=None): 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 not learning_module.active: + msg = "{0} is not active".format(learning_module.name) + return course_modules(request, course_id, msg) all_modules = course.get_learning_modules() if learning_module.has_prerequisite(): if not learning_module.is_prerequisite_passed(user, course): |