summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/forms.py19
-rw-r--r--yaksh/models.py15
-rw-r--r--yaksh/templates/yaksh/course_progress.html13
-rw-r--r--yaksh/templates/yaksh/courses.html28
-rw-r--r--yaksh/templates/yaksh/lessons.html30
-rw-r--r--yaksh/templates/yaksh/modules.html30
-rw-r--r--yaksh/templates/yaksh/quizzes.html30
-rw-r--r--yaksh/test_models.py8
-rw-r--r--yaksh/test_views.py166
-rw-r--r--yaksh/urls.py2
-rw-r--r--yaksh/views.py112
11 files changed, 436 insertions, 17 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py
index 7d5362b..c0f40ea 100644
--- a/yaksh/forms.py
+++ b/yaksh/forms.py
@@ -49,6 +49,12 @@ test_case_types = (
("floattestcase", "Float Testcase"),
)
+status_types = (
+ ('select','Select Status'),
+ ('active', 'Active'),
+ ('closed', 'Inactive'),
+ )
+
UNAME_CHARS = letters + "._" + digits
PWD_CHARS = letters + punctuation + digits
@@ -377,6 +383,19 @@ class QuestionFilterForm(forms.Form):
)
+class SearchFilterForm(forms.Form):
+ search_tags = forms.CharField(
+ label='Search Tags',
+ widget=forms.TextInput(attrs={'placeholder': 'Search',
+ 'class': form_input_class,}),
+ required=False
+ )
+ search_status = forms.CharField(max_length=16, widget=forms.Select(
+ choices=status_types,
+ attrs={'class': 'custom-select'}),
+ )
+
+
class CourseForm(forms.ModelForm):
""" course form for moderators """
class Meta:
diff --git a/yaksh/models.py b/yaksh/models.py
index 9aa3e0b..12c902b 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -601,7 +601,12 @@ class LearningUnit(models.Model):
check_prerequisite = models.BooleanField(default=True)
def get_lesson_or_quiz(self):
- return self.lesson if self.lesson else self.quiz
+ unit = None
+ if self.type == 'lesson':
+ unit = self.lesson
+ else:
+ unit = self.quiz
+ return unit
def toggle_check_prerequisite(self):
if self.check_prerequisite:
@@ -653,6 +658,14 @@ class LearningUnit(models.Model):
order=self.order, type="lesson", lesson=new_lesson)
return new_unit
+ def __str__(self):
+ name = None
+ if self.type == 'lesson':
+ name = self.lesson.name
+ else:
+ name = self.quiz.description
+ return name
+
###############################################################################
class LearningModule(models.Model):
diff --git a/yaksh/templates/yaksh/course_progress.html b/yaksh/templates/yaksh/course_progress.html
index a833c92..826f84b 100644
--- a/yaksh/templates/yaksh/course_progress.html
+++ b/yaksh/templates/yaksh/course_progress.html
@@ -4,10 +4,19 @@
{% if student_details %}
<div class="text-center">
<h3>Course Progress</h3>
- <div class="col">
- Number Of Students: {{ students_no }}
+ <br>
+ <div class="row">
+ <div class="col">
+ Number Of Students: {{ students_no }}
+ </div>
+ <div class="col">
+ <a href="{% url 'yaksh:download_course_progress' course.id %}" class="btn btn-info">
+ <i class="fa fa-download"></i>&nbsp;Download Data
+ </a>
+ </div>
</div>
</div>
+ <br>
{% include "yaksh/paginator.html" %}
<table class="tablesorter table table-bordered table-responsive-sm" id="course_table" data-sortlist="[0,0]">
<thead>
diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html
index 33d44ac..084d0f6 100644
--- a/yaksh/templates/yaksh/courses.html
+++ b/yaksh/templates/yaksh/courses.html
@@ -61,6 +61,32 @@
</div>
{% else %}
<hr>
+ <form name=frm action="" method="post">
+ {% csrf_token %}
+ <div class="card">
+ <div class="card-header">
+ <h3>Search/Filter Courses</h3>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ {{ form.search_tags }}
+ </div>
+ <div class="col-md-3">
+ {{ form.search_status }}
+ </div>
+ </div>
+ <br>
+ <button class="btn btn-success" type="submit">Search</button>
+ <a class="btn btn-primary" href="{% url 'yaksh:courses' %}">
+ Clear Search
+ </a>
+ </div>
+ </div>
+ </form>
+ <hr>
+ <center><h4 class="badge badge-success">{{ courses_found }} Course(s) Available</h4></center>
+
{% if messages %}
{% for message in messages %}
<div class="alert alert-dismissible alert-{{ message.tags }}">
@@ -198,7 +224,7 @@
<br>
{% for course in courses %}
<div class="card">
- <div class="card-header">
+ <div class="card-header bg-secondary">
<div class="row">
<div class="col-md-5">
{{course.name}}
diff --git a/yaksh/templates/yaksh/lessons.html b/yaksh/templates/yaksh/lessons.html
index 4b5d18e..e5af061 100644
--- a/yaksh/templates/yaksh/lessons.html
+++ b/yaksh/templates/yaksh/lessons.html
@@ -45,10 +45,36 @@
{% if not lessons %}
<br><br>
<div class="alert alert-info">
- <center><h3> No new Lessons added</h3></center>
+ <center><h3> No lessons found</h3></center>
</div>
{% else %}
- <center><h3> Lessons </h3></center>
+ <hr>
+ <form name=frm action="" method="post">
+ {% csrf_token %}
+ <div class="card">
+ <div class="card-header">
+ <h3> Search/Filter Lessons </h3>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ {{ form.search_tags }}
+ </div>
+ <div class="col-md-3">
+ {{ form.search_status }}
+ </div>
+ </div>
+ <br>
+ <button class="btn btn-success" type="submit">Search</button>
+ <a class="btn btn-primary" href="{% url 'yaksh:show_all_lessons' %}">
+ Clear Search
+ </a>
+ </div>
+ </div>
+ </form>
+ <hr>
+ <center><h4 class="badge badge-success">{{ lessons_found }} Lesson(s) Available</h4></center>
+
<table id="course-details" class="table table-bordered table-responsive-sm">
<tr>
<th>Sr.No</th>
diff --git a/yaksh/templates/yaksh/modules.html b/yaksh/templates/yaksh/modules.html
index e835cff..8da207c 100644
--- a/yaksh/templates/yaksh/modules.html
+++ b/yaksh/templates/yaksh/modules.html
@@ -46,10 +46,36 @@
{% if not modules %}
<br><br>
<div class="alert alert-info">
- <center><h3> No new learning modules added</h3></center>
+ <center><h3> No learning modules found</h3></center>
</div>
{% else %}
- <center><h3> Learning Modules </h3></center>
+ <hr>
+ <form name=frm action="" method="post">
+ {% csrf_token %}
+ <div class="card">
+ <div class="card-header">
+ <h3> Search/Filter Learning Modules </h3>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ {{ form.search_tags }}
+ </div>
+ <div class="col-md-3">
+ {{ form.search_status }}
+ </div>
+ </div>
+ <br>
+ <button class="btn btn-success" type="submit">Search</button>
+ <a class="btn btn-primary" href="{% url 'yaksh:show_all_modules' %}">
+ Clear Search
+ </a>
+ </div>
+ </div>
+ </form>
+ <hr>
+ <center><h4 class="badge badge-success">{{ modules_found }} Learning Module(s) Available</h4></center>
+
<table class="table table-bordered table-responsive-sm">
<tr>
<th>Sr.No</th>
diff --git a/yaksh/templates/yaksh/quizzes.html b/yaksh/templates/yaksh/quizzes.html
index 9b40647..58dec99 100644
--- a/yaksh/templates/yaksh/quizzes.html
+++ b/yaksh/templates/yaksh/quizzes.html
@@ -49,10 +49,36 @@
{% if not quizzes %}
<br><br>
<div class="alert alert-info">
- <center><h3> No new Quiz added</h3></center>
+ <center><h3> No quizzes found</h3></center>
</div>
{% else %}
- <center><h3> Quizzes </h3></center>
+ <hr>
+ <form name=frm action="" method="post">
+ {% csrf_token %}
+ <div class="card">
+ <div class="card-header">
+ <h3> Search/Filter Quizzes </h3>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ {{ form.search_tags }}
+ </div>
+ <div class="col-md-3">
+ {{ form.search_status }}
+ </div>
+ </div>
+ <br>
+ <button class="btn btn-success" type="submit">Search</button>
+ <a class="btn btn-primary" href="{% url 'yaksh:show_all_quizzes' %}">
+ Clear Search
+ </a>
+ </div>
+ </div>
+ </form>
+ <hr>
+ <center><h4 class="badge badge-success">{{ quizzes_found }} Quiz(zes) Available</h4></center>
+
<table class="table table-bordered table-responsive-sm">
<tr>
<th>Sr.No</th>
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index 6e5a26d..a60a1d6 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -322,8 +322,12 @@ class LearningUnitTestCases(unittest.TestCase):
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.assertEqual(
+ self.learning_unit_one.get_lesson_or_quiz(), self.lesson
+ )
+ self.assertEqual(
+ self.learning_unit_two.get_lesson_or_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)
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 7c932b5..569d4d7 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -2242,6 +2242,154 @@ class TestCourses(TestCase):
self.user1_course.learning_module.remove(self.learning_module1)
+class TestSearchFilters(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ # Create moderator group
+ self.mod_group = Group.objects.create(name="moderator")
+
+ #Create user1 with profile
+ self.user1_plaintext_pass = "demo1"
+ self.user1 = User.objects.create_user(
+ username='demo_user1',
+ password=self.user1_plaintext_pass,
+ first_name='user1_first_name',
+ last_name='user1_last_name',
+ email='demo1@test.com'
+ )
+ Profile.objects.create(
+ user=self.user1,
+ roll_number=10,
+ institute="IIT",
+ department="Chemical",
+ position="moderator",
+ timezone="UTC",
+ is_moderator=True
+ )
+
+ # Add user1 to moderator group
+ self.mod_group.user_set.add(self.user1)
+
+ # Create courses for user1
+ self.user1_course1 = Course.objects.create(
+ name="Demo Course",
+ enrollment="Enroll Request", creator=self.user1)
+ self.user1_course2 = Course.objects.create(
+ name="Test Course",
+ enrollment="Enroll Request", creator=self.user1)
+
+ # Create learning modules for user1
+ self.learning_module1 = LearningModule.objects.create(
+ order=0, name="Demo Module", description="Demo Module",
+ check_prerequisite=False, creator=self.user1)
+ self.learning_module2 = LearningModule.objects.create(
+ order=0, name="Test Module", description="Test Module",
+ check_prerequisite=False, creator=self.user1)
+
+ # Create quizzes for user1
+ self.quiz1 = Quiz.objects.create(
+ time_between_attempts=0, description='Demo Quiz',
+ creator=self.user1)
+ self.question_paper1 = QuestionPaper.objects.create(
+ quiz=self.quiz1, total_marks=1.0)
+
+ self.quiz2 = Quiz.objects.create(
+ time_between_attempts=0, description='Test Quiz',
+ creator=self.user1)
+ self.question_paper2 = QuestionPaper.objects.create(
+ quiz=self.quiz2, total_marks=1.0)
+
+ # Create lessons for user1
+ self.lesson1 = Lesson.objects.create(
+ name="Demo Lesson", description="Demo Lession",
+ creator=self.user1)
+ self.lesson2 = Lesson.objects.create(
+ name="Test Lesson", description="Test Lesson",
+ creator=self.user1)
+
+ # Create units for lesson and quiz
+ self.lesson_unit1 = LearningUnit.objects.create(
+ order=1, type="lesson", lesson=self.lesson1)
+ self.lesson_unit2 = LearningUnit.objects.create(
+ order=1, type="lesson", lesson=self.lesson2)
+ self.quiz_unit1 = LearningUnit.objects.create(
+ order=2, type="quiz", quiz=self.quiz1)
+ self.quiz_unit2 = LearningUnit.objects.create(
+ order=2, type="quiz", quiz=self.quiz2)
+
+ # Add units to module
+ self.learning_module1.learning_unit.add(self.lesson_unit1)
+ self.learning_module1.learning_unit.add(self.quiz_unit1)
+ self.learning_module2.learning_unit.add(self.lesson_unit2)
+ self.learning_module2.learning_unit.add(self.quiz_unit2)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user1.delete()
+ self.mod_group.delete()
+
+ def test_courses_search_filter(self):
+ """ Test to check if courses are obtained with tags and status """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:courses'),
+ data={'course_tags': 'demo', 'course_status': 'active'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/courses.html')
+ self.assertIsNotNone(response.context['form'])
+ self.assertIn(self.user1_course1, response.context['courses'])
+
+ def test_quizzes_search_filter(self):
+ """ Test to check if quizzes are obtained with tags and status """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:show_all_quizzes'),
+ data={'quiz_tags': 'demo', 'quiz_status': 'active'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/quizzes.html')
+ self.assertIsNotNone(response.context['form'])
+ self.assertIn(self.quiz1, response.context['quizzes'])
+
+ def test_lessons_search_filter(self):
+ """ Test to check if lessons are obtained with tags and status """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:show_all_lessons'),
+ data={'lesson_tags': 'demo', 'lesson_status': 'active'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/lessons.html')
+ self.assertIsNotNone(response.context['form'])
+ self.assertIn(self.lesson1, response.context['lessons'])
+
+ def test_learning_modules_search_filter(self):
+ """ Test to check if learning modules are obtained with tags and status """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(
+ reverse('yaksh:show_all_modules'),
+ data={'module_tags': 'demo', 'module_status': 'active'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/modules.html')
+ self.assertIsNotNone(response.context['form'])
+ self.assertIn(self.learning_module1, response.context['modules'])
+
+
class TestAddCourse(TestCase):
def setUp(self):
self.client = Client()
@@ -4211,6 +4359,24 @@ class TestDownloadCsv(TestCase):
self.assertEqual(response.get('Content-Disposition'),
'attachment; filename="{0}"'.format(file_name))
+ def test_download_course_progress_csv(self):
+ """
+ Check for csv result of a course progress
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(
+ reverse('yaksh:download_course_progress',
+ kwargs={'course_id': self.course.id}),
+ follow=True
+ )
+ file_name = "{0}.csv".format(self.course.name.lower().replace(" ", "_"))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get('Content-Disposition'),
+ 'attachment; filename="{0}"'.format(file_name))
+
def test_download_quiz_csv(self):
"""
Check for csv result of a quiz
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 49c3d4f..b53d335 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -218,4 +218,6 @@ urlpatterns = [
views.get_course_modules, name="get_course_modules"),
url(r'^manage/course/teachers/(?P<course_id>\d+)',
views.course_teachers, name="course_teachers"),
+ url(r'^manage/download/course/progress/(?P<course_id>\d+)',
+ views.download_course_progress, name="download_course_progress"),
]
diff --git a/yaksh/views.py b/yaksh/views.py
index a75763a..b54461f 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -43,7 +43,8 @@ from yaksh.forms import (
UserRegisterForm, UserLoginForm, QuizForm, QuestionForm,
QuestionFilterForm, CourseForm, ProfileForm,
UploadFileForm, FileForm, QuestionPaperForm, LessonForm,
- LessonFileForm, LearningModuleForm, ExerciseForm, TestcaseForm
+ LessonFileForm, LearningModuleForm, ExerciseForm, TestcaseForm,
+ SearchFilterForm
)
from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME
from .settings import URL_ROOT
@@ -1038,10 +1039,29 @@ def courses(request):
courses = Course.objects.filter(
Q(creator=user) | Q(teachers=user),
is_trial=False).order_by('-active').distinct()
+
+ form = SearchFilterForm()
+
+ if request.method == 'POST':
+ course_tags = request.POST.get('search_tags')
+ course_status = request.POST.get('search_status')
+
+ if course_status == 'select' :
+ courses = courses.filter(
+ name__contains=course_tags)
+ elif course_status == 'active' :
+ courses = courses.filter(
+ name__contains=course_tags, active=True)
+ elif course_status == 'closed':
+ courses = courses.filter(
+ name__contains=course_tags, active=False)
+
paginator = Paginator(courses, 30)
page = request.GET.get('page')
courses = paginator.get_page(page)
- context = {'objects': courses, 'created': True}
+ courses_found = courses.object_list.count()
+ context = {'objects': courses, 'created': True,
+ 'form': form, 'courses_found': courses_found}
return my_render_to_response(request, 'yaksh/courses.html', context)
@@ -2693,7 +2713,26 @@ def show_all_quizzes(request):
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}
+
+ form = SearchFilterForm()
+
+ if request.method == 'POST':
+ quiz_tags = request.POST.get('search_tags')
+ quiz_status = request.POST.get('search_status')
+
+ if quiz_status == 'select' :
+ quizzes = quizzes.filter(
+ description__contains=quiz_tags)
+ elif quiz_status == 'active' :
+ quizzes = quizzes.filter(
+ description__contains=quiz_tags, active=True)
+ elif quiz_status == 'closed':
+ quizzes = quizzes.filter(
+ description__contains=quiz_tags, active=False)
+ quizzes_found = quizzes.count()
+
+ context = {"quizzes": quizzes, "form": form,
+ "quizzes_found": quizzes_found}
return my_render_to_response(request, 'yaksh/quizzes.html', context)
@@ -2704,7 +2743,26 @@ def show_all_lessons(request):
if not is_moderator(user):
raise Http404('You are not allowed to view this page!')
lessons = Lesson.objects.filter(creator=user)
- context = {"lessons": lessons}
+
+ form = SearchFilterForm()
+
+ if request.method == 'POST':
+ lesson_tags = request.POST.get('search_tags')
+ lesson_status = request.POST.get('search_status')
+
+ if lesson_status == 'select' :
+ lessons = lessons.filter(
+ description__contains=lesson_tags)
+ elif lesson_status == 'active' :
+ lessons = lessons.filter(
+ description__contains=lesson_tags, active=True)
+ elif lesson_status == 'closed':
+ lessons = lessons.filter(
+ description__contains=lesson_tags, active=False)
+ lessons_found = lessons.count()
+
+ context = {"lessons": lessons, "form": form,
+ "lessons_found": lessons_found}
return my_render_to_response(request, 'yaksh/lessons.html', context)
@@ -2716,7 +2774,26 @@ def show_all_modules(request):
raise Http404('You are not allowed to view this page!')
learning_modules = LearningModule.objects.filter(
creator=user, is_trial=False)
- context = {"modules": learning_modules}
+
+ form = SearchFilterForm()
+
+ if request.method == 'POST':
+ module_tags = request.POST.get('search_tags')
+ module_status = request.POST.get('search_status')
+
+ if module_status == 'select' :
+ learning_modules = learning_modules.filter(
+ name__contains=module_tags)
+ elif module_status == 'active' :
+ learning_modules = learning_modules.filter(
+ name__contains=module_tags, active=True)
+ elif module_status == 'closed':
+ learning_modules = learning_modules.filter(
+ name__contains=module_tags, active=False)
+ learning_modules_found = learning_modules.count()
+
+ context = {"modules": learning_modules, "form": form,
+ "modules_found": learning_modules_found}
return my_render_to_response(
request, 'yaksh/modules.html', context
)
@@ -3135,3 +3212,28 @@ def get_course_modules(request, course_id):
modules = course.get_learning_modules()
context = {"modules": modules, "is_modules": True, "course": course}
return my_render_to_response(request, 'yaksh/course_detail.html', context)
+
+
+@login_required
+@email_verified
+def download_course_progress(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)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ students = course.students.order_by("-id")
+ stud_details = [(student.get_full_name(), course.get_grade(student),
+ course.get_completion_percent(student),
+ course.get_current_unit(student))
+ for student in students]
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = 'attachment; filename="{0}.csv"'.format(
+ (course.name).lower().replace(' ', '_'))
+ header = ['Name', 'Grade', 'Completion Percent', 'Current Unit']
+ writer = csv.writer(response)
+ writer.writerow(header)
+ for student in stud_details:
+ writer.writerow(student)
+ return response