diff options
author | Palaparthy Adityachandra | 2021-02-08 09:37:18 +0530 |
---|---|---|
committer | GitHub | 2021-02-08 09:37:18 +0530 |
commit | 1206e886588b3d7e8dfe5fb88a6891e85c5b73ee (patch) | |
tree | da2620ee519b68cfad42b2169b2a79cbccd3734d /yaksh | |
parent | 7448e1fa32f4e5af14a3af6b9196d6e1983edf55 (diff) | |
parent | fca319fefcd2a0e476415968b18873b0c791a5fe (diff) | |
download | online_test-1206e886588b3d7e8dfe5fb88a6891e85c5b73ee.tar.gz online_test-1206e886588b3d7e8dfe5fb88a6891e85c5b73ee.tar.bz2 online_test-1206e886588b3d7e8dfe5fb88a6891e85c5b73ee.zip |
Merge pull request #814 from adityacp/code_refactors
Code Refactor
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/forms.py | 2 | ||||
-rw-r--r-- | yaksh/models.py | 213 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/custom.css | 2 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/show_courses.js | 7 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/show_question.js | 7 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_course.html | 4 | ||||
-rw-r--r-- | yaksh/templates/yaksh/courses.html | 460 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 4 | ||||
-rw-r--r-- | yaksh/templates/yaksh/monitor.html | 104 | ||||
-rw-r--r-- | yaksh/templates/yaksh/post_comments.html | 4 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 32 | ||||
-rw-r--r-- | yaksh/templates/yaksh/quizzes_user.html | 4 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_lesson_quiz.html | 28 | ||||
-rw-r--r-- | yaksh/templates/yaksh/showquestions.html | 133 | ||||
-rw-r--r-- | yaksh/templates/yaksh/statistics_question.html | 123 | ||||
-rw-r--r-- | yaksh/templatetags/custom_filters.py | 7 | ||||
-rw-r--r-- | yaksh/test_models.py | 19 | ||||
-rw-r--r-- | yaksh/test_views.py | 32 | ||||
-rw-r--r-- | yaksh/views.py | 297 |
19 files changed, 722 insertions, 760 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py index 7a9eb87..01e691d 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -387,7 +387,7 @@ class QuestionFilterForm(forms.Form): class SearchFilterForm(forms.Form): search_tags = forms.CharField( label='Search Tags', - widget=forms.TextInput(attrs={'placeholder': 'Search', + widget=forms.TextInput(attrs={'placeholder': 'Search by course name', 'class': form_input_class, }), required=False ) diff --git a/yaksh/models.py b/yaksh/models.py index a475493..0f62948 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -991,13 +991,13 @@ class Course(models.Model): return self.rejected.all() def is_enrolled(self, user): - return user in self.students.all() + return self.students.filter(id=user.id).exists() def is_creator(self, user): return self.creator == user def is_teacher(self, user): - return True if user in self.teachers.all() else False + return self.teachers.filter(id=user.id).exists() def is_self_enroll(self): return True if self.enrollment == enrollment_methods[1][0] else False @@ -1061,7 +1061,7 @@ class Course(models.Model): return success def get_only_students(self): - teachers = list(self.teachers.all().values_list("id", flat=True)) + teachers = list(self.teachers.values_list("id", flat=True)) teachers.append(self.creator.id) students = self.students.exclude(id__in=teachers) return students @@ -1159,7 +1159,7 @@ class Course(models.Model): return grade def get_current_unit(self, user): - course_status = CourseStatus.objects.filter(course=self, user=user) + course_status = CourseStatus.objects.filter(course=self, user_id=user) if course_status.exists(): return course_status.first().current_unit @@ -1180,7 +1180,7 @@ class Course(models.Model): return percentage def is_student(self, user): - return user in self.students.all() + return self.students.filter(id=user.id).exists() def create_zip(self, path, static_files): zip_file_name = string_io() @@ -1953,6 +1953,12 @@ class QuestionPaper(models.Model): list(self.random_questions.all()) return len(questions) > 0 + def get_questions_count(self): + que_count = self.fixed_questions.count() + for r_set in self.random_questions.all(): + que_count += r_set.num_questions + return que_count + def __str__(self): return "Question Paper for " + self.quiz.description @@ -1979,58 +1985,6 @@ class QuestionSet(models.Model): ############################################################################### class AnswerPaperManager(models.Manager): - def get_all_questions(self, questionpaper_id, attempt_number, course_id, - status='completed'): - ''' Return a dict of question id as key and count as value''' - papers = self.filter(question_paper_id=questionpaper_id, - course_id=course_id, - attempt_number=attempt_number, status=status) - all_questions = list() - questions = list() - for paper in papers: - all_questions += paper.get_questions() - for question in all_questions: - questions.append(question.id) - return Counter(questions) - - def get_per_answer_stats(self, questionpaper_id, attempt_number, - course_id, status='completed'): - papers = self.filter(question_paper_id=questionpaper_id, - course_id=course_id, - attempt_number=attempt_number, status=status) - questions = Question.objects.filter( - questions__id__in=papers, - ).distinct() - - stats = {} - for question in questions: - answers = Answer.objects.filter( - answerpaper__id__in=papers, question=question.id - ).values('answer', 'question__id', 'answerpaper__id') - question_ans_count = {} - answerpaper_count = [] - for ans in answers: - if ans.get('answerpaper__id'): - if ans.get('answer') not in question_ans_count: - question_ans_count[ans.get('answer')] = 1 - else: - question_ans_count[ans.get('answer')] += 1 - answerpaper_count.append(ans.get('answerpaper__id')) - stats[question] = question_ans_count - return stats - - def get_all_questions_answered(self, questionpaper_id, attempt_number, - course_id, status='completed'): - ''' Return a dict of answered question id as key and count as value''' - papers = self.filter(question_paper_id=questionpaper_id, - course_id=course_id, - attempt_number=attempt_number, status=status) - questions_answered = list() - for paper in papers: - for question in filter(None, paper.get_questions_answered()): - if paper.is_answer_correct(question): - questions_answered.append(question.id) - return Counter(questions_answered) def get_attempt_numbers(self, questionpaper_id, course_id, status='completed'): @@ -2063,36 +2017,45 @@ class AnswerPaperManager(models.Manager): def get_question_statistics(self, questionpaper_id, attempt_number, course_id, status='completed'): ''' Return dict with question object as key and list as value - The list contains two value, first the number of times a question - was answered correctly, and second the number of times a question - appeared in a quiz''' + The list contains four values, first total attempts, second correct + attempts, third correct percentage, fourth per test case answers + a question + ''' question_stats = {} - questions_answered = self.get_all_questions_answered(questionpaper_id, - attempt_number, - course_id) - questions = self.get_all_questions(questionpaper_id, attempt_number, - course_id) - per_answer_stats = self.get_per_answer_stats( - questionpaper_id, attempt_number, course_id + qp = QuestionPaper.objects.get(id=questionpaper_id) + all_questions = qp.get_question_bank() + que_ids = [que.id for que in all_questions] + papers = self.filter( + question_paper_id=questionpaper_id, course_id=course_id, + attempt_number=attempt_number + ).values_list("id", flat=True) + answers = Answer.objects.filter( + answerpaper__id__in=papers, question_id__in=que_ids + ).order_by("id").values( + "answerpaper__id", "question_id", "correct", "answer" ) - all_questions = Question.objects.filter( - id__in=set(questions), - active=True - ).order_by('type') - for question in all_questions: - if question.id in questions_answered: - question_stats[question] = { - 'answered': [questions_answered[question.id], - questions[question.id]], - 'per_answer': per_answer_stats[question], - } - - else: - question_stats[question] = { - 'answered': [0, questions[question.id]], - 'per_answer': per_answer_stats[question], - } - + def _get_per_tc_data(answers, q_type): + tc = [] + for answer in answers["answer"]: + ans = literal_eval(answer) if answer else None + tc.extend(ans) if q_type == "mcc" else tc.append(str(ans)) + return dict(Counter(tc)) + df = pd.DataFrame(answers) + if not df.empty: + for question in all_questions: + que = df[df["question_id"]==question.id].groupby( + "answerpaper__id").tail(1) + if not que.empty: + total_attempts = que.shape[0] + correct_attempts = que[que["correct"]==True].shape[0] + per_tc_ans = {} + if question.type in ["mcq", "mcc"]: + per_tc_ans = _get_per_tc_data(que, question.type) + question_stats[question] = ( + total_attempts, correct_attempts, + round((correct_attempts/total_attempts)*100), + per_tc_ans + ) return question_stats def _get_answerpapers_for_quiz(self, questionpaper_id, course_id, @@ -2171,11 +2134,51 @@ class AnswerPaperManager(models.Manager): best_attempt = 0.0 papers = self.filter(question_paper__quiz_id=quiz.id, course_id=course_id, - user=user_id).values("marks_obtained") - if papers: - best_attempt = max([marks["marks_obtained"] for marks in papers]) + user=user_id).order_by("-marks_obtained").values( + "marks_obtained") + if papers.exists(): + best_attempt = papers[0]["marks_obtained"] return best_attempt + def get_user_scores(self, question_papers, user, course_id): + if not question_papers: + return None + qp_ids = list(zip(*question_papers))[0] + papers = self.filter( + course_id=course_id, user_id=user.get("id"), + question_paper__id__in=qp_ids + ).values("question_paper_id", "marks_obtained") + df = pd.DataFrame(papers) + user_marks = 0 + ap_data = None + if not df.empty: + ap_data = df.groupby("question_paper_id").tail(1) + for qp_id, quiz, quiz_marks in question_papers: + if ap_data is not None: + qp = ap_data['question_paper_id'].to_list() + marks = ap_data['marks_obtained'].to_list() + if qp_id in qp: + idx = qp.index(qp_id) + user_marks += marks[idx] + user[f"{quiz}-{quiz_marks}-Marks"] = marks[idx] + else: + user[f"{quiz}-{quiz_marks}-Marks"] = 0 + else: + user[f"{quiz}-{quiz_marks}-Marks"] = 0 + user.pop("id") + user["total_marks"] = user_marks + + def get_questions_attempted(self, answerpaper_ids): + answers = Answer.objects.filter( + answerpaper__id__in=answerpaper_ids + ).values("question_id", "answerpaper__id") + df = pd.DataFrame(answers) + answerpapers = df.groupby("answerpaper__id") + question_attempted = {} + for ap in answerpapers: + question_attempted[ap[0]] = len(ap[1]["question_id"].unique()) + return question_attempted + ############################################################################### class AnswerPaper(models.Model): @@ -2249,15 +2252,29 @@ class AnswerPaper(models.Model): 'attempt_number', "course" ) - def get_per_question_score(self, question_id): - questions = self.get_questions().values_list('id', flat=True) - if question_id not in questions: - return 'NA' - answer = self.get_latest_answer(question_id) - if answer: - return answer.marks - else: - return 0 + def get_per_question_score(self, question_ids): + if not question_ids: + return None + que_ids = list(zip(*question_ids))[1] + answers = self.answers.filter( + question_id__in=que_ids).values("question_id", "marks") + que_data = {} + df = pd.DataFrame(answers) + ans_data = None + if not df.empty: + ans_data = df.groupby("question_id").tail(1) + for que_summary, que_id in question_ids: + if ans_data is not None: + ans = ans_data['question_id'].to_list() + marks = ans_data['marks'].to_list() + if que_id in ans: + idx = ans.index(que_id) + que_data[que_summary] = marks[idx] + else: + que_data[que_summary] = 0 + else: + que_data[que_summary] = 0 + return que_data def current_question(self): """Returns the current active question to display.""" @@ -2420,7 +2437,7 @@ class AnswerPaper(models.Model): """ q_a = {} for question in self.questions.all(): - answers = question.answer_set.filter(answerpaper=self) + answers = question.answer_set.filter(answerpaper=self).distinct() if not answers.exists(): q_a[question] = [None, 0.0] continue diff --git a/yaksh/static/yaksh/css/custom.css b/yaksh/static/yaksh/css/custom.css index 91f68b5..78eaba4 100644 --- a/yaksh/static/yaksh/css/custom.css +++ b/yaksh/static/yaksh/css/custom.css @@ -210,4 +210,4 @@ iframe { bottom: 0; width: 100%; text-align: center; -}
\ No newline at end of file +} diff --git a/yaksh/static/yaksh/js/show_courses.js b/yaksh/static/yaksh/js/show_courses.js index 1209ce3..b887d01 100644 --- a/yaksh/static/yaksh/js/show_courses.js +++ b/yaksh/static/yaksh/js/show_courses.js @@ -36,4 +36,11 @@ window.onload = function() { $('#gridbtn').addClass('active'); $('#listbtn').removeClass('active'); } + if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + $("#course-list").removeClass("col-9"); + $("#course-list").addClass("col"); + } else{ + $("#course-list").addClass("col-9"); + $("#course-list").removeClass("col"); + } }
\ No newline at end of file diff --git a/yaksh/static/yaksh/js/show_question.js b/yaksh/static/yaksh/js/show_question.js index d7b6a44..abebdab 100644 --- a/yaksh/static/yaksh/js/show_question.js +++ b/yaksh/static/yaksh/js/show_question.js @@ -61,4 +61,11 @@ $(document).ready(function() { }); } }); + if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + $("#question-list").removeClass("col-9"); + $("#question-list").addClass("col"); + } else{ + $("#question-list").addClass("col-9"); + $("#question-list").removeClass("col"); + } }); diff --git a/yaksh/templates/yaksh/add_course.html b/yaksh/templates/yaksh/add_course.html index b264c5e..1afa34b 100644 --- a/yaksh/templates/yaksh/add_course.html +++ b/yaksh/templates/yaksh/add_course.html @@ -14,7 +14,7 @@ {% block title %} Add Course {% endblock %} {% block pagetitle %} Add Course {% endblock %} {% block content %} -<div class="container"> +<div class="container-fluid"> <div class="row"> <div class="col-md-8"> <ul class="nav nav-pills" id="course_tabs"> @@ -37,6 +37,7 @@ </div> </div> <hr> + <div class="container"> <form name=frm id=frm action="" method="post" > {% csrf_token %} <center> @@ -60,5 +61,6 @@ </center> <br> </form> + </div> </div> {% endblock %} diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index 38c106c..02cbad9 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -10,127 +10,218 @@ {% block content %} <div class="container-fluid"> - <div class="container"> - <div class="row"> - <div class="col-md-8"> - <ul class="nav nav-pills" id="course_tabs"> - <li class="nav-item"> - <a class="nav-link {% if created %}active{% endif %}" href="{% url 'yaksh:courses' %}"> - My Courses - </a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{% url 'yaksh:add_course' %}"> - Add/Edit Course - </a> - </li> - <li class="nav-item"> - <a href="{% url 'grades:grading_systems'%}" class="nav-link" > - Add/View Grading Systems - </a> - </li> - </ul> - </div> + <div class="row"> + <div class="col-md-8"> + <ul class="nav nav-pills" id="course_tabs"> + <li class="nav-item"> + <a class="nav-link {% if created %}active{% endif %}" href="{% url 'yaksh:courses' %}"> + My Courses + </a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{% url 'yaksh:add_course' %}"> + Add/Edit Course + </a> + </li> + <li class="nav-item"> + <a href="{% url 'grades:grading_systems'%}" class="nav-link" > + Add/View Grading Systems + </a> + </li> + </ul> </div> </div> - <div class="container"> + <br> + <div> {% if not objects %} <br><br> <div class="alert alert-info"> <center> <h3> No Courses Found </h3> </center> </div> {% else %} - <hr> - <form name=frm action="" method="get"> - <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 class="row"> + <div class="col"> + <form name=frm action="" method="get"> + <div class="card"> + <div class="card-header"> + <h3>Search/Filter Courses</h3> </div> - <div class="col-md-3"> - {{ form.search_status }} + <div class="card-body"> + <div> + {{form.search_tags}} + <br> + {{form.search_status}} + </div> + <br> + <button class="btn btn-outline-success" type="submit"> + <i class="fa fa-search"></i> Search + </button> + <a class="btn btn-outline-danger" href="{% url 'yaksh:courses' %}"> + <i class="fa fa-times"></i> Clear + </a> </div> </div> - <br> - <button class="btn btn-outline-success" type="submit"> - <i class="fa fa-search"></i> Search - </button> - <a class="btn btn-outline-danger" href="{% url 'yaksh:courses' %}"> - <i class="fa fa-times"></i> Clear - </a> - </div> + </form> </div> - </form> - <hr> - <center><h4 class="badge badge-success">{{ courses_found }} Course(s) Available</h4></center> + <div id="course-list"> + <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 }}"> - <button type="button" class="close" data-dismiss="alert"> - <i class="fa fa-close"></i> - </button> - <strong>{{ message }}</strong> - </div> - {% endfor %} - {% endif %} - {% with objects as courses %} - <div class="row"> - <div class="col-md-4"> - {% include "yaksh/paginator.html" %} - </div> - <div class="ml-auto"> - <div class="nav nav-pills" role="tablist" aria-orientation="vertical"> - <a id="listbtn" class="nav-link" data-toggle="pill" role="tab" aria-controls="show" aria-selected="true"> - <i class="fa fa-list"></i> - </a> - <a id="gridbtn" class="nav-link" data-toggle="pill" role="tab" aria-controls="updown" aria-selected="false"> - <i class="fa fa-columns"></i> - </a> + {% if messages %} + {% for message in messages %} + <div class="alert alert-dismissible alert-{{ message.tags }}"> + <button type="button" class="close" data-dismiss="alert"> + <i class="fa fa-close"></i> + </button> + <strong>{{ message }}</strong> + </div> + {% endfor %} + {% endif %} + {% with objects as courses %} + <div class="row"> + <div class="col-md-4"> + {% include "yaksh/paginator.html" %} + </div> + <div class="ml-auto"> + <div class="nav nav-pills" role="tablist" aria-orientation="vertical"> + <a id="listbtn" class="nav-link" data-toggle="pill" role="tab" aria-controls="show" aria-selected="true"> + <i class="fa fa-list"></i> + </a> + <a id="gridbtn" class="nav-link" data-toggle="pill" role="tab" aria-controls="updown" aria-selected="false"> + <i class="fa fa-columns"></i> + </a> + </div> + </div> </div> - </div> - </div> - <div class="tab-content"> - <div class="tab-pane active" id="gridview" role="tabpanel" aria-labelledby="gridbtn"> - <!-- GridView --> - <br> - <div class="row"> - <br> - {% for course in courses %} - <div class="col-md-6"> - <div class="card border-primary"> - <div class="card-header" style="height: 150px"> - {{course.name}} - <div> - {% if user.id != course.creator.id %} - <span class="badge badge-pill badge-warning"> - Allotted Course - </span> - {% else %} - <span class="badge badge-pill badge-primary"> - Created Course - </span> - {% endif %} - </div> - </div> - <div class="card-body"> - <div class="row"> - <div class="col"> - <strong>Starts On:</strong> - {{course.start_enroll_time}} + <div class="tab-content"> + <div class="tab-pane active" id="gridview" role="tabpanel" aria-labelledby="gridbtn"> + <!-- GridView --> + <br> + <div class="row"> + <br> + {% for course in courses %} + <div class="col-md-6"> + <div class="card border-primary"> + <div class="card-header" style="height: 150px"> + {{course.name}} + <div> + {% if user.id != course.creator.id %} + <span class="badge badge-pill badge-warning"> + Allotted Course + </span> + {% else %} + <span class="badge badge-pill badge-primary"> + Created Course + </span> + {% endif %} + </div> + </div> + <div class="card-body"> + <div class="row"> + <div class="col"> + <strong>Starts On:</strong> + {{course.start_enroll_time}} + <br> + <strong>Ends On:</strong> + {{course.end_enroll_time}} + </div> + </div> + <hr> + <div class="row"> + <div class="col-md-3"> + {% if course.active %} + <span class="badge badge-pill badge-success"> + Active + </span> + {% else %} + <span class="badge badge-pill badge-danger"> + Inactive + </span> + {% endif %} + </div> + <div class="col-md-4"> + <a href="{% url 'yaksh:toggle_course_status' course.id %}"> + {% if course.active %} + <i class="fa fa-toggle-on fa-2x"></i> + {% else %} + <i class="fa fa-toggle-off fa-2x"></i> + {% endif %} + </a> + </div> + </div> + <hr> + <div class="row"> + <div class="col-md-5"> + <a href="{% url 'yaksh:edit_course' course.id %}" class="btn btn-info"> + <i class="fa fa-edit"></i> + Edit + </a> + </div> + <div class="col-md-5"> + <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> + <i class="fa fa-tasks"></i> + Manage + </a> + </div> + </div> <br> - <strong>Ends On:</strong> - {{course.end_enroll_time}} + <div class="row"> + <div class="col-md-5"> + <a href="{% url 'yaksh:duplicate_course' course.id %}" class="btn btn-secondary"> + <i class="fa fa-clone"></i> + Clone + </a> + </div> + <div class="col-md-5"> + <div class="btn-group" role="group" aria-label="Button group with nested dropdown"> + <button type="button" class="btn btn-secondary"> + <i class="fa fa-download"></i> + Download + </button> + <div class="btn-group" role="group"> + <button id="btnGroupDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button> + <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style=""> + <a class="dropdown-item" href="{% url 'yaksh:download_course_csv' course.id %}"> + CSV + </a> + <a class="dropdown-item" href="{% url 'yaksh:download_course' course.id %}"> + Course + </a> + </div> + </div> + </div> + </div> + </div> </div> </div> - <hr> + <br> + </div> + {% endfor %} + <br> + </div> + </div> + <div class="tab-pane" id="listview" role="tabpanel" aria-labelledby="gridbtn"> + <!-- ListView --> + <br> + {% for course in courses %} + <div class="card"> + <div class="card-header bg-secondary"> <div class="row"> + <div class="col-md-5"> + {{course.name}} + </div> <div class="col-md-3"> + {% if user.id != course.creator.id %} + <span class="badge badge-pill badge-warning"> + Allotted Course + </span> + {% else %} + <span class="badge badge-pill badge-primary"> + Created Course + </span> + {% endif %} + </div> + <div class="col-md-2"> {% if course.active %} <span class="badge badge-pill badge-success"> Active @@ -141,7 +232,7 @@ </span> {% endif %} </div> - <div class="col-md-4"> + <div class="col-md-2"> <a href="{% url 'yaksh:toggle_course_status' course.id %}"> {% if course.active %} <i class="fa fa-toggle-on fa-2x"></i> @@ -151,157 +242,68 @@ </a> </div> </div> + </div> + <div class="card-body"> + <div class="row"> + <div class="col"> + <strong>Starts On:</strong> + {{course.start_enroll_time}} + <br> + <strong>Ends On:</strong> + {{course.end_enroll_time}} + </div> + </div> <hr> <div class="row"> - <div class="col-md-5"> + <div class="col-md-3"> <a href="{% url 'yaksh:edit_course' course.id %}" class="btn btn-info"> <i class="fa fa-edit"></i> - Edit Course + Edit </a> </div> - <div class="col-md-5"> + <div class="col-md-3"> <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> <i class="fa fa-tasks"></i> - Manage Course + Manage </a> </div> - </div> - <br> - <div class="row"> - <div class="col-md-5"> + <div class="col-md-3"> <a href="{% url 'yaksh:duplicate_course' course.id %}" class="btn btn-secondary"> <i class="fa fa-clone"></i> - Clone Course + Clone </a> </div> - <div class="col-md-5"> + <div class="col-md-3"> <div class="btn-group" role="group" aria-label="Button group with nested dropdown"> - <button type="button" class="btn btn-secondary"> - <i class="fa fa-download"></i> - Download - </button> - <div class="btn-group" role="group"> - <button id="btnGroupDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button> - <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style=""> - <a class="dropdown-item" href="{% url 'yaksh:download_course_csv' course.id %}"> - CSV - </a> - <a class="dropdown-item" href="{% url 'yaksh:download_course' course.id %}"> - Course - </a> - </div> - </div> + <button type="button" class="btn btn-secondary"> + <i class="fa fa-download"></i> + Download + </button> + <div class="btn-group" role="group"> + <button id="btnGroupDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button> + <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style=""> + <a class="dropdown-item" href="{% url 'yaksh:download_course_csv' course.id %}"> + CSV + </a> + <a class="dropdown-item" href="{% url 'yaksh:download_course' course.id %}"> + Course + </a> + </div> + </div> </div> </div> </div> </div> </div> <br> - </div> - {% endfor %} - <br> - </div> - </div> - <div class="tab-pane" id="listview" role="tabpanel" aria-labelledby="gridbtn"> - <!-- ListView --> - <br> - {% for course in courses %} - <div class="card"> - <div class="card-header bg-secondary"> - <div class="row"> - <div class="col-md-5"> - {{course.name}} - </div> - <div class="col-md-3"> - <span class="badge badge-pill badge-info"> - {% if user.id != course.creator.id %} - Allotted Course - {% else %} - Created Course - {% endif %} - </span> - </div> - <div class="col-md-2"> - {% if course.active %} - <span class="badge badge-pill badge-success"> - Active - </span> - {% else %} - <span class="badge badge-pill badge-danger"> - Inactive - </span> - {% endif %} - </div> - <div class="col-md-2"> - <a href="{% url 'yaksh:toggle_course_status' course.id %}"> - {% if course.active %} - <i class="fa fa-toggle-on fa-2x"></i> - {% else %} - <i class="fa fa-toggle-off fa-2x"></i> - {% endif %} - </a> - </div> - </div> - </div> - <div class="card-body"> - <div class="row"> - <div class="col"> - <strong>Starts On:</strong> - {{course.start_enroll_time}} - <br> - <strong>Ends On:</strong> - {{course.end_enroll_time}} - </div> - </div> - <hr> - <div class="row"> - <div class="col-md-3"> - <a href="{% url 'yaksh:edit_course' course.id %}" class="btn btn-info"> - <i class="fa fa-edit"></i> - Edit Course - </a> - </div> - <div class="col-md-3"> - <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> - <i class="fa fa-tasks"></i> - Manage Course - </a> - </div> - <div class="col-md-3"> - <div class="btn-group" role="group" aria-label="Button group with nested dropdown"> - <button type="button" class="btn btn-secondary"> - <i class="fa fa-download"></i> - Download - </button> - <div class="btn-group" role="group"> - <button id="btnGroupDrop1" type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button> - <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style=""> - <a class="dropdown-item" href="{% url 'yaksh:download_course_csv' course.id %}"> - CSV - </a> - <a class="dropdown-item" href="{% url 'yaksh:download_course' course.id %}"> - Course - </a> - </div> - </div> - </div> - </div> - <div class="col-md-3"> - <a href="{% url 'yaksh:duplicate_course' course.id %}" class="btn btn-secondary"> - <i class="fa fa-clone"></i> - Clone Course - </a> - </div> - </div> - </div> + {% endfor %} + <br> </div> - <br> - {% endfor %} - <br> + </div> + {% include "yaksh/paginator.html" %} + {% endwith %} </div> </div> - {% include "yaksh/paginator.html" %} - {% endwith %} {% endif %} </div> </div> diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index 86b7c47..4e1db2b 100644 --- a/yaksh/templates/yaksh/grade_user.html +++ b/yaksh/templates/yaksh/grade_user.html @@ -2,9 +2,9 @@ {% load custom_filters %} {% load static %} -{% block title %} Grader {% endblock %} +{% block title %} Quizzes {% endblock %} -{% block pagetitle %} Grader {% endblock pagetitle %} +{% block pagetitle %} Quizzes {% endblock pagetitle %} {% block script %} <script type="text/javascript" src="{% static 'yaksh/js/jquery.tablesorter.min.js' %}"> diff --git a/yaksh/templates/yaksh/monitor.html b/yaksh/templates/yaksh/monitor.html index fccf201..ca5a7fc 100644 --- a/yaksh/templates/yaksh/monitor.html +++ b/yaksh/templates/yaksh/monitor.html @@ -39,9 +39,20 @@ $(document).ready(function() {% block content %} <div class="container-fluid"> + {% if messages %} + {% for message in messages %} + <div class="alert alert-dismissible alert-{{ message.tags }}"> + <button type="button" class="close" data-dismiss="alert"> + <i class="fa fa-close"></i> + </button> + <strong>{{ message }}</strong> + </div> + {% endfor %} + {% endif %} {% if quiz %} {% if papers %} - <div class="card"> + <div class="row"> + <div class="card col"> <div class="table-responsive"> <table id="course-detail" class="table"> <tr> @@ -52,40 +63,32 @@ $(document).ready(function() <td><b>Quiz Name: </b></td> <td>{{quiz.description}}</td> </tr> - <tr> - <td><b>Number of papers:  </b></td> - <td>{{papers|length}}</td> - </tr> - <tr> - <td><b>Papers Completed:  </b></td> - <td> - {% completed papers as completed_papers %} - <b>{{completed_papers}}</b> - </td> - </tr> - <tr> - <td><b>Papers in progress:  </b></td> - <td> - {% inprogress papers as inprogress_papers %} - <b>{{ inprogress_papers }}</b> - </td> - </tr> </table> </div> </div> - <br> - <br> - {% if messages %} - {% for message in messages %} - <div class="alert alert-dismissible alert-{{ message.tags }}"> - <button type="button" class="close" data-dismiss="alert"> - <i class="fa fa-close"></i> - </button> - <strong>{{ message }}</strong> - </div> - {% endfor %} - {% endif %} - <br> + <div class="card col"> + <div class="table-responsive"> + <table id="course-detail" class="table"> + <tr> + <td><b>Number of papers:  </b></td> + <td>{{total_papers}}</td> + </tr> + <tr> + <td><b>Papers Completed:  </b></td> + <td> + <b>{{completed_papers}}</b> + </td> + </tr> + <tr> + <td><b>Papers in progress:  </b></td> + <td> + <b>{{ inprogress_papers }}</b> + </td> + </tr> + </table> + </div> + </div> + </div> <div class="row"> <div class="col-md-4"> <button type="button" class="btn btn-info" data-toggle="modal" data-target="#csvModal"> @@ -133,7 +136,7 @@ $(document).ready(function() </div> <br> <div class="card"> - {% if latest_attempts|length > 10 %} + {% if total_papers > 10 %} <div class="table-responsive" style="height: 800px"> {% else %} <div class="table-responsive"> @@ -145,7 +148,7 @@ $(document).ready(function() <th> Name <i class="fa fa-sort"></i> </th> <th> Roll No <i class="fa fa-sort"></i> </th> <th> Marks <i class="fa fa-sort"></i> </th> - <th> Attempts <i class="fa fa-sort"></i> </th> + <th> Questions Attempted <i class="fa fa-sort"></i> </th> <th> Time Left <i class="fa fa-sort"></i> </th> <th> Status <i class="fa fa-sort"></i> </th> <th> Extend time <i class="fa fa-sort"></i> </th> @@ -153,14 +156,19 @@ $(document).ready(function() </tr> </thead> <tbody class="list"> - {% for paper in latest_attempts %} + {% for paper in papers %} <tr> <td>{{forloop.counter}}</td> - <td> <a href="{% url 'yaksh:user_data' paper.user.id paper.question_paper.id course.id %}"> - {{ paper.user.get_full_name.title }}</a> </td> - <td> {{ paper.user.profile.roll_number }} </td> + {% with paper.user as student %} + <td> <a href="{% url 'yaksh:user_data' paper.user_id paper.question_paper_id course.id %}"> + {{ student.get_full_name.title }}</a> </td> + <td> {{ student.profile.roll_number }} </td> + {% endwith %} <td> {{ paper.marks_obtained }} </td> - <td> {{ paper.answers.count }} </td> + <td> + {% get_dict_value questions_attempted paper.id as que_attempt %} + {{que_attempt}} out of {{questions_count}} + </td> <td id="time_left{{forloop.counter0}}"> {{ paper.time_left }} </td> <td> {% if paper.is_attempt_inprogress %} <span class="badge badge-warning"> Inprogress </span> @@ -202,23 +210,9 @@ $(document).ready(function() <form action="{% url 'yaksh:download_quiz_csv' course.id quiz.id %}" method="post"> {% csrf_token %} <div class="modal-body"> - <b>Uncheck unwanted columns</b> - <br> - {% for field in csv_fields %} - <div class="form-check form-check-inline"> - <label class="form-check-label"> - {% if field == 'username' or field == 'questions' %} - <input class="form-check-input" name="csv_fields" type="checkbox" value="{{ field }}" checked onclick="return false"> {{ field }} - {% else %} - <input class="form-check-input" name="csv_fields" type="checkbox" value="{{ field }}" checked> {{ field }} - {% endif %} - </label> - </div> - <br> - {% endfor %} <b>Select Attempt Number: Default latest attempt</b> <select class="form-control" name = "attempt_number"> - {%for attempt_number in attempt_numbers %} + {% for attempt_number in attempt_numbers %} {% if forloop.last %} <option value="{{ attempt_number }}" selected>{{ attempt_number }} (Latest)</option> {% else %} @@ -243,7 +237,7 @@ $(document).ready(function() <div class="col-md-12"> <div class="alert alert-warning"> <center> - <h4>No Users Found for {{ quiz.description }}</h4> + <p>No Users Found for {{ quiz.description }}</p> </center> </div> </div> diff --git a/yaksh/templates/yaksh/post_comments.html b/yaksh/templates/yaksh/post_comments.html index aadc48b..17576b8 100644 --- a/yaksh/templates/yaksh/post_comments.html +++ b/yaksh/templates/yaksh/post_comments.html @@ -25,8 +25,8 @@ </center> </div> {% endif %} - <div class="card mb-2 border-dark"> - <div class="card-header text-white bg-dark py-2 px-3"> + <div class="card mb-2"> + <div class="card-header py-2 px-3"> {{post.title}} <br> <small> diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index 67bbf3f..56edcf3 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -158,28 +158,18 @@ question_type = "{{ question.type }}"; {% else %} <small class="textx text-muted"><strong>Language:</strong> <span class="badge badge-primary">{{question.language}}</span></small> {% endif %} - {% if question.type == "mcq" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">SINGLE CORRECT CHOICE</span></small> - {% elif question.type == "mcc" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">MULTIPLE CORRECT CHOICES</span></small> - {% elif question.type == "code" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">PROGRAMMING</span></small> - {% elif question.type == "upload" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">ASSIGNMENT UPLOAD</span></small> - {% elif question.type == "integer" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH INTEGER ANSWER</span></small> - {% elif question.type == "string" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH STRING ANSWER</span></small> - {% if testcase.string_check == "lower" %} - <br>(CASE INSENSITIVE) - {% else %} - <br>(CASE SENSITIVE) + <small class="text text-muted"><strong>Type:</strong> + <span class="badge badge-primary"> + {{question.get_type_display}} + {% if question.type == "string" %} + {% if testcase.string_check == "lower" %} + (CASE INSENSITIVE) + {% else %} + (CASE SENSITIVE) + {% endif %} {% endif %} - {% elif question.type == "float" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH FLOAT ANSWER</span></small> - {% elif question.type == "arrange" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">ARRANGE THE OPTIONS IN CORRECT ORDER</span></small> - {% endif %} + </span> + </small> <span class="badge badge-info pull-right"> <small><strong>Marks: {{ question.points }}</strong></small> </span> diff --git a/yaksh/templates/yaksh/quizzes_user.html b/yaksh/templates/yaksh/quizzes_user.html index b1c5e3c..3dfcbac 100644 --- a/yaksh/templates/yaksh/quizzes_user.html +++ b/yaksh/templates/yaksh/quizzes_user.html @@ -67,7 +67,7 @@ </div> <div class="col-md-2"> {% if user in course.data.requests.all %} - <span class="badge badge-warning badge-pill"> + <span class="badge badge-primary badge-pill"> Request Pending </span> {% elif user in course.data.rejected.all %} @@ -94,7 +94,7 @@ {% endif %} {% else %} <span class="badge badge-danger badge-pill"> - Enrollment Closed + No Enrollments allowed </span> {% endif %} {% else %} diff --git a/yaksh/templates/yaksh/show_lesson_quiz.html b/yaksh/templates/yaksh/show_lesson_quiz.html index 71c997d..48765bd 100644 --- a/yaksh/templates/yaksh/show_lesson_quiz.html +++ b/yaksh/templates/yaksh/show_lesson_quiz.html @@ -35,24 +35,18 @@ {% else %} <small class="textx text-muted"><strong>Language:</strong> <span class="badge badge-primary">{{question.language}}</span></small> {% endif %} - {% if question.type == "mcq" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">SINGLE CORRECT CHOICE</span></small> - {% elif question.type == "mcc" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">MULTIPLE CORRECT CHOICES</span></small> - {% elif question.type == "integer" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH INTEGER ANSWER</span></small> - {% elif question.type == "string" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH STRING ANSWER</span></small> - {% if testcase.string_check == "lower" %} - <br>(CASE INSENSITIVE) - {% else %} - <br>(CASE SENSITIVE) + <small class="text text-muted"><strong>Type:</strong> + <span class="badge badge-primary"> + {{question.get_type_display}} + {% if question.type == "string" %} + {% if testcase.string_check == "lower" %} + (CASE INSENSITIVE) + {% else %} + (CASE SENSITIVE) + {% endif %} {% endif %} - {% elif question.type == "float" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">FILL IN THE BLANKS WITH FLOAT ANSWER</span></small> - {% elif question.type == "arrange" %} - <small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">ARRANGE THE OPTIONS IN CORRECT ORDER</span></small> - {% endif %} + </span> + </small> <span class="badge badge-info pull-right"> <small><strong>Points: {{ question.points }}</strong></small> </span> diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index 81bf01a..aefb3a6 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -78,74 +78,73 @@ </div> {% endfor %} {% endif %} - <div class="card"> - <div class="card-body"> - <!-- Filter Questions --> - <h4>Filters Questions: </h4> - <form method="GET" action="{% url 'yaksh:questions_filter' %}"> - - <div class="row"> - <div class="col-md-4">{{ form.question_type }}</div> - <div class="col-md-4">{{ form.language }}</div> - <div class="col-md-4">{{ form.marks }}</div> - <br><br> - <div class="col"> - <button class="btn btn-outline-success"> - <i class="fa fa-filter"></i> Filter + <div class="row"> + <div class="col"> + <div class="card"> + <div class="card-body"> + <!-- Filter Questions --> + <h4>Filters Questions: </h4> + <form method="GET" action="{% url 'yaksh:questions_filter' %}"> + {{form.as_table}} + <br><br> + <button class="btn btn-outline-success"> + <i class="fa fa-filter"></i> Filter + </button> + </form> + <!-- End Filter Questions --> + <hr> + <h4>OR</h4> + <!-- Search by Tags --> + <h4>Search using Tags: </h4> + <select class="form-control" id="sel1" onchange="append_tag(this);"> + {% if all_tags %} + <option value="" disabled selected>Available Tags</option> + {% for tag in all_tags %} + <option> + {{tag}} + </option> + {% endfor %} + {% else %} + <option value="" disabled selected>No Available Tags</option> + {% endif %} + </select> + <br> + <form method="GET" action="{% url 'yaksh:search_questions_by_tags' %}"> + <div class="input-group"> + <input type="text" name="question_tags" id="question_tags" class="form-control" type="search" placeholder="Search questions using comma separated Tags"> + <span class="input-group-append"> + <button class="btn btn-outline-success" type="submit"> + <i class="fa fa-search"></i> </button> - </div> - </div> - </form> - <!-- End Filter Questions --> - <hr> - <!-- Search by Tags --> - <h4 >Search using Tags: </h4> - <div class="row"> - <div class="col"> - <form method="GET" action="{% url 'yaksh:search_questions_by_tags' %}"> - <div class="input-group"> - <input type="text" name="question_tags" id="question_tags" class="form-control" type="search" placeholder="Search questions using comma separated Tags"> - <span class="input-group-append"> - <button class="btn btn-outline-success" type="submit"> - <i class="fa fa-search"></i> Search - </button> - </span> - </div> - </form> - </div> - <div class="col"> - <select class="form-control" id="sel1" onchange="append_tag(this);"> - {% if all_tags %} - <option value="" disabled selected>Available Tags</option> - {% for tag in all_tags %} - <option> - {{tag}} - </option> - {% endfor %} - {% else %} - <option value="" disabled selected>No Available Tags</option> - {% endif %} - </select> - </div> + </span> + </div> + </form> + <br> + <!-- End Search by Tags --> + <a class="btn btn-outline-danger" href="{% url 'yaksh:show_questions' %}"> + <i class="fa fa-times"></i> Clear + </a> </div> - <br> - <!-- End Search by Tags --> - <a class="btn btn-outline-danger" href="{% url 'yaksh:show_questions' %}"> - <i class="fa fa-times"></i> Clear - </a> + <!-- End Card body --> </div> - <!-- End Card body --> </div> + <div id="question-list"> <!-- End card filters and search --> <form name=frm action="{% url 'yaksh:show_questions' %}" method="post"> {% csrf_token %} <div id="filtered-questions"> <br> - <a class="btn btn-lg btn-success" href="{% url 'yaksh:add_question' %}"> - <i class="fa fa-plus-circle"></i> Add Question</a> {% if objects %} - <br><br> - {% include "yaksh/paginator.html" %} + <div class="row"> + <div class="col"> + {% include "yaksh/paginator.html" %} + </div> + <div class="col"> + <a class="btn btn-lg btn-success" href="{% url 'yaksh:add_question' %}"> + <i class="fa fa-plus-circle"></i> Add Question + </a> + </div> + </div> <br> <h5><input id="checkall" type="checkbox"> Select All </h5> <div class="card"> @@ -158,7 +157,7 @@ <thead class="thead-dark"> <tr> <th> Select </th> - <th> Sr No. </th> + <th> Sr No.</th> <th> Summary <i class="fa fa-sort"></i> </th> <th> Language <i class="fa fa-sort"></i> </th> <th> Type <i class="fa fa-sort"></i> </th> @@ -174,8 +173,14 @@ <td> <input type="checkbox" name="question" value="{{ question.id }}"> </td> - <td>{{forloop.counter}}</td> - <td><a href="{% url 'yaksh:add_question' question.id %}">{{question.summary|capfirst}}</a></td> + <td> + {{forloop.counter}} + </td> + <td> + <a href="{% url 'yaksh:add_question' question.id %}" class="text-white"> + {{question.summary|capfirst}} + </a> + </td> <td>{{question.language|capfirst}}</td> <td>{{question.type|capfirst}}</td> <td>{{question.points}}</td> @@ -185,9 +190,9 @@ </a> </td> <td><a href="{% url 'yaksh:download_question' question.id %}" class="btn btn-primary"> - <i class="fa fa-download"></i> Download</a></td> + <i class="fa fa-download"></i></a></td> <td><a href="{% url 'yaksh:delete_question' question.id %}" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete {{question.summary|capfirst}}?')"> - <i class="fa fa-trash"></i> Delete</a></td> + <i class="fa fa-trash"></i></a></td> </tr> {% endfor %} </tbody> @@ -212,6 +217,8 @@ {% endif %} </center> </form> + </div> + </div> </div> </div> </div> diff --git a/yaksh/templates/yaksh/statistics_question.html b/yaksh/templates/yaksh/statistics_question.html index d70256b..588e131 100644 --- a/yaksh/templates/yaksh/statistics_question.html +++ b/yaksh/templates/yaksh/statistics_question.html @@ -18,10 +18,25 @@ </ul> </div> <div class="col-md-9"> + {% if messages %} + {% for message in messages %} + <div class="alert alert-dismissible alert-{{ message.tags }}"> + <button type="button" class="close" data-dismiss="alert"> + <i class="fa fa-close"></i> + </button> + <strong>{{ message }}</strong> + </div> + {% endfor %} + {% endif %} {% if question_stats %} <p><b>Total number of participants: {{ total }}</b></p> <table class="table table-responsive-sm"> - <tr class="bg-light yakshred"><th>Question</th><th></th><th>Type</th><th>Total</th><th>Answered Correctly</th></tr> + <tr class="bg-light"> + <th>Question</th> + <th>Type</th> + <th>Total attempts</th> + <th>Answered Correctly</th> + </tr> {% for question, data in question_stats.items %} <tr> <td style="width: 45%"> @@ -40,7 +55,7 @@ <strong> Description: </strong> - <p> + <p width="100%"> {{ question.description|safe }} </p> <strong> @@ -55,87 +70,49 @@ <p> {{ question.get_type_display }} </p> - {% if question.type in 'mcq mcc' %} - <strong> - Options: - </strong> - <p> - <ol> - {% for tc in question.testcase_set.all %} - <li> - {{ tc.mcqtestcase.options }} - {% if tc.mcqtestcase.correct %} - <span class="badge badge-primary">Correct</span> - {% endif %} - {% get_dict_value data.per_answer tc.id|stringformat:"i" as num %} - <span class="badge badge-info">Answered: {{ num }}</span> - </li> + {% if question.type in "mcq mcc" %} + {% for tc in question.get_test_cases %} + {% if tc.correct %} + <span class="badge badge-pill badge-success"> + {{forloop.counter}}. + </span> + {% else %} + <span class="badge badge-pill badge-dark"> + {{ forloop.counter }}. + </span> + {% endif %} + {{tc.options}} + {% get_percent_value data.3 tc.id total as percent %} + <div class="progress-wrapper col-md-4"> + <div class="progress-info"> + <div class="progress-percentage"> + <span> + {% if percent %} {{percent|floatformat}} {% else %} 0 {% endif %}% + </span> + </div> + </div> + <div class="progress"> + {% if percent %} + <div class="progress-bar bg-success" role="progressbar" aria-valuenow="{{percent}}" + aria-valuemin="0" aria-valuemax="100" style="width:{{percent|floatformat}}%"> + </div> + {% endif %} + </div> + </div> + <br> {% endfor %} - </ol> - </p> {% endif %} </div> </div> </div> </td> - <td>{{ question.type }}</td> - <td>{{data.answered.1}}</td><td>{{ data.answered.0 }} ({% widthratio data.answered.0 data.answered.1 100 %}%)</td> - - + <td>{{ question.get_type_display }}</td> + <td>{{data.0}} out of {{total}}</td> + <td>{{ data.1 }} out of {{data.0}} ({{data.2}}%)</td> </tr> {% endfor %} </table> {% endif %} - - <!-- The Modal --> - <div class="modal" id="question_detail_modal"> - <div class="modal-dialog"> - <div class="modal-content"> - - <!-- Modal Header --> - <div class="modal-header"> - <h4 class="modal-title">Question Details</h4> - <button type="button" class="close" data-dismiss="modal">×</button> - </div> - - <!-- Modal body --> - <div class="modal-body"> - <table> - <tr> - <td>Summary</td> - <td>{{ question.summary }}</td> - </tr> - <tr> - <td>Description</td> - <td>{{ question.description }}</td> - </tr> <tr> - <td>Type</td> - <td>{{ question.type }}</td> - </tr> <tr> - <td>Points</td> - <td>{{ question.points }}</td> - </tr> - <tr> - {% for tc in question.testcase_set.all %} - tc - {% endfor %} - <br><br> - </tr> - </table> - </div> - - <!-- Modal footer --> - <div class="modal-footer"> - <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button> - </div> - - </div> - </div> - </div> - - </div> - </div> - <!-- end Modal outer --> </div> </div> </div> diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 81572a7..b026159 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -214,7 +214,10 @@ def get_lesson_views(course_id, lesson_id): @register.simple_tag -def get_dict_value(dictionary, key): - return dictionary.get(key, None) +def get_percent_value(dictionary, key, total): + return round((dictionary.get(str(key), 0)/total)*100) +@register.simple_tag +def get_dict_value(dictionary, key): + return dictionary.get(key, 0) diff --git a/yaksh/test_models.py b/yaksh/test_models.py index fe0d3b5..a424b36 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1416,27 +1416,34 @@ class AnswerPaperTestCases(unittest.TestCase): def test_get_per_question_score(self): # Given question_id = self.question4.id - expected_score = 1 + question_name = self.question4.summary + expected_score = {"Q4": 1.0} # When score = self.answerpaper_single_question.get_per_question_score( - question_id + [(question_name, question_id)] ) # Then self.assertEqual(score, expected_score) # Given question_id = self.question2.id - expected_score = 0 + question_name = self.question2.summary + expected_score = {"Q2": 0.0} # When - score = self.answerpaper.get_per_question_score(question_id) + score = self.answerpaper.get_per_question_score( + [(question_name, question_id)] + ) # Then self.assertEqual(score, expected_score) # Given question_id = 131 - expected_score = 'NA' + question_name = "NA" + expected_score = {'NA': 0} # When - score = self.answerpaper.get_per_question_score(question_id) + score = self.answerpaper.get_per_question_score( + [(question_name, question_id)] + ) # Then self.assertEqual(score, expected_score) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 31066d1..58b7506 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -614,10 +614,7 @@ class TestMonitor(TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "yaksh/monitor.html") - self.assertEqual(response.context['msg'], "Quiz Results") self.assertEqual(response.context['papers'][0], self.answerpaper) - self.assertEqual(response.context['latest_attempts'][0], - self.answerpaper) def test_get_quiz_user_data(self): """ @@ -4955,6 +4952,14 @@ class TestDownloadCsv(TestCase): total_marks=1.0, fixed_question_order=str(self.question.id) ) self.question_paper.fixed_questions.add(self.question) + self.learning_unit = LearningUnit.objects.create( + order=1, type="quiz", quiz=self.quiz) + self.learning_module = LearningModule.objects.create( + order=1, name="download module", description="download 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) + # student answerpaper user_answer = "def add(a, b)\n\treturn a+b" @@ -5056,7 +5061,9 @@ class TestDownloadCsv(TestCase): kwargs={'course_id': self.course.id}), follow=True ) - file_name = "{0}.csv".format(self.course.name.lower()) + file_name = "{0}.csv".format( + self.course.name.replace(" ", "_").lower() + ) self.assertEqual(response.status_code, 200) self.assertEqual(response.get('Content-Disposition'), 'attachment; filename="{0}"'.format(file_name)) @@ -5089,15 +5096,16 @@ class TestDownloadCsv(TestCase): username=self.user.username, password=self.user_plaintext_pass ) - response = self.client.get( + response = self.client.post( reverse('yaksh:download_quiz_csv', kwargs={"course_id": self.course.id, "quiz_id": self.quiz.id}), + data={"attempt_number": 1}, follow=True ) - file_name = "{0}-{1}-attempt{2}.csv".format( - self.course.name.replace('.', ''), - self.quiz.description.replace('.', ''), 1 + file_name = "{0}-{1}-attempt-{2}.csv".format( + self.course.name.replace(' ', '_'), + self.quiz.description.replace(' ', '_'), 1 ) self.assertEqual(response.status_code, 200) self.assertEqual(response.get('Content-Disposition'), @@ -5660,11 +5668,9 @@ class TestShowStatistics(TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/statistics_question.html') self.assertIn(self.question, list(question_stats.keys())) - self.assertSequenceEqual( - list(question_stats.values())[0]['answered'], [1, 1] - ) - self.assertEqual(response.context['attempts'][0], 1) - self.assertEqual(response.context['total'], 1) + q_data = list(question_stats.values())[0] + self.assertSequenceEqual(q_data[0:2], [1, 1]) + self.assertEqual(100, q_data[2]) class TestQuestionPaper(TestCase): diff --git a/yaksh/views.py b/yaksh/views.py index 50f9ded..83b6766 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -26,6 +26,7 @@ from textwrap import dedent import zipfile import markdown import ruamel +import pandas as pd try: from StringIO import StringIO as string_io except ImportError: @@ -80,7 +81,10 @@ def is_moderator(user, group_name=MOD_GROUP_NAME): """Check if the user is having moderator rights""" try: group = Group.objects.get(name=group_name) - return user.profile.is_moderator and user in group.user_set.all() + return ( + user.profile.is_moderator and + group.user_set.filter(id=user.id).exists() + ) except Profile.DoesNotExist: return False except Group.DoesNotExist: @@ -99,10 +103,6 @@ def add_as_moderator(users, group_name=MOD_GROUP_NAME): user.profile.save() -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.markdown( @@ -681,8 +681,9 @@ def show_question(request, question, paper, error_message=None, delay_time = paper.time_left_on_question(question) if previous_question and quiz.is_exercise: - if (delay_time <= 0 or previous_question in - paper.questions_answered.all()): + is_prev_que_answered = paper.questions_answered.filter( + id=previous_question.id).exists() + if (delay_time <= 0 or is_prev_que_answered): can_skip = True question = previous_question if not question: @@ -706,7 +707,7 @@ def show_question(request, question, paper, error_message=None, ) else: quiz_type = 'Exercise' - if question in paper.questions_answered.all(): + if paper.questions_answered.filter(id=question.id).exists(): notification = ( 'You have already attempted this question successfully' if question.type == "code" else @@ -798,6 +799,14 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, course_id=course_id ) current_question = get_object_or_404(Question, pk=q_id) + def is_valid_answer(answer): + status = True + if ((current_question.type == "mcc" or + current_question.type == "arrange") and not answer): + status = False + elif answer is None or not str(answer): + status = False + return status if request.method == 'POST': # Add the answer submitted, regardless of it being correct or not. @@ -877,7 +886,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, previous_question=current_question) else: user_answer = request.POST.get('answer') - if not str(user_answer): + if not is_valid_answer(user_answer): msg = "Please submit a valid answer." return show_question( request, current_question, paper, notification=msg, @@ -1326,10 +1335,10 @@ def show_statistics(request, questionpaper_id, attempt_number=None, attempt_numbers = AnswerPaper.objects.get_attempt_numbers( questionpaper_id, course_id) quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz + context = {'quiz': quiz, 'attempts': attempt_numbers, + 'questionpaper_id': questionpaper_id, + 'course_id': course_id} if attempt_number is None: - context = {'quiz': quiz, 'attempts': attempt_numbers, - 'questionpaper_id': questionpaper_id, - 'course_id': course_id} return my_render_to_response( request, 'yaksh/statistics_question.html', context ) @@ -1338,7 +1347,10 @@ def show_statistics(request, questionpaper_id, attempt_number=None, course_id) if not AnswerPaper.objects.has_attempt(questionpaper_id, attempt_number, course_id): - return my_redirect('/exam/manage/') + messages.warning(request, "No answerpapers found") + return my_render_to_response( + request, 'yaksh/statistics_question.html', context + ) question_stats = AnswerPaper.objects.get_question_statistics( questionpaper_id, attempt_number, course_id ) @@ -1360,51 +1372,42 @@ def monitor(request, quiz_id=None, course_id=None): if not is_moderator(user): raise Http404('You are not allowed to view this page!') - # quiz_id is not None. - try: - quiz = get_object_or_404(Quiz, id=quiz_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 course does not belong to you') - q_paper = QuestionPaper.objects.filter(quiz__is_trial=False, - quiz_id=quiz_id).distinct() - except (QuestionPaper.DoesNotExist, Course.DoesNotExist): - papers = [] - q_paper = None - latest_attempts = [] - attempt_numbers = [] + 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') + + quiz = get_object_or_404(Quiz, id=quiz_id) + q_paper = QuestionPaper.objects.filter(quiz__is_trial=False, + quiz_id=quiz_id).distinct().last() + attempt_numbers = AnswerPaper.objects.get_attempt_numbers( + q_paper.id, course.id + ) + latest_attempt_num = max(list(attempt_numbers)) if attempt_numbers else 0 + questions_count = 0 + questions_attempted = {} + completed_papers = 0 + inprogress_papers = 0 + papers = AnswerPaper.objects.filter( + question_paper_id=q_paper.id, + course_id=course_id, attempt_number=latest_attempt_num + ).order_by('user__first_name') + if not papers.exists(): + messages.warning(request, "No AnswerPapers found") else: - if q_paper: - attempt_numbers = AnswerPaper.objects.get_attempt_numbers( - q_paper.last().id, course.id) - else: - attempt_numbers = [] - latest_attempts = [] - papers = AnswerPaper.objects.filter( - question_paper_id=q_paper.first().id, - course_id=course_id).order_by( - 'user__profile__roll_number' + questions_count = q_paper.get_questions_count() + questions_attempted = AnswerPaper.objects.get_questions_attempted( + papers.values_list("id", flat=True) ) - users = papers.values_list('user').distinct() - for auser in users: - last_attempt = papers.filter(user__in=auser).aggregate( - last_attempt_num=Max('attempt_number') - ) - latest_attempts.append( - papers.get( - user__in=auser, - attempt_number=last_attempt['last_attempt_num'] - ) - ) - csv_fields = CSV_FIELDS + completed_papers = papers.filter(status="completed").count() + inprogress_papers = papers.filter(status="inprogress").count() context = { - "papers": papers, - "quiz": quiz, - "msg": "Quiz Results", - "latest_attempts": latest_attempts, - "csv_fields": csv_fields, + "papers": papers, "quiz": quiz, + "inprogress_papers": inprogress_papers, "attempt_numbers": attempt_numbers, - "course": course + "course": course, "total_papers": papers.count(), + "completed_papers": completed_papers, + "questions_attempted": questions_attempted, + "questions_count": questions_count } return my_render_to_response(request, 'yaksh/monitor.html', context) @@ -1817,18 +1820,6 @@ def user_data(request, user_id, questionpaper_id=None, course_id=None): return my_render_to_response(request, 'yaksh/user_data.html', context) -def _expand_questions(questions, field_list): - i = field_list.index('questions') - field_list.remove('questions') - for question in questions: - field_list.insert( - i, 'Q-{0}-{1}-{2}-marks'.format(question.id, question.summary, - question.points)) - field_list.insert( - i+1, 'Q-{0}-{1}-comments'.format(question.id, question.summary)) - return field_list - - @login_required @email_verified def download_quiz_csv(request, course_id, quiz_id): @@ -1836,84 +1827,43 @@ def download_quiz_csv(request, course_id, quiz_id): if not is_moderator(current_user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, id=course_id) - quiz = get_object_or_404(Quiz, id=quiz_id) if not course.is_creator(current_user) and \ not course.is_teacher(current_user): raise Http404('The quiz does not belong to your course') - users = course.get_enrolled().order_by('first_name') - if not users: - return monitor(request, quiz_id) - csv_fields = [] - attempt_number = None + quiz = get_object_or_404(Quiz, id=quiz_id) question_paper = quiz.questionpaper_set.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) - if not csv_fields: - csv_fields = CSV_FIELDS - if not attempt_number: - attempt_number = last_attempt_number - - questions = question_paper.get_question_bank() - answerpapers = AnswerPaper.objects.filter( - question_paper=question_paper, - attempt_number=attempt_number, course_id=course_id) - if not answerpapers: + attempt_number = request.POST.get('attempt_number') + questions = question_paper.get_question_bank() + answerpapers = AnswerPaper.objects.select_related( + "user").select_related('question_paper').prefetch_related( + 'answers').filter( + course_id=course_id, question_paper_id=question_paper.id, + attempt_number=attempt_number + ).order_by("user__first_name") + que_summaries = [ + (f"{que.summary}-{que.points}-marks", que.id) for que in questions + ] + user_data = list(answerpapers.values( + "user__first_name", "user__last_name", + "user__profile__roll_number", "user__profile__institute", + "user__profile__department", "marks_obtained", + "question_paper__total_marks", "percent", "status" + )) + for idx, ap in enumerate(answerpapers): + que_data = ap.get_per_question_score(que_summaries) + user_data[idx].update(que_data) + df = pd.DataFrame(user_data) + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = \ + 'attachment; filename="{0}-{1}-attempt-{2}.csv"'.format( + course.name.replace(' ', '_'), + quiz.description.replace(' ', '_'), attempt_number + ) + df.to_csv(response, index=False) + return response + else: return monitor(request, quiz_id, course_id) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = \ - 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format( - course.name.replace('.', ''), quiz.description.replace('.', ''), - attempt_number) - writer = csv.writer(response) - if 'questions' in csv_fields: - csv_fields = _expand_questions(questions, csv_fields) - writer.writerow(csv_fields) - - csv_fields_values = { - 'name': 'user.get_full_name().title()', - 'roll_number': 'user.profile.roll_number', - 'institute': 'user.profile.institute', - 'department': 'user.profile.department', - 'username': 'user.username', - 'marks_obtained': 'answerpaper.marks_obtained', - 'out_of': 'question_paper.total_marks', - 'percentage': 'answerpaper.percent', - 'status': 'answerpaper.status'} - questions_scores = {} - for question in questions: - questions_scores['Q-{0}-{1}-{2}-marks'.format( - question.id, question.summary, question.points)] \ - = 'answerpaper.get_per_question_score({0})'.format(question.id) - answer = question.answer_set.last() - comment = None - if answer: - comment = answer.comment - else: - comment = '' - questions_scores['Q-{0}-{1}-comments'.format( - question.id, question.summary)] \ - = 'answerpaper.get_answer_comment({0})'.format(question.id) - csv_fields_values.update(questions_scores) - - users = users.exclude(id=course.creator.id).exclude( - id__in=course.teachers.all()) - for user in users: - row = [] - answerpaper = None - papers = answerpapers.filter(user=user) - if papers: - answerpaper = papers.first() - for field in csv_fields: - try: - row.append(eval(csv_fields_values[field])) - except AttributeError: - row.append('-') - writer.writerow(row) - return response @login_required @@ -2295,40 +2245,36 @@ 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 = Course.objects.prefetch_related("learning_module").get( - id=course_id) + course = get_object_or_404( + Course.objects.prefetch_related("learning_module"), 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( + raise Http404('You are not allowed to view this course') + students = list(course.get_only_students().annotate( roll_number=F('profile__roll_number'), institute=F('profile__institute') ).values( - "id", "first_name", "last_name", - "email", "institute", "roll_number" - ) - quizzes = course.get_quizzes() - + "id", "first_name", "last_name", "email", "institute", "roll_number" + )) + que_pprs = [ + quiz.questionpaper_set.values( + "id", "quiz__description", "total_marks")[0] + for quiz in course.get_quizzes() + ] + total_course_marks = sum([qp.get("total_marks", 0) for qp in que_pprs]) + qp_ids = [ + (qp.get("id"), qp.get("quiz__description"), qp.get("total_marks")) + for qp in que_pprs + ] 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"], course_id) - user_course_marks += quiz_best_marks - total_course_marks += quiz.questionpaper_set.values_list( - "total_marks", flat=True)[0] - student["{}".format(quiz.description)] = quiz_best_marks - student["total_scored"] = user_course_marks + AnswerPaper.objects.get_user_scores(qp_ids, student, course_id) student["out_of"] = total_course_marks + df = pd.DataFrame(students) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{0}.csv"'.format( - (course.name).lower().replace('.', '')) - header = ['first_name', 'last_name', "roll_number", "email", "institute"]\ - + [quiz.description for quiz in quizzes] + ['total_scored', 'out_of'] - writer = csv.DictWriter(response, fieldnames=header, extrasaction='ignore') - writer.writeheader() - for student in students: - writer.writerow(student) + (course.name).lower().replace(' ', '_')) + output_file = df.to_csv(response, index=False) return response @@ -3257,7 +3203,7 @@ def course_status(request, course_id): stud_details = [(student, course.get_grade(student), course.get_completion_percent(student), - course.get_current_unit(student)) + course.get_current_unit(student.id)) for student in students.object_list] context = { 'course': course, 'objects': students, 'is_progress': True, @@ -3440,19 +3386,22 @@ def download_course_progress(request, 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 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] + students = course.students.order_by("-id").values_list("id") + stud_details = list(CourseStatus.objects.filter( + course_id=course_id, user_id__in=students + ).values( + "user_id", "user__first_name", "user__last_name", + "grade", "percent_completed" + )) + for student in stud_details: + student["current_unit"] = course.get_current_unit( + student.pop("user_id") + ) + df = pd.DataFrame(stud_details) 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) + df.to_csv(response, index=False) return response |