diff options
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/middleware/get_notifications.py | 21 | ||||
-rw-r--r-- | yaksh/models.py | 16 | ||||
-rw-r--r-- | yaksh/tasks.py | 82 | ||||
-rw-r--r-- | yaksh/templates/manage.html | 30 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_forum.html | 52 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_students.html | 154 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 109 | ||||
-rw-r--r-- | yaksh/templates/yaksh/monitor.html | 366 | ||||
-rw-r--r-- | yaksh/templates/yaksh/paginator.html | 6 | ||||
-rw-r--r-- | yaksh/templates/yaksh/post_comments.html | 7 | ||||
-rw-r--r-- | yaksh/templates/yaksh/regrade.html | 175 | ||||
-rw-r--r-- | yaksh/templates/yaksh/user_data.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/view_notifications.html | 62 | ||||
-rw-r--r-- | yaksh/test_views.py | 1053 | ||||
-rw-r--r-- | yaksh/urls.py | 60 | ||||
-rw-r--r-- | yaksh/views.py | 250 |
16 files changed, 1733 insertions, 715 deletions
diff --git a/yaksh/middleware/get_notifications.py b/yaksh/middleware/get_notifications.py new file mode 100644 index 0000000..d211ad3 --- /dev/null +++ b/yaksh/middleware/get_notifications.py @@ -0,0 +1,21 @@ +from notifications_plugin.models import Notification + +class NotificationMiddleware(object): + """ Middleware to get user's notifications """ + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Code to be executed for each request before + # the view (and later middleware) are called. + user = request.user + if user.is_authenticated: + notifications = Notification.objects.get_unread_receiver_notifications( + user.id + ).count() + request.custom_notifications = notifications + response = self.get_response(request) + + # Code to be executed for each request/response after + # the view is called. + return response diff --git a/yaksh/models.py b/yaksh/models.py index 69894a2..7d4dd98 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -774,14 +774,20 @@ class LearningModule(models.Model): def get_passing_status(self, user, course): course_status = CourseStatus.objects.filter(user=user, course=course) + ordered_units = [] if course_status.exists(): - learning_units_with_quiz = self.learning_unit.filter(type='quiz') + learning_units_with_quiz = self.learning_unit.filter( + type='quiz' + ).order_by("order") ordered_units = learning_units_with_quiz.order_by("order") - statuses = [ - unit.quiz.get_answerpaper_passing_status(user, course) - for unit in ordered_units - ] + if ordered_units: + statuses = [ + unit.quiz.get_answerpaper_passing_status(user, course) + for unit in ordered_units + ] + else: + statuses = [] if not statuses: status = False diff --git a/yaksh/tasks.py b/yaksh/tasks.py new file mode 100644 index 0000000..1c4658b --- /dev/null +++ b/yaksh/tasks.py @@ -0,0 +1,82 @@ +# Python Imports +from __future__ import absolute_import, unicode_literals +from textwrap import dedent + +# Django and celery imports +from celery import shared_task +from django.urls import reverse +from django.shortcuts import get_object_or_404 + +# Local imports +from .models import Course, QuestionPaper, Quiz, AnswerPaper, CourseStatus +from notifications_plugin.models import NotificationMessage, Notification + + +@shared_task +def regrade_papers(data): + question_id = data.get("question_id") + questionpaper_id = data.get("questionpaper_id") + answerpaper_id = data.get("answerpaper_id") + course_id = data.get("course_id") + user_id = data.get("user_id") + quiz_id = data.get("quiz_id") + quiz_name = data.get("quiz_name") + course_name = data.get("course_name") + + url = reverse("yaksh:grade_user", args=[quiz_id, course_id]) + + try: + if answerpaper_id is not None and question_id is None: + # Regrade specific user for all questions + answerpaper = AnswerPaper.objects.get(id=answerpaper_id) + url = reverse("yaksh:grade_user", + args=[quiz_id, answerpaper.user_id, course_id]) + for question in answerpaper.questions.all(): + answerpaper.regrade(question.id) + course_status = CourseStatus.objects.filter( + user=answerpaper.user, course=answerpaper.course) + if course_status.exists(): + course_status.first().set_grade() + + elif answerpaper_id is not None and question_id is not None: + # Regrade specific user for a specific question + answerpaper = AnswerPaper.objects.get(pk=answerpaper_id) + url = reverse("yaksh:grade_user", + args=[quiz_id, answerpaper.user_id, course_id]) + answerpaper.regrade(question_id) + course_status = CourseStatus.objects.filter( + user=answerpaper.user, course=answerpaper.course) + if course_status.exists(): + course_status.first().set_grade() + + elif questionpaper_id is not None and question_id is not None: + # Regrade all users for a specific question + answerpapers = AnswerPaper.objects.filter( + questions=question_id, + question_paper_id=questionpaper_id, course_id=course_id) + for answerpaper in answerpapers: + answerpaper.regrade(question_id) + course_status = CourseStatus.objects.filter( + user=answerpaper.user, course=answerpaper.course) + if course_status.exists(): + course_status.first().set_grade() + + message = dedent(""" + Quiz re-evaluation is complete. + Click <a href="{0}">here</a> to view + """.format(url) + ) + notification_type = "success" + except Exception as e: + message = dedent(""" + Unable to regrade please try again. + Click <a href="{0}">here</a> to view""".format(url) + ) + notification_type = "warning" + nm = NotificationMessage.objects.add_single_message( + user_id, "{0} re-evaluation status".format(quiz_name), + message, notification_type + ) + notification = Notification.objects.add_single_notification( + user_id, nm.id + ) diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html index 8e74494..6047fc5 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -12,13 +12,33 @@ <div class="collapse navbar-collapse" id="navbarColor01"> <ul class="navbar-nav mr-auto"> - <li class="nav-item"><a class="nav-link" href="{% url 'yaksh:show_questions' %}">Questions</a></li> - <li class="nav-item"><a class="nav-link" href="{% url 'yaksh:courses' %}">Courses</a></li> - <li class="nav-item"><a class="nav-link" href="{% url 'yaksh:monitor' %}">Monitor</a></li> - <li class="nav-item"><a class="nav-link" href="{% url 'yaksh:grade_user' %}">Grade User</a></li> - <li class="nav-item"><a class="nav-link" href="{% url 'yaksh:grader' %}"> Regrade </a></li> + <li class="nav-item"> + <a class="nav-link" href="{% url 'yaksh:show_questions' %}"> + Questions + </a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{% url 'yaksh:courses' %}"> + Courses + </a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{% url 'yaksh:grade_user' %}"> + Quizzes + </a> + </li> </ul> <ul class="navbar-nav ml-auto"> + <li class="nav-item"> + <a class="nav-link" href="{% url 'yaksh:view_notifications' %}"> + <i class="fa fa-bell" style="size: 18px"></i> Notifications + {% if request.custom_notifications > 0 %} + <span class="badge badge-success badge-pill"> + {{request.custom_notifications}} + </span> + {% endif %} + </a> + </li> <li class="nav-item dropdown my-lg-0" style="font-size: 1.2rem"> <a class="dropdown-toggle nav-link" id="user_dropdown" data-toggle="dropdown" href="#">{{user.get_full_name|title}} </a> diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html index e6b6a90..4724981 100644 --- a/yaksh/templates/yaksh/course_forum.html +++ b/yaksh/templates/yaksh/course_forum.html @@ -12,23 +12,19 @@ <div class="d-flex p-2 bd-highlight"> <div class="col-md-4"> {% if moderator %} - <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary">Back to Course</a> + <a href="{% url 'yaksh:course_detail' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> {% else %} - <a href="{% url 'yaksh:course_modules' course.id %}" class="btn btn-primary">Back to Course</a> + <a href="{% url 'yaksh:course_modules' course.id %}" class="btn btn-primary"> + <i class="fa fa-arrow-left"></i> Back + </a> {% endif %} </div> - <div class="col-md-4"> - <form class="my-2 my-lg-0" action="" method="GET"> - <div class="input-group"> - <input type="search" placeholder="Search" name="search" class="form-control"> - <span class="input-group-append"> - <button class="btn btn-outline-info" type="submit"><i class="fa fa-search"></i> Search</button> - </span> - </div> - </form> - </div> - <div class="col-md-4"> - <button type="button" class="btn btn-primary pull-right" data-toggle="modal" data-target="#newPostModal">New Post</button> + <div class="col-md"> + <button type="button" class="btn btn-success pull-right" data-toggle="modal" data-target="#newPostModal"> + <i class="fa fa-plus-circle"></i> New Post + </button> </div> </div> <!-- Modal --> @@ -58,12 +54,34 @@ </div> </div> <br> + <div class="row justify-content-center"> + <div class="col-md-6"> + <form class="my-2 my-lg-0" action="" method="GET"> + <div class="input-group"> + <input type="search" placeholder="Search Post" name="search_post" class="form-control"> + <span class="input-group-append"> + <button class="btn btn-outline-info"> + <i class="fa fa-search"></i> Search + </button> + </span> + </div> + </form> + </div> + <div class="col-md-4"> + <a class="btn btn-outline-danger" href="{% url 'yaksh:course_forum' course.id %}"> + <i class="fa fa-times"></i> Clear Search + </a> + </div> + </div> <br> + {% with objects as posts %} {% if posts %} + {% include "yaksh/paginator.html" %} + <br> <table id="posts_table" class="tablesorter table"> <thead class="thread-inverse"> <tr> - <th width="700">Questions</th> + <th width="700">Posts</th> <th>Created by</th> <th>Replies</th> <th>Last reply</th> @@ -98,10 +116,12 @@ {% endfor %} </tbody> </table> + <br> + {% include "yaksh/paginator.html" %} {% else %} No discussion posts are there yet. Create one to start discussing. {% endif %} - {% include "yaksh/paginator.html" %} + {% endwith %} </div> {% endblock content %} {% block script %} diff --git a/yaksh/templates/yaksh/course_students.html b/yaksh/templates/yaksh/course_students.html index 2052a69..e8ca800 100644 --- a/yaksh/templates/yaksh/course_students.html +++ b/yaksh/templates/yaksh/course_students.html @@ -3,7 +3,7 @@ <div id="accordian-upload" class="card"> <div class="card-header"> <a class="card-link" data-toggle="collapse" href="#upload_users_csv"> - Upload Users <i class="fa fa-angle-down"></i> + Upload Users <i class="fa fa-angle-down"></i> </a> </div> </div> @@ -48,60 +48,59 @@ <div id="accordian-request" class="card"> <div class="card-header"> <a class="card-link" data-toggle="collapse" href="#requested"> - Requested Students <i class="fa fa-angle-down"></i> + Requested Students <i class="fa fa-angle-down"></i> </a> </div> </div> <div id="requested" class="collapse show" data-parent="#accordion-request"> - {% if requested %} + {% if requested_users %} <br> - <input type="checkbox" class="checkall"/> - <font size="5">Select all</font> + <input type="checkbox" class="checkall"/> Select all <div id="enroll-all"> - <form action="{% url 'yaksh:enroll_users' course.id %}" method="post"> + <form action="{% url 'yaksh:enroll_reject_user' course.id %}" method="post"> {% csrf_token %} <table id="requested_table" class="tablesorter table table-striped table-responsive-sm course-detail" data-sortlist="[1,0]"> <thead> <th></th> - <th>Full Name <i class="fa fa-sort"></i></th> - <th>Email <i class="fa fa-sort"></i></th> - <th>Roll Number <i class="fa fa-sort"></i></th> - <th>Institute <i class="fa fa-sort"></i></th> - <th>Department <i class="fa fa-sort"></i></th> + <th>Full Name <i class="fa fa-sort"></i></th> + <th>Email <i class="fa fa-sort"></i></th> + <th>Roll Number <i class="fa fa-sort"></i></th> + <th>Institute <i class="fa fa-sort"></i></th> + <th>Department <i class="fa fa-sort"></i></th> <th>Enroll/Reject</th> </thead> <tbody> - {% for request in requested %} + {% for user in requested_users %} <tr> <td> {{ forloop.counter }}. - <input type="checkbox" name="check" value="{{ request.id }}"> + <input type="checkbox" name="check" value="{{ user.id }}"> </td> - <td>{{request.get_full_name}}</td> - <td> {{request.email}}</td> - {% with request.profile as request_profile %} - <td> {{request_profile.roll_number}}</td> - <td> {{request_profile.institute}}</td> - <td> {{request_profile.department}}</td> + <td>{{user.get_full_name}}</td> + <td> {{user.email}}</td> + {% with user.profile as user_profile %} + <td> {{user_profile.roll_number}}</td> + <td> {{user_profile.institute}}</td> + <td> {{user_profile.department}}</td> {% endwith %} <td> - <a class="btn btn-success" - href="{% url 'yaksh:enroll_user' course.id request.id %}"> - <i class="fa fa-plus-square"></i> - Enroll </a> - <a class="btn btn-danger" - href="{% url 'yaksh:reject_user' course.id request.id %}"> - <i class="fa fa-minus-square"></i> - Reject </a> + <a class="btn btn-success" href="{% url 'yaksh:enroll_user' course.id user.id %}"> + <i class="fa fa-plus-square"></i> + Enroll + </a> + <a class="btn btn-danger" href="{% url 'yaksh:reject_user' course.id user.id %}"> + <i class="fa fa-minus-square"></i> + Reject + </a> </td> </tr> {% endfor %} </tbody> </table> - <button class="btn btn-success btn-lg" type="submit" name='enroll' value='enroll'> - <i class="fa fa-plus-square"></i> - Enroll Selected - </button> <br> + <input class="btn btn-success btn-lg" type="submit" name='enroll' value='enroll selected'> + </input> + <input class="btn btn-danger btn-lg" type="submit" name='reject' value='reject selected'> + </input> </form> </div> {% else %} @@ -116,45 +115,43 @@ <div id="accordian-enrolled" class="card"> <div class="card-header"> <a class="card-link" data-toggle="collapse" href="#enrolled"> - Enrolled Students <i class="fa fa-angle-down"></i> + Enrolled Students <i class="fa fa-angle-down"></i> </a> </div> </div> <div id="enrolled" class="collapse hide" data-parent="#accordion-enrolled"> - {% if enrolled %} + {% if enrolled_users %} <br> - <input type="checkbox" class="reject"/> - <font size="5">Select all</font> + <input type="checkbox" class="reject"/> Select all <div id="reject"> - <form action="{% url 'yaksh:reject_users' course.id %}" method="post" id="reject-form"> + <form action="{% url 'yaksh:reject_enrolled_users' course.id %}" method="post" id="reject-form"> {% csrf_token %} <table id="enrolled_table" class="tablesorter table table-striped table-responsive-sm course-detail" data-sortlist="[1,0]" style="width: 100%"> <thead> <th></th> - <th>Full Name <i class="fa fa-sort"></i></th> - <th>Email <i class="fa fa-sort"></i></th> - <th>Roll Number <i class="fa fa-sort"></i></th> - <th>Institute <i class="fa fa-sort"></i></th> - <th>Department <i class="fa fa-sort"></i></th> + <th>Full Name <i class="fa fa-sort"></i></th> + <th>Email <i class="fa fa-sort"></i></th> + <th>Roll Number <i class="fa fa-sort"></i></th> + <th>Institute <i class="fa fa-sort"></i></th> + <th>Department <i class="fa fa-sort"></i></th> <th>Reject</th> </thead> <tbody> - {% for enroll in enrolled %} + {% for user in enrolled_users %} <tr> <td> {{ forloop.counter }}. - <input type="checkbox" name="check" value="{{ enroll.id }}"> + <input type="checkbox" name="check" value="{{ user.id }}"> </td> - <td> {{ enroll.get_full_name|title }} </td> - <td> {{enroll.email}}</td> - {% with enroll.profile as enroll_profile %} - <td> {{enroll_profile.roll_number}}</td> - <td> {{enroll_profile.institute}}</td> - <td> {{enroll_profile.department}}</td> + <td> {{ user.get_full_name|title }} </td> + <td> {{user.email}}</td> + {% with user.profile as user_profile %} + <td> {{user_profile.roll_number}}</td> + <td> {{user_profile.institute}}</td> + <td> {{user_profile.department}}</td> {% endwith %} <td> - <a class="btn btn-danger" - href="{% url 'yaksh:reject_user' course.id enroll.id %}"> + <a class="btn btn-danger" href="{% url 'yaksh:reject_enrolled_user' course.id user.id %}"> <i class="fa fa-minus-square"></i> Reject </a> @@ -163,10 +160,8 @@ {% endfor %} </tbody> </table> - <button class="btn btn-danger btn-lg" type="submit" name='reject' value='reject'> - <i class="fa fa-minus-square"></i> - Reject Selected - </button> <br> + <input class="btn btn-danger btn-lg" type="submit" name='reject' value='reject selected'> + </input> </form> </div> {% else %} @@ -182,56 +177,53 @@ <div id="accordian-rejected" class="card"> <div class="card-header"> <a class="card-link" data-toggle="collapse" href="#rejected"> - Rejected Students <i class="fa fa-angle-down"></i> + Rejected Students <i class="fa fa-angle-down"></i> </a> </div> </div> <div id="rejected" class="collapse hide" data-parent="#accordion-rejected"> - {% if rejected %} + {% if rejected_users %} <br> - <input type="checkbox" class="enroll"/> - <font size="5">Select all</font> + <input type="checkbox" class="enroll"/> Select all <div id="enroll"> - <form action="{% url 'yaksh:enroll_rejected' course.id %}" method="post"> + <form action="{% url 'yaksh:enroll_rejected_users' course.id %}" method="post"> {% csrf_token %} <table id="rejected_table" class="tablesorter table table-striped table-responsive-sm course-detail" data-sortlist="[1,0]"> <thead> - <th>Full Name <i class="fa fa-sort"></i></th> - <th>Email <i class="fa fa-sort"></i></th> - <th>Roll Number <i class="fa fa-sort"></i></th> - <th>Institute <i class="fa fa-sort"></i></th> - <th>Department <i class="fa fa-sort"></i></th> + <th>Full Name <i class="fa fa-sort"></i></th> + <th>Email <i class="fa fa-sort"></i></th> + <th>Roll Number <i class="fa fa-sort"></i></th> + <th>Institute <i class="fa fa-sort"></i></th> + <th>Department <i class="fa fa-sort"></i></th> <th>Enroll</th> </thead> <tbody> - {% for reject in rejected %} + {% for user in rejected_users %} <tr> <td> {{ forloop.counter }}. - <input type="checkbox" name="check" value="{{ reject.id }}"> + <input type="checkbox" name="check" value="{{ user.id }}"> </td> - <td>{{reject.get_full_name|title}}</td> - <td> {{reject.email}}</td> - {% with reject.profile as reject_profile %} - <td> {{reject_profile.roll_number}}</td> - <td> {{reject_profile.institute}}</td> - <td> {{reject_profile.department}}</td> + <td>{{user.get_full_name|title}}</td> + <td> {{user.email}}</td> + {% with user.profile as user_profile %} + <td> {{user_profile.roll_number}}</td> + <td> {{user_profile.institute}}</td> + <td> {{user_profile.department}}</td> {% endwith %} <td> - <a class="btn btn-success" - href="{% url 'yaksh:enroll_rejected' course.id reject.id %}"> - <i class="fa fa-plus-square"></i> - Enroll </a> + <a class="btn btn-success" href="{% url 'yaksh:enroll_rejected_user' course.id user.id %}"> + <i class="fa fa-plus-square"></i> + Enroll + </a> </td> </tr> {% endfor %} </tbody> </table> <br> - <button class="btn btn-success btn-lg" type="submit" name='enroll' value='enroll'> - <i class="fa fa-plus-square"></i> - Enroll Selected - </button> + <input class="btn btn-success btn-lg" type="submit" name='enroll' value='enroll selected'> + </input> <br> </form> </div> diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index f4c7d67..a9ab53e 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 %} Grade User {% endblock %} +{% block title %} Grader {% endblock %} -{% block pagetitle %} Grade User {% endblock pagetitle %} +{% block pagetitle %} Grader {% endblock pagetitle %} {% block script %} <script type="text/javascript" src="{% static 'yaksh/js/jquery.tablesorter.min.js' %}"> @@ -51,7 +51,7 @@ function searchNames() { </h5> </div> <div class="col-md"> - <a class="card-link btn btn-info" data-toggle="collapse" href="#collapse{{course.id}}"> + <a class="card-link btn btn-outline-success" data-toggle="collapse" href="#collapse{{course.id}}"> Details <i class="fa fa-toggle-down" id="toggle_course_{{course.id}}"></i> </a> @@ -70,7 +70,12 @@ function searchNames() { {{quiz.description}} </div> <div class="col-md-2"> - <a href="{% url 'yaksh:grade_user' quiz.id course.id%}" class="btn btn-primary"> + <a href="{% url 'yaksh:monitor' quiz.id course.id%}" class="btn btn-primary"> + Monitor + </a> + </div> + <div class="col-md-2"> + <a href="{% url 'yaksh:grade_user' quiz.id course.id%}" class="btn btn-info"> Grade </a> </div> @@ -135,6 +140,46 @@ function searchNames() { </a> <br><br> {% endif %} + {% if status == 'grade' and users %} + <div id="accordian-questions" class="card"> + <div class="card-header"> + <a class="card-link" data-toggle="collapse" href="#questions"> + Regrade papers by questions <i class="fa fa-angle-down"></i> + </a> + </div> + <div id="questions" class="collapse hide" data-parent="#accordion-questions"> + {% with quiz.questionpaper_set.get as qp %} + <div class="card-body"> + <table class="table table-responsive-sm"> + <thead> + <tr> + <th>Questions</th> + <th>Type</th> + <th>Marks</th> + <th>Regrade</th> + </tr> + </thead> + <tbody> + {% for question in qp.get_question_bank %} + <tr> + <td>{{ question.summary }}</td> + <td>{{ question.get_type_display }}</td> + <td>{{ question.points }}</td> + <td> + <a href="{% url 'yaksh:regrade_by_quiz' course_id quiz.questionpaper_set.get.id question.id %}" class="btn btn-outline-success"> + <i class="fa fa-repeat"></i> Regrade + </a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endwith %} + </div> + </div> + <br> + {% endif %} {% if messages %} {% for message in messages %} @@ -251,7 +296,16 @@ function searchNames() { {% if paper.answers.count %} <div class="card"> <div class="card-header"> - Submission Details + <div class="row"> + <div class="col-md-6"> + Submission Details + </div> + <div class="col-md-4"> + <a href="{% url 'yaksh:regrade_by_user' course_id quiz.questionpaper_set.get.id paper.id %}" class="btn btn-outline-success"> + <i class="fa fa-repeat"></i> Regrade Whole Paper + </a> + </div> + </div> </div> <div class="card-body"> <table class="tablesorter table table-striped table-bordered table-responsive-sm" id='marks_table'> @@ -260,6 +314,7 @@ function searchNames() { <th>Questions</th> <th>Type</th> <th>Marks Obtained</th> + <th>Regrade by question</th> </tr> </thead> <tbody> @@ -273,6 +328,11 @@ function searchNames() { </td> <td>{{ question.type }}</td> <td>{{ answer.answer.marks }}</td> + <td> + <a href="{% url 'yaksh:regrade_by_question' course_id quiz.questionpaper_set.get.id paper.id question.id %}" class="btn btn-outline-success"> + <i class="fa fa-repeat"></i> Regrade + </a> + </td> </tr> {% endwith %} {% endfor %} @@ -287,7 +347,8 @@ function searchNames() { <div class="card-header text-white bg-info"> <strong> Details: {{forloop.counter}}. {{ question.summary }} - <span class="marks pull-right"> Mark(s): {{ question.points }} </span> + <span class="marks pull-right"> Mark(s): {{ question.points }} + </span> </strong> </div> <div class="card-body"> @@ -533,4 +594,40 @@ function searchNames() { </div> </div> </div> +{% if details %} + <div class="modal"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">Regrade</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <table class="table table-responsive-sm"> + <tbody> + {% for detail in details %} + {% if detail.0 %} + <tr class="table-success"> + <td> Graded Successfully </td> + {% else%} + <tr class="table-danger"> + <td> Did not Grade </td> + {% endif %} + <td> {{ detail.1|linebreaks }} </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal"> + Close + </button> + </div> + </div> + </div> + </div> +{% endif %} {% endblock%} diff --git a/yaksh/templates/yaksh/monitor.html b/yaksh/templates/yaksh/monitor.html index cf6888c..ef7b033 100644 --- a/yaksh/templates/yaksh/monitor.html +++ b/yaksh/templates/yaksh/monitor.html @@ -12,228 +12,176 @@ </script> <script type="text/javascript"> $(document).ready(function() - { - $("#result-table").tablesorter({sortList: [[5,1]]}); - var papers_length = "{{papers|length}}"; - for (var i=0; i < papers_length; i++){ - var time_left = $("#time_left"+[i]); + { + $("#result-table").tablesorter({}); + var papers_length = "{{papers|length}}"; + for (var i=0; i < papers_length; i++){ + var paper_status = $("#status"+[i]); + var hh, mm, ss; + var time_left = $("#time_left"+[i]); + if (paper_status.text() == "completed"){ + hh = "-"; + mm = "-"; + ss = "-"; + } + else{ var time = time_left.text(); - var hh = Math.floor(time / 3600); - var mm = Math.floor((time - (hh * 3600)) / 60); - var ss = time - (hh * 3600) - (mm * 60); - time_left.text(hh + ":" + mm + ":" + ss) - } -}); + hh = Math.floor(time / 3600); + mm = Math.floor((time - (hh * 3600)) / 60); + ss = time - (hh * 3600) - (mm * 60); + } + time_left.text(hh + ":" + mm + ":" + ss); + } + }); </script> {% endif %} {% endblock %} {% block content %} <div class="container"> - {# ############################################################### #} - {# This is rendered when we are just viewing exam/monitor #} - {% if objects %} - {% include "yaksh/paginator.html" %} - <div id="accordion"> - {% for course in objects %} - <div class="card"> - <div class="card-header"> - <div class="row"> - <div class="col-md-9"> - <h5 data-toggle="tooltip" title="{{course.name}}"> - {{ course.name }} - </h5> - </div> - <div class="col-md"> - <a class="card-link btn btn-info" data-toggle="collapse" href="#collapse{{course.id}}"> - Details - <i class="fa fa-toggle-down" id="toggle_course_{{course.id}}"></i> - </a> - </div> - </div> - </div> - <div id="collapse{{course.id}}" class="collapse hide" data-parent="#accordion"> - <div class="card-body"> - {% with course.get_quizzes as quizzes %} - {% if quizzes %} - <ul class="list-group"> - {% for quiz in quizzes %} - <li class="list-group-item"> - <div class="row"> - <div class="col-md-8"> - {{quiz.description}} - </div> - <div class="col-md-2"> - <a href="{% url 'yaksh:monitor' quiz.id course.id%}" class="btn btn-primary"> - Monitor - </a> - </div> - </div> - </li> - {% endfor %} - </ul> - {% else %} - <center> - <p class="badge badge-danger badge-pill"> - No Quizzes - </p> - </center> - {% endif %} - {% endwith %} - </div> - </div> - </div> - <br> - {% endfor %} - </div> - {% include "yaksh/paginator.html" %} - - {% elif msg == 'Monitor' and not objects %} - <br> - <div class="alert alert-info"> - <center><h3>No courses to monitor</h3></center> - </div> - {% endif %} - {# ############################################################### #} - {# This is rendered when we are just viewing exam/monitor/quiz_num #} - {% if msg != "Monitor" %} - {% if quiz %} - {% if papers %} - <div class="card"> - <div class="table-responsive"> - <table id="course-detail" class="table"> - <tr> - <td><b>Course Name: </b></td> - <td>{{course.name}}</td> - </tr> - <tr> - <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> + {% if quiz %} + {% if papers %} + <div class="card"> + <div class="table-responsive"> + <table id="course-detail" class="table"> + <tr> + <td><b>Course Name: </b></td> + <td>{{course.name}}</td> + </tr> + <tr> + <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> - <br> - <div class="row"> - <div class="col-md-4"> - <a href="{% url 'yaksh:show_statistics' papers.0.question_paper.id course.id %}" class="btn btn-primary"> - <i class="fa fa-line-chart"></i> Question Statistics - </a> - </div> - <div class="col-md-4"> - <button type="button" class="btn btn-info" data-toggle="modal" data-target="#csvModal"> - <i class="fa fa-download"></i> Download CSV - </button> - </div> + </div> + <br> + <div class="row"> + <div class="col-md-4"> + <a href="{% url 'yaksh:show_statistics' papers.0.question_paper.id course.id %}" class="btn btn-primary"> + <i class="fa fa-line-chart"></i> Question Statistics + </a> + </div> + <div class="col-md-4"> + <button type="button" class="btn btn-info" data-toggle="modal" data-target="#csvModal"> + <i class="fa fa-download"></i> Download CSV + </button> + </div> + <div class="col-md-4"> + <div class="badge badge-info"> + Auto-Refreshes every 30 seconds </div> - <br> - <table id="result-table" class="tablesorter table table-striped table-responsive-sm"> - <thead> - <tr> - <th> Name <i class="fa fa-sort"></i> </th> - <th> Username <i class="fa fa-sort"></i> </th> - <th> Roll No <i class="fa fa-sort"></i> </th> - <th> Institute <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> Time <i class="fa fa-sort"></i> </th> - <th> Status <i class="fa fa-sort"></i> </th> - </tr> - </thead> - <tbody> - {% for paper in latest_attempts %} - <tr> - <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.username }} </td> - <td> {{ paper.user.profile.roll_number }} </td> - <td> {{ paper.user.profile.institute }} </td> - <td> {{ paper.marks_obtained }} </td> - <td> {{ paper.answers.count }} </td> - <td id="time_left{{forloop.counter0}}"> {{ paper.time_left }} </td> - <td>{{ paper.status }}</td> - </tr> - {% endfor %} - </tbody> - </table> - <!-- CSV Modal --> - <div class="modal fade" id="csvModal" role="dialog"> - <div class="modal-dialog"> + </div> + </div> + <br> + <table id="result-table" class="tablesorter table table-striped table-responsive-sm"> + <thead> + <tr> + <th> Sr No. </th> + <th> Name <i class="fa fa-sort"></i> </th> + <th> Username <i class="fa fa-sort"></i> </th> + <th> Roll No <i class="fa fa-sort"></i> </th> + <th> Institute <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> Time <i class="fa fa-sort"></i> </th> + <th> Status <i class="fa fa-sort"></i> </th> + </tr> + </thead> + <tbody> + {% for paper in latest_attempts %} + <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.username }} </td> + <td> {{ paper.user.profile.roll_number }} </td> + <td> {{ paper.user.profile.institute }} </td> + <td> {{ paper.marks_obtained }} </td> + <td> {{ paper.answers.count }} </td> + <td id="time_left{{forloop.counter0}}"> {{ paper.time_left }} </td> + <td id="status{{forloop.counter0}}">{{ paper.status }}</td> + </tr> + {% endfor %} + </tbody> + </table> + <!-- CSV Modal --> + <div class="modal fade" id="csvModal" role="dialog"> + <div class="modal-dialog"> - <!-- Modal content--> - <div class="modal-content"> - <div class="modal-header"> - <h3 class="modal-title">Download CSV for {{quiz.description}} </h3> - <button type="button" class="close" data-dismiss="modal"> - <i class="fa fa-close"></i> - </button> + <!-- Modal content--> + <div class="modal-content"> + <div class="modal-header"> + <h3 class="modal-title">Download CSV for {{quiz.description}} </h3> + <button type="button" class="close" data-dismiss="modal"> + <i class="fa fa-close"></i> + </button> + </div> + <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"> + <input class="form-check-input" name="csv_fields" type="checkbox" value="{{ field }}" checked> {{ field }} + </label> </div> - <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"> - <input class="form-check-input" name="csv_fields" type="checkbox" value="{{ field }}" checked> {{ field }} - </label> - </div> - <br> + <br> + {% endfor %} + <b>Select Attempt Number: Default latest attempt</b> + <select class="form-control" name = "attempt_number"> + {%for attempt_number in attempt_numbers %} + {% if forloop.last %} + <option value="{{ attempt_number }}" selected>{{ attempt_number }} (Latest)</option> + {% else %} + <option value = "{{ attempt_number }}"> {{ attempt_number }}</option> + {% endif %} {% endfor %} - <b>Select Attempt Number: Default latest attempt</b> - <select class="form-control" name = "attempt_number"> - {%for attempt_number in attempt_numbers %} - {% if forloop.last %} - <option value="{{ attempt_number }}" selected>{{ attempt_number }} (Latest)</option> - {% else %} - <option value = "{{ attempt_number }}"> {{ attempt_number }}</option> - {% endif %} - {% endfor %} - </select> - </div> - <div class="modal-footer"> - <button type="submit" class="btn btn-success"> - <span class="fa fa-save"></span> Download - </button> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - Close - </button> - </div> - </form> - </div> - </div> + </select> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-success"> + <span class="fa fa-save"></span> Download + </button> + <button type="button" class="btn btn-secondary" data-dismiss="modal"> + Close + </button> + </div> + </form> </div> - {% else %} - <div class="col-md-12"> - <div class="alert alert-warning"> - <center> - <h4>No Users Found for {{ quiz.description }}</h4> - </center> - </div> </div> - {% endif %} {# if papers #} - {% else %} - <h4>No Quiz Found</h4> - {% endif %} - {% endif %} + </div> + {% else %} + <div class="col-md-12"> + <div class="alert alert-warning"> + <center> + <h4>No Users Found for {{ quiz.description }}</h4> + </center> + </div> + </div> + {% endif %} {# if papers #} + {% else %} + <h4>No Quiz Found</h4> + {% endif %} </div> {% endblock %} diff --git a/yaksh/templates/yaksh/paginator.html b/yaksh/templates/yaksh/paginator.html index e958519..e18cbce 100644 --- a/yaksh/templates/yaksh/paginator.html +++ b/yaksh/templates/yaksh/paginator.html @@ -2,7 +2,7 @@ {% if objects.has_previous %} <li class="page-item"> <a class="page-link" href="?page=1{% if request.GET.question_type %}&question_type={{ request.GET.question_type }}{% endif %}{% if request.GET.language %}&language={{ request.GET.language }}{% endif %}{% if request.GET.marks %}&marks={{ request.GET.marks }}{% endif %}{% if request.GET.question_tags %}&question_tags={{ request.GET.question_tags }}{% endif %} - {% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}" aria-label="Previous"> + {% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}{% if request.GET.search_post %}&search_post={{ request.GET.search_post }}{% endif %}" aria-label="Previous"> <span aria-hidden="true"> <i class="fa fa-angle-double-left"></i> </span> @@ -17,13 +17,13 @@ <span class="page-link">{{ n }}<span class="sr-only">(current)</span></span> </li> {% elif n > objects.number|add:'-5' and n < objects.number|add:'5' %} - <li class="page-item"><a class="page-link" href="?page={{ n }}{% if request.GET.question_type %}&question_type={{ request.GET.question_type }}{% endif %}{% if request.GET.language %}&language={{ request.GET.language }}{% endif %}{% if request.GET.marks %}&marks={{ request.GET.marks }}{% endif %}{% if request.GET.question_tags %}&question_tags={{ request.GET.question_tags }}{% endif %}{% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}">{{ n }}</a></li> + <li class="page-item"><a class="page-link" href="?page={{ n }}{% if request.GET.question_type %}&question_type={{ request.GET.question_type }}{% endif %}{% if request.GET.language %}&language={{ request.GET.language }}{% endif %}{% if request.GET.marks %}&marks={{ request.GET.marks }}{% endif %}{% if request.GET.question_tags %}&question_tags={{ request.GET.question_tags }}{% endif %}{% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}{% if request.GET.search_post %}&search_post={{ request.GET.search_post }}{% endif %}">{{ n }}</a></li> {% endif %} {% endfor %} {% if objects.has_next %} <li class="page-item"> - <a class="page-link" href="?page={{ objects.paginator.num_pages }}{% if request.GET.question_type %}&question_type={{ request.GET.question_type }}{% endif %}{% if request.GET.language %}&language={{ request.GET.language }}{% endif %}{% if request.GET.marks %}&marks={{ request.GET.marks }}{% endif %}{% if request.GET.question_tags %}&question_tags={{ request.GET.question_tags }}{% endif %}{% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}" aria-label="Next"> + <a class="page-link" href="?page={{ objects.paginator.num_pages }}{% if request.GET.question_type %}&question_type={{ request.GET.question_type }}{% endif %}{% if request.GET.language %}&language={{ request.GET.language }}{% endif %}{% if request.GET.marks %}&marks={{ request.GET.marks }}{% endif %}{% if request.GET.question_tags %}&question_tags={{ request.GET.question_tags }}{% endif %}{% if request.GET.search_tags %}&search_tags={{ request.GET.search_tags }}{% endif %}{% if request.GET.search_status %}&search_status={{ request.GET.search_status }}{% endif %}{% if request.GET.search_post %}&search_post={{ request.GET.search_post }}{% endif %}" aria-label="Next"> <span aria-hidden="true"> <i class="fa fa-angle-double-right"></i> </span> diff --git a/yaksh/templates/yaksh/post_comments.html b/yaksh/templates/yaksh/post_comments.html index 463103e..b16b80c 100644 --- a/yaksh/templates/yaksh/post_comments.html +++ b/yaksh/templates/yaksh/post_comments.html @@ -6,7 +6,9 @@ {% block content %} <div class="container"> - <a class="btn btn-primary" href="{% url 'yaksh:course_forum' post.course.id %}">Back to Posts</a> + <a class="btn btn-primary" href="{% url 'yaksh:course_forum' post.course.id %}"> + <i class="fa fa-arrow-left"></i> Back to Posts + </a> <br> <br> <div class="card mb-2 border-dark"> @@ -56,12 +58,13 @@ {% endif %} <br> <div> + <b><u>Add comment:</u></b> <form action="{% url 'yaksh:post_comments' post.course.id post.uid %}" method="POST" enctype='multipart/form-data'> <div class="form-group"> {% csrf_token %} {{form}} </div> - <input type="submit" value="Submit" class="btn btn-primary"> + <input type="submit" value="Submit" class="btn btn-success"> </form> </div> </div> diff --git a/yaksh/templates/yaksh/regrade.html b/yaksh/templates/yaksh/regrade.html deleted file mode 100644 index c70e470..0000000 --- a/yaksh/templates/yaksh/regrade.html +++ /dev/null @@ -1,175 +0,0 @@ -{% extends "manage.html" %} - -{% block pagetitle %} Grader {% endblock pagetitle %} - -{% block content %} -<div class="yakshwell container"> -<div class="row"> - <div class="col-md-3 yakshlabel collapse" id="sidebar"> - <div class="nav nav-pills flex-column" role="tablist" aria-orientation="vertical"> - <a href="#intro" data-toggle="pill" class="nav-link active" role="tab" aria-controls="intro" aria-selected="true" id="introtab"> Intro </a> - <a href="#questions" data-toggle="pill" class="nav-link" role="tab" aria-controls="questions" aria-selected="false" id="questionstab"> Question-wise regrade </a> - <a href="#quizzes" data-toggle="pill" class="nav-link" role="tab" aria-controls="quizzes" aria-selected="false" id="quizzestab"> Quiz-wise regrade </a> - <a href="#users" data-toggle="pill" class="nav-link" role="tab" aria-controls="users" aria-selected="false" id="userstab"> User-wise regrade </a> - </div> - </div><!--end of siddebar--> - <a href="#sidebar" data-toggle="collapse" id="sidebaricon"><i class="fa fa-navicon fa-lg"></i></a> - <main class="tab-content col" id="sidebarbody"> - - - <div id="intro" class="tab-pane fade show active" role="tabpanel" aria-labelledby="introtab"> - <h3 class="yakshred"> Regrade </h3> - <dl> - <dt class="yakshgreen"> Question wise regrade </dt> - <dd> You can regrade a question for all answerpapers for a given quiz. </dd> - <dt class="yakshgreen"> Quiz wise regrade <dt> - <dd> You can regrade an answerpaper for a quiz or a question for the same. </dd> - <dt class="yakshgreen"> User wise regrade </dt> - <dd> You can regrade an answerpaper for an user or a question for the same. </dd> - </dl> - </div> - <div id="questions" class="tab-pane fade" role="tabpanel" aria-labelledby="questionstab"> - <div class="card"> - {% for course in courses %} - - <div class="card-body"> - <h4><span class=""> - <a href="#questions_quizzes{{ course.id }}" data-toggle="collapse" class="btn btn-outline-success">Course: {{ course }}</a> - </span></h4> - <div id="questions_quizzes{{ course.id }}" class="collapse card"> - <div class="card-body"> - {% for quiz in course.get_quizzes %} - <p><a href="#questions_questions{{ course.id }}{{ quiz.id }}" data-toggle="collapse" class="btn btn-outline-info">Quiz: {{ quiz }}</a></p> - <div id="questions_questions{{ course.id }}{{ quiz.id }}" class="collapse card"> - {% with questionpaper=quiz.questionpaper_set.get %} - <p class="text-center yakshred h5 bg-light"> Questions: </p> - <ol class="list-group yakshwell"> - {% for question in questionpaper.fixed_questions.all %} - <li class="list-group-item">{{ question.summary }} - <a href="{{ URL_ROOT }}/exam/manage/regrade/questionpaper/{{ course.id }}/{{ question.id }}/{{ questionpaper.id }}/" - class="btn btn-success pull-right"><span class="fa fa-repeat"></span> Regrade </a> - </li> - {% endfor %} - {% for random_set in questionpaper.random_questions.all %} - {% for question in random_set.questions.all %} - <li class="list-group-item"> {{ question.summary }} - <a href="{{ URL_ROOT }}/exam/manage/regrade/questionpaper/{{ course.id}}/{{ question.id }}/{{ questionpaper.id }}/" - class="btn btn-success pull-right"><span class="fa fa-repeat"></span> Regrade </a> - </li> - {% endfor %} - {% endfor %} - </ol> - {% endwith %}<br /><br /> - </div> - {% endfor %} - </div> - </div> - </div> - - {% endfor %} - </div><!--card--> - </div> - - <div id="quizzes" class="tab-pane fade" role="tabpanel" aria-labelledby="quizzestab"> - <div class="card"> - {% for course in courses %} - - <div class="card-body"> - <h4><span class=""> - <span class=""><a href="#quizzes_quizzes{{ course.id }}" data-toggle="collapse" class="btn btn-outline-success">Course: {{ course }}</a></span> - </span></h4> - <div id="quizzes_quizzes{{ course.id }}" class="collapse card"> - <div class="card-body"> - {% for quiz in course.get_quizzes %} - <p><a href="#quizzes_papers{{ course.id }}{{ quiz.id }}" data-toggle="collapse" class="btn btn-outline-info">Quiz: {{ quiz }}</a></p> - <div id="quizzes_papers{{ course.id }}{{ quiz.id }}" class="collapse"> - <ol class="list-group yakshwell"> - {% for answerpaper in quiz.questionpaper_set.get.answerpaper_set.all %} - <li class="list-group-item bg-light"> - Username: {{ answerpaper.user.username }}; Name: {{ answerpaper.user.get_full_name }}; Attempt Number: {{ answerpaper.attempt_number}} - <a href="{{ URL_ROOT }}/exam/manage/regrade/paper/{{ course.id }}/{{ answerpaper.id }}/" - class="btn btn-success btn-sm pull-right"><span class="fa fa-repeat"></span> Regrade whole paper </a> - </li> - <ol class="list-group yakshwell"> - {% for question in answerpaper.questions.all %} - <li class="list-group-item"> {{ question.summary }} - <a href="{{ URL_ROOT }}/exam/manage/regrade/answerpaper/{{ course.id }}/{{ question.id }}/{{ answerpaper.id }}/" - class="btn btn-success btn-sm pull-right"><span class="fa fa-repeat"></span> Regrade </a> - </li> - {% endfor %} - </ol> - {% endfor %} - </ol> - </div> - {% endfor %} - </div> - </div> - </div> - - {% endfor %} - </div><!--card--> - </div> - - <div id="users" class="tab-pane fade" role="tabpanel" aria-labelledby="userstab"> - <div class="card"> - <div class="card-body"> - {% for course in courses %} - - <h4><span class=""> - <a href="#users_users{{ course.id }}" data-toggle="collapse" class="btn btn-outline-success">Course: {{ course }}</a> - </span></h4> - <div id="users_users{{ course.id }}" class="collapse card"> - <div class="card-bodys"> - {% for user in course.students.all %} - <p><a href="#users_papers{{ course.id }}{{ user.id }}" data-toggle="collapse" class="btn btn-outline-info"> Answer Papers for {{ user.get_full_name }}</a></p> - <div id="users_papers{{ course.id }}{{ user.id }}" class="collapse card"> - <ol class="list-group yakshwell"> - {% for answerpaper in user.answerpaper_set.all %} - <li class="list-group-item bg-light"> Quiz: {{answerpaper.question_paper.quiz.description }}; Attempt Number: {{ answerpaper.attempt_number }} - <a href="{{ URL_ROOT }}/exam/manage/regrade/paper/{{ course.id }}/{{ answerpaper.id }}/" - class="btn btn-success pull-right" ><span class="fa fa-repeat"></span> Regrade whole paper </a> - </li> - <ol class="list-group yakshwell"> - {% for question in answerpaper.questions.all %} - <li class="list-group-item"> {{ question.summary }} - <a href="{{ URL_ROOT }}/exam/manage/regrade/answerpaper/{{ course.id }}/{{ question.id }}/{{ answerpaper.id }}/" - class="btn btn-success pull-right"><span class="fa fa-repeat"></span> Regrade </a> - </li> - {% endfor %} - </ol> - {% endfor %} - </ol> - </div> - {% endfor %} - </div> - </div> - - {% endfor %} -</div> - </div><!--well--> - </div> - - </main><!--span10--> -</div><!--row--> - -{% if details %} -<div> - <table class="table table-responsive-sm"> - <tbody> - {% for detail in details %} - {% if detail.0 %} - <tr class="table-success"> - <td> Graded Successfully </td> - {% else%} - <tr class="table-danger"> - <td> Did not Grade </td> - {% endif %} - <td> {{ detail.1|linebreaks }} </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - {% endif %} -</div> -{% endblock %} diff --git a/yaksh/templates/yaksh/user_data.html b/yaksh/templates/yaksh/user_data.html index 6547851..6252fb3 100644 --- a/yaksh/templates/yaksh/user_data.html +++ b/yaksh/templates/yaksh/user_data.html @@ -33,11 +33,6 @@ </div> </div> {% endwith %} - <br> - <a href="{% url 'yaksh:grade_user' data.papers.0.question_paper.quiz.id data.user.id course_id %}" class="btn btn-info"> - Grade User - </a> - <br> {% for paper in data.papers %} <br> <h3><b><u>Attempt Number:</u></b> <span class="badge badge-pill badge-info"> diff --git a/yaksh/templates/yaksh/view_notifications.html b/yaksh/templates/yaksh/view_notifications.html new file mode 100644 index 0000000..48193ed --- /dev/null +++ b/yaksh/templates/yaksh/view_notifications.html @@ -0,0 +1,62 @@ +{% extends template %} +{% block title %} Notifications {% endblock %} +{% block pagetitle %} Notifications {% endblock %} + +{% block main %} + <div class="container"> + {% 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 notifications %} + <form method="post" action="{% url 'yaksh:mark_notification' %}"> + {% csrf_token %} + <button href="{% url 'yaksh:mark_notification' %}" class="btn btn-outline-success"> + <i class="fa fa-check"></i> Mark all as read + </button> + <br><br> + <div class="row"> + {% for notification in notifications %} + <div class="col-md-4"> + <div class="toast show" role="alert" aria-live="assertive" aria-atomic="true"> + {% with notification.message as message %} + <input type="hidden" name="uid" value="{{message.uid}}"> + <div class="toast-header bg-{{message.message_type}}"> + <strong class="mr-auto text-white"> + {{message.summary}} + </strong> + <small class="text-white"> + {{notification.timestamp|timesince:current_date_time}} + </small> + <a href="{% url 'yaksh:mark_notification' message.uid %}" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span class="fa fa-check" aria-hidden="true" title="Mark as read"></span> + </a> + </div> + <div class="toast-body"> + {% if user.id != message.creator.id %} + <p><b>From: </b>{{message.creator.get_full_name}}</p> + {% endif %} + <p><b>Description:</b></p> + {{message.description|safe}} + </div> + {% endwith %} + </div> + <br> + </div> + {% endfor %} + </div> + </form> + {% else %} + <br> + <div class="alert alert-info"> + <center>No Notifications Found</center> + </div> + {% endif %} + </div> +{% endblock %}
\ No newline at end of file diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 94b81ad..1418ee0 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -2,6 +2,7 @@ from datetime import datetime import pytz import os import json +import time try: from StringIO import StringIO as string_io except ImportError: @@ -21,6 +22,8 @@ from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files import File from django.contrib.messages import get_messages +from celery.contrib.testing.worker import start_worker +from django.test import SimpleTestCase from yaksh.models import ( @@ -32,6 +35,9 @@ from yaksh.models import ( from yaksh.views import add_as_moderator, course_forum, post_comments from yaksh.forms import PostForm, CommentForm from yaksh.decorators import user_has_profile +from online_test.celery import app + +from notifications_plugin.models import Notification class TestUserRegistration(TestCase): @@ -576,9 +582,9 @@ class TestMonitor(TestCase): ) self.assertEqual(response.status_code, 404) - def test_monitor_display_quizzes(self): + def test_monitor_quiz_not_found(self): """ - Check all the available quizzes in monitor + Check if quiz is not found """ self.client.login( username=self.user.username, @@ -587,10 +593,7 @@ class TestMonitor(TestCase): response = self.client.get(reverse('yaksh:monitor'), follow=True ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "yaksh/monitor.html") - self.assertEqual(response.context['objects'][0], self.course) - self.assertEqual(response.context['msg'], "Monitor") + self.assertEqual(response.status_code, 404) def test_monitor_display_quiz_results(self): """ @@ -2632,10 +2635,19 @@ class TestCourseDetail(TestCase): name="Python Course", enrollment="Enroll Request", creator=self.user1 ) + self.user1_othercourse = Course.objects.create( name="Python Course II", enrollment="Enroll Request", creator=self.user1 ) + + self.user1_deactive_course = Course.objects.create( + name="Python Course II", + enrollment="Enroll Request", + creator=self.user1, + end_enroll_time=timezone.now() + ) + self.learning_module = LearningModule.objects.create( name="test module", description="test description module", html_data="test html description module", creator=self.user1, @@ -2974,15 +2986,74 @@ class TestCourseDetail(TestCase): username=self.user1.username, password=self.user1_plaintext_pass ) - response = self.client.post( - reverse('yaksh:enroll_users', - kwargs={'course_id': self.user1_course.id}), - data={'check': self.student1.id} - ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'check': self.student1.id, + 'enroll': 'enroll' + } + response = self.client.post(url, data) enrolled_student = self.user1_course.students.all() self.assertEqual(response.status_code, 302) self.assertSequenceEqual([self.student1], enrolled_student) + def test_student_course_enroll_post_without_enroll_in_request(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'check': self.student1.id, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, 302) + + def test_student_course_enroll_post_without_enroll_ids(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'enroll': 'enroll' + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, 302) + + def test_student_course_reject_post_without_reject_in_request(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'check': self.student1.id, + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, 302) + + def test_student_course_reject_post_without_reject_ids(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'reject': 'reject' + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, 302) + def test_student_course_reject_get(self): """ Reject student in a course using get request @@ -3008,15 +3079,123 @@ class TestCourseDetail(TestCase): username=self.user1.username, password=self.user1_plaintext_pass ) - response = self.client.post( - reverse('yaksh:reject_users', - kwargs={'course_id': self.user1_course.id}), - data={'check': self.student1.id} - ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id + }) + data = { + 'check': self.student1.id, + 'reject': 'reject' + } + response = self.client.post(url, data) enrolled_student = self.user1_course.rejected.all() self.assertEqual(response.status_code, 302) self.assertSequenceEqual([self.student1], enrolled_student) + def test_enroll_user_not_moderator(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + url = reverse('yaksh:enroll_user', kwargs={ + 'course_id': self.user1_course.id, + 'user_id': self.user1.id + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_enroll_user_in_expired_course(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_user', kwargs={ + 'course_id': self.user1_deactive_course.id, + 'user_id': self.student.id + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + + def test_enroll_user_where_moderator_is_neither_creator_nor_teacher(self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + url = reverse('yaksh:enroll_user', kwargs={ + 'course_id': self.user1_course.id, + 'user_id': self.user1.id + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_reject_user_not_moderator(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + url = reverse('yaksh:reject_user', kwargs={ + 'course_id': self.user1_course.id, + 'user_id': self.user1.id + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_reject_user_where_moderator_is_neither_creator_nor_teacher(self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + url = reverse('yaksh:reject_user', kwargs={ + 'course_id': self.user1_course.id, + 'user_id': self.user1.id + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_enroll_reject_user_not_moderator(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id, + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_enroll_reject_user_in_deactivated_course(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_deactive_course.id, + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + + def test_enroll_reject_user_where_moderator_is_neither_creator_nor_teacher( + self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id, + }) + response = self.client.post(url) + self.assertEqual(response.status_code, 404) + + def test_get_enroll_reject_user_view(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:enroll_reject_user', kwargs={ + 'course_id': self.user1_course.id, + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + def test_toggle_course_status_get(self): self.client.login( username=self.user1.username, @@ -3210,6 +3389,170 @@ class TestCourseDetail(TestCase): self.assertIn("Per Module Progress", data) +class TestCourseStudents(TestCase): + def setUp(self): + self.client = Client() + self.mod_group = Group.objects.create(name='moderator') + + # Create Moderator 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='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC', + is_moderator=True + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC', + is_moderator=True + ) + + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + self.student1_plaintext_pass = 'demo_student' + self.student1 = User.objects.create_user( + username='demo_student1', + password=self.student1_plaintext_pass, + first_name='student1_first_name', + last_name='student1_last_name', + email='demo_student1@test.com' + ) + + self.student2_plaintext_pass = 'demo_student' + self.student2 = User.objects.create_user( + username='demo_student2', + password=self.student2_plaintext_pass, + first_name='student2_first_name', + last_name='student2_last_name', + email='demo_student2@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.user1_course = Course.objects.create( + name="Python Course", + enrollment="Enroll Request", creator=self.user1 + ) + + self.user1_course.enroll(False, self.student) + self.user1_course.reject(False, self.student1) + self.user1_course.request(self.student2) + + def test_enrolled_users(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id + }) + response = self.client.get(url) + enrolled_users = self.user1_course.get_enrolled() + self.assertTrue(enrolled_users.exists()) + + def test_requested_users(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id + }) + response = self.client.get(url) + requested_users = self.user1_course.get_requests() + self.assertTrue(requested_users.exists()) + + def test_rejected_users(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id + }) + response = self.client.get(url) + rejected_users = self.user1_course.get_rejected() + self.assertTrue(rejected_users.exists()) + + def test_course_students_context(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id + }) + response = self.client.get(url) + self.assertTrue('enrolled_users' in response.context) + self.assertTrue('requested_users' in response.context) + self.assertTrue('rejected_users' in response.context) + + def test_course_students_where_moderator_is_neither_creator_nor_teacher( + self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id, + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_course_students_where_user_is_not_moderator(self): + self.client.login( + username=self.student1, + password=self.student1_plaintext_pass + ) + url = reverse('yaksh:course_students', kwargs={ + 'course_id': self.user1_course.id, + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def tearDown(self): + self.user1.delete() + self.user2.delete() + self.student.delete() + self.student1.delete() + self.student2.delete() + self.user1_course.delete() + + class TestEnrollRequest(TestCase): def setUp(self): self.client = Client() @@ -3595,11 +3938,13 @@ class TestSelfEnroll(TestCase): self.assertRedirects(response, '/exam/manage/') -class TestGrader(TestCase): +class TestGrader(SimpleTestCase): + allow_database_queries = True + def setUp(self): self.client = Client() - self.mod_group = Group.objects.create(name='moderator') + self.mod_group, created = Group.objects.get_or_create(name='moderator') # Create Moderator with profile self.user1_plaintext_pass = 'demo1' @@ -3679,6 +4024,9 @@ class TestGrader(TestCase): end_time=timezone.now()+timezone.timedelta(minutes=20), ) + self.celery_worker = start_worker(app) + self.celery_worker.__enter__() + def tearDown(self): User.objects.all().delete() Course.objects.all().delete() @@ -3687,42 +4035,22 @@ class TestGrader(TestCase): QuestionPaper.objects.all().delete() AnswerPaper.objects.all().delete() self.mod_group.delete() - - def test_grader_denies_anonymous(self): - # Given - redirect_destination = ('/exam/login/?next=/exam/manage/grader/') - - # When - response = self.client.get(reverse('yaksh:grader'), follow=True) - - # Then - self.assertRedirects(response, redirect_destination) - - def test_grader_denies_students(self): - # Given - self.client.login( - username=self.student.username, - password=self.student_plaintext_pass - ) - - # When - response = self.client.get(reverse('yaksh:grader'), follow=True) - - # Then - self.assertEqual(response.status_code, 404) + self.celery_worker.__exit__(None, None, None) def test_regrade_denies_anonymous(self): # Given - url = "/exam/login/?next=/exam/manage/regrade/answerpaper" + url = "/exam/login/?next=/exam/manage/regrade/user/question" redirect_destination = ( - url + "/{}/{}/{}/".format( - self.course.id, self.question.id, self.answerpaper.id) + url + "/{}/{}/{}/{}/".format( + self.course.id, self.question_paper.id, + self.answerpaper.id, self.question.id) ) # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_question', kwargs={'course_id': self.course.id, + 'questionpaper_id': self.question_paper.id, 'question_id': self.question.id, 'answerpaper_id': self.answerpaper.id}), follow=True @@ -3740,8 +4068,9 @@ class TestGrader(TestCase): # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_question', kwargs={'course_id': self.course.id, + 'questionpaper_id': self.question_paper.id, 'question_id': self.question.id, 'answerpaper_id': self.answerpaper.id}), follow=True @@ -3750,21 +4079,6 @@ class TestGrader(TestCase): # Then self.assertEqual(response.status_code, 404) - def test_grader_by_moderator(self): - # Given - self.client.login( - username=self.user1.username, - password=self.user1_plaintext_pass - ) - - # When - response = self.client.get(reverse('yaksh:grader'), follow=True) - - # Then - self.assertEqual(response.status_code, 200) - self.assertTrue('courses' in response.context) - self.assertTemplateUsed(response, 'yaksh/regrade.html') - def test_regrade_by_moderator(self): # Given self.client.login( @@ -3774,44 +4088,46 @@ class TestGrader(TestCase): # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_question', kwargs={'course_id': self.course.id, + 'questionpaper_id': self.question_paper.id, 'question_id': self.question.id, 'answerpaper_id': self.answerpaper.id}), follow=True) # Then + messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(response.status_code, 200) - self.assertTrue('courses' in response.context) - self.assertTrue('details' in response.context) - self.assertTemplateUsed(response, 'yaksh/regrade.html') + self.assertIn("demo quiz is submitted for re-evaluation", messages[0]) # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_user', kwargs={'course_id': self.course.id, + 'questionpaper_id': self.question_paper.id, 'answerpaper_id': self.answerpaper.id}), follow=True) # Then + messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(response.status_code, 200) - self.assertTrue('courses' in response.context) - self.assertTrue('details' in response.context) - self.assertTemplateUsed(response, 'yaksh/regrade.html') + self.assertIn("demo quiz is submitted for re-evaluation", messages[0]) # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_quiz', kwargs={'course_id': self.course.id, 'question_id': self.question.id, 'questionpaper_id': self.question_paper.id}), follow=True) # Then + messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(response.status_code, 200) - self.assertTrue('courses' in response.context) - self.assertTrue('details' in response.context) - self.assertTemplateUsed(response, 'yaksh/regrade.html') + self.assertIn("demo quiz is submitted for re-evaluation", messages[0]) + self.assertEqual(Notification.objects.get_receiver_notifications( + self.user1.id + ).count(), 3) def test_regrade_denies_moderator_not_in_course(self): # Given @@ -3823,8 +4139,9 @@ class TestGrader(TestCase): self.mod_group.user_set.remove(self.user2) # When response = self.client.get( - reverse('yaksh:regrade', + reverse('yaksh:regrade_by_question', kwargs={'course_id': self.course.id, + 'questionpaper_id': self.question_paper.id, 'question_id': self.question.id, 'answerpaper_id': self.answerpaper.id}), follow=True) @@ -6402,7 +6719,7 @@ class TestPost(TestCase): self.assertContains(response, 'csrfmiddlewaretoken') def test_view_course_forum_denies_anonymous_user(self): - url = reverse('yaksh:course_forum', kwargs= { + url = reverse('yaksh:course_forum', kwargs={ 'course_id': self.course.id }) response = self.client.get(url, follow=True) @@ -6464,7 +6781,6 @@ class TestPost(TestCase): }) self.assertContains(response, 'href="{0}'.format(post_comments_url)) - def test_new_post_valid_post_data(self): self.client.login( username=self.student.username, @@ -6812,3 +7128,588 @@ class TestPostComment(TestCase): self.user.delete() self.course.delete() self.mod_group.delete() + + +class TestStartExam(TestCase): + def setUp(self): + self.client = Client() + self.mod_group = Group.objects.create(name='moderator') + tzone = pytz.timezone('UTC') + + # Create Moderator with profile + self.user1_plaintext_pass = 'demo' + self.user1 = User.objects.create_user( + username='demo_user', + password=self.user1_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com', + ) + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC', + is_moderator=True + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + Profile.objects.create( + user=self.student, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + # Create courses for user1 + self.user1_course1 = Course.objects.create( + name="Demo Course", + enrollment="Enroll Request", creator=self.user1 + ) + # course1 status + self.course1_status = CourseStatus.objects.create( + course=self.user1_course1, user=self.user1 + ) + + # Create learning modules for user1 + self.learning_module1 = LearningModule.objects.create( + order=1, name="Demo Module", description="Demo Module", + check_prerequisite=False, creator=self.user1 + ) + self.learning_module2 = LearningModule.objects.create( + order=2, name="Demo Module 2", description="Demo Module 2", + check_prerequisite=False, creator=self.user1 + ) + + self.quiz1 = Quiz.objects.create( + time_between_attempts=0, description='Demo Quiz', + creator=self.user1 + ) + + self.quiz2 = Quiz.objects.create( + time_between_attempts=0, description='Demo Quiz 2', + creator=self.user1 + ) + + self.question_paper1 = QuestionPaper.objects.create( + quiz=self.quiz1, total_marks=1.0 + ) + + self.question_paper2 = QuestionPaper.objects.create( + quiz=self.quiz2, total_marks=1.0 + ) + + self.question1 = Question.objects.create( + summary="Test_question 1", description="Add two numbers", + points=1.0, language="python", type="code", user=self.user1 + ) + + user_answer = "def add(a, b)\n\treturn a+b" + self.new_answer = Answer.objects.create( + question=self.question1, answer=user_answer, + correct=True, error=json.dumps([]) + ) + + self.answerpaper = AnswerPaper.objects.create( + user=self.student, question_paper=self.question_paper1, + attempt_number=1, + start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), + end_time=datetime(2020, 10, 9, 10, 15, 15, 0, tzone), + user_ip="127.0.0.1", status="inprogress", passed=True, + percent=1, marks_obtained=1, course=self.user1_course1 + ) + + self.answerpaper.answers.add(self.new_answer) + self.answerpaper.questions_answered.add(self.question1) + + # 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 Lession", + creator=self.user1 + ) + + # Create units for lesson and quiz + self.lesson_unit1 = LearningUnit.objects.create( + order=1, type="lesson", lesson=self.lesson1 + ) + self.quiz_unit1 = LearningUnit.objects.create( + order=2, type="quiz", quiz=self.quiz1 + ) + self.lesson_unit2 = LearningUnit.objects.create( + order=1, type="lesson", lesson=self.lesson2 + ) + 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) + + # Add module to course + self.user1_course1.learning_module.add(self.learning_module1) + self.user1_course1.learning_module.add(self.learning_module2) + + def test_start_question_paper_does_not_exists_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': 99, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_start_question_paper_does_not_exists_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': 99, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_question_paper_has_no_question_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_start_question_paper_has_no_question_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_has_no_active_learning_module_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = False + self.learning_module1.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_start_learning_module_has_prerequisite_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + self.learning_module2.active = True + self.learning_module2.check_prerequisite = True + self.learning_module2.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_learning_module_prerequisite_passes_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = True + self.learning_module2.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_user_enrolled_in_the_course_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_user_enrolled_in_the_course_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.is_trial = True + self.user1_course1.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_course_is_active_and_not_expired_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.active = False + self.user1_course1.end_enroll_time = timezone.now() + self.user1_course1.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_course_is_active_and_not_expired_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.user1) + self.user1_course1.active = False + self.user1_course1.end_enroll_time = timezone.now() + self.user1_course1.is_trial = True + self.user1_course1.save() + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_quiz_is_active_and_is_not_expired_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.save() + + self.question_paper1.quiz.end_date_time = timezone.now() + self.question_paper1.quiz.active = False + self.question_paper1.quiz.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_quiz_is_active_and_is_not_expired_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.user1) + self.user1_course1.is_trial = True + self.user1_course1.save() + + self.question_paper1.quiz.end_date_time = timezone.now() + self.question_paper1.quiz.active = False + self.question_paper1.quiz.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_prereq_check_and_pass_criteria_for_quiz_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = False + self.learning_module2.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.is_trial = True + self.user1_course1.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_prereq_check_and_pass_criteria_for_quiz_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = False + self.learning_module2.save() + + self.user1_course1.students.add(self.user1) + self.user1_course1.is_trial = True + self.user1_course1.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_not_allowed_to_start_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.is_trial = True + self.user1_course1.save() + + learning_unit = self.learning_module1.learning_unit.get( + quiz=self.question_paper1.quiz.id + ) + learning_unit.check_prerequisite = False + learning_unit.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper1.id, + 'module_id': self.learning_module1.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_not_allowed_to_start_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper1.fixed_questions.add(self.question1) + + self.learning_module1.active = True + self.learning_module1.check_prerequisite = False + self.learning_module1.check_prerequisite_passes = False + self.learning_module1.save() + + self.user1_course1.students.add(self.user1) + self.user1_course1.is_trial = True + self.user1_course1.save() + + learning_unit = self.learning_module1.learning_unit.get( + quiz=self.question_paper1.quiz.id + ) + learning_unit.check_prerequisite = False + learning_unit.save() + + def test_start_allowed_to_start_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = False + self.learning_module2.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.is_trial = True + self.user1_course1.save() + + learning_unit = self.learning_module2.learning_unit.get( + quiz=self.question_paper2.quiz.id + ) + learning_unit.check_prerequisite = False + learning_unit.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_allowed_to_start_for_moderator(self): + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = False + self.learning_module2.save() + + self.user1_course1.students.add(self.user1) + self.user1_course1.is_trial = True + self.user1_course1.save() + + learning_unit = self.learning_module2.learning_unit.get( + quiz=self.question_paper2.quiz.id + ) + learning_unit.check_prerequisite = False + learning_unit.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_start_allowed_to_start_when_quiz_is_exercise_for_user(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + self.question_paper2.fixed_questions.add(self.question1) + + self.learning_module2.active = True + self.learning_module2.check_prerequisite = False + self.learning_module2.check_prerequisite_passes = False + self.learning_module2.save() + + self.user1_course1.students.add(self.student) + self.user1_course1.is_trial = True + self.user1_course1.save() + + learning_unit = self.learning_module2.learning_unit.get( + quiz=self.question_paper2.quiz.id + ) + learning_unit.check_prerequisite = False + learning_unit.save() + + self.question_paper2.quiz.is_exercise = True + self.question_paper2.quiz.save() + + url = reverse('yaksh:start_quiz', kwargs={ + 'questionpaper_id': self.question_paper2.id, + 'module_id': self.learning_module2.id, + 'course_id': self.user1_course1.id + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.student.delete() + self.quiz1.delete() + self.user1_course1.delete() diff --git a/yaksh/urls.py b/yaksh/urls.py index 149e4d6..0639b25 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import url +from django.urls import path from yaksh import views urlpatterns = [ @@ -122,30 +123,38 @@ urlpatterns = [ name='edit_course'), url(r'manage/course_detail/(?P<course_id>\d+)/$', views.course_detail, name='course_detail'), - url(r'manage/enroll/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll, - name="enroll_user"), + + url(r'manage/enroll/(?P<course_id>\d+)/$', views.enroll_reject_user, + name="enroll_reject_user"), + url(r'manage/enroll/(?P<course_id>\d+)/(?P<user_id>\d+)/$', + views.enroll_user, name="enroll_user"), + url(r'manage/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', + views.reject_user, name="reject_user"), + url(r'manage/enrolled/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', + views.reject_user, {'was_enrolled': True}, + name="reject_enrolled_user"), + url(r'manage/enrolled/reject/(?P<course_id>\d+)/$', + views.enroll_reject_user, {'was_enrolled': True}, + name="reject_enrolled_users"), url(r'manage/enroll/rejected/(?P<course_id>\d+)/(?P<user_id>\d+)/$', - views.enroll, {'was_rejected': True}, name="enroll_rejected"), + views.enroll_user, {'was_rejected': True}, + name="enroll_rejected_user"), + url(r'manage/enroll/rejected/(?P<course_id>\d+)/$', + views.enroll_reject_user, {'was_rejected': True}, + name="enroll_rejected_users"), + url(r'manage/upload_users/(?P<course_id>\d+)/$', views.upload_users, name="upload_users"), url(r'manage/send_mail/(?P<course_id>\d+)/$', views.send_mail, name="send_mail"), - url(r'manage/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.reject, - name="reject_user"), - url(r'manage/enrolled/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', - views.reject, {'was_enrolled': True}, name="reject_user"), + url(r'manage/toggle_status/(?P<course_id>\d+)/$', views.toggle_course_status, name="toggle_course_status"), url(r'^questions/filter$', views.questions_filter, name="questions_filter"), url(r'^editprofile/$', views.edit_profile, name='edit_profile'), url(r'^viewprofile/$', views.view_profile, name='view_profile'), - url(r'^manage/enroll/(?P<course_id>\d+)/$', views.enroll, - name="enroll_users"), - url(r'manage/enroll/rejected/(?P<course_id>\d+)/$', - views.enroll, {'was_rejected': True}, name="enroll_rejected"), - url(r'manage/enrolled/reject/(?P<course_id>\d+)/$', - views.reject, {'was_enrolled': True}, name="reject_users"), + url(r'^manage/searchteacher/(?P<course_id>\d+)/$', views.search_teacher, name="search_teacher"), url(r'^manage/addteacher/(?P<course_id>\d+)/$', views.add_teacher, @@ -156,17 +165,14 @@ urlpatterns = [ name="download_questions"), url(r'^manage/upload_questions/$', views.show_all_questions, name="upload_questions"), - url(r'^manage/grader/$', views.grader, name='grader'), - url(r'^manage/regrade/question/(?P<course_id>\d+)/(?P<question_id>\d+)/$', - views.regrade, name='regrade'), - url(r'^manage/regrade/questionpaper/(?P<course_id>\d+)/' - '(?P<question_id>\d+)/(?P<questionpaper_id>\d+)/$', - views.regrade, name='regrade'), - url(r'^manage/regrade/answerpaper/(?P<course_id>\d+)/' - '(?P<question_id>\d+)/(?P<answerpaper_id>\d+)/$', - views.regrade, name='regrade'), - url(r'^manage/regrade/paper/(?P<course_id>\d+)/(?P<answerpaper_id>\d+)/$', - views.regrade, name='regrade'), + url(r'^manage/regrade/paper/question/(?P<course_id>\d+)/' + '(?P<questionpaper_id>\d+)/(?P<question_id>\d+)/$', + views.regrade, name='regrade_by_quiz'), + url(r'^manage/regrade/user/(?P<course_id>\d+)/(?P<questionpaper_id>\d+)/' + '(?P<answerpaper_id>\d+)/$', views.regrade, name='regrade_by_user'), + url(r'^manage/regrade/user/question/(?P<course_id>\d+)/' + '(?P<questionpaper_id>\d+)/(?P<answerpaper_id>\d+)/' + '(?P<question_id>\d+)/', views.regrade, name='regrade_by_question'), url(r'^manage/(?P<mode>godmode|usermode)/(?P<quiz_id>\d+)/' '(?P<course_id>\d+)/$', views.test_quiz, name="test_quiz"), url(r'^manage/create_demo_course/$', views.create_demo_course, @@ -225,4 +231,10 @@ urlpatterns = [ views.delete_question, name="delete_question"), url(r'^manage/search/questions', views.search_questions_by_tags, name="search_questions_by_tags"), + path('view/notifications', views.view_notifications, + name="view_notifications"), + path('mark/notifications/<uuid:message_uid>', + views.mark_notification, name="mark_notification"), + path('mark/notifications', views.mark_notification, + name="mark_notification"), ] diff --git a/yaksh/views.py b/yaksh/views.py index 397e7c8..e4a9038 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -52,6 +52,8 @@ from .file_utils import extract_files, is_csv from .send_emails import (send_user_mail, generate_activation_key, send_bulk_mail) from .decorators import email_verified, has_profile +from .tasks import regrade_papers +from notifications_plugin.models import Notification def my_redirect(url): @@ -439,6 +441,7 @@ def prof_manage(request, msg=None): courses = Course.objects.get_queryset().filter( Q(creator=user) | Q(teachers=user), is_trial=False).distinct().order_by("-active") + paginator = Paginator(courses, 20) page = request.GET.get('page') courses = paginator.get_page(page) @@ -1113,12 +1116,54 @@ def course_detail(request, course_id): @login_required @email_verified -def enroll(request, course_id, user_id=None, was_rejected=False): +def enroll_user(request, course_id, user_id=None, was_rejected=False): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') - course = get_object_or_404(Course, pk=course_id) + course = get_object_or_404(Course, id=course_id) + if not course.is_active_enrollment(): + msg = ( + 'Enrollment for this course has been closed,' + ' please contact your ' + 'instructor/administrator.' + ) + messages.warning(request, msg) + return redirect('yaksh:course_students', 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') + + user = User.objects.get(id=user_id) + course.enroll(was_rejected, user) + messages.success(request, 'Enrolled student successfully') + return redirect('yaksh:course_students', course_id=course_id) + + +@login_required +@email_verified +def reject_user(request, course_id, user_id=None, was_enrolled=False): + user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page') + 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') + user = User.objects.get(id=user_id) + course.reject(was_enrolled, user) + messages.success(request, "Rejected students successfully") + return redirect('yaksh:course_students', course_id=course_id) + + +@login_required +@email_verified +def enroll_reject_user(request, + course_id, was_enrolled=False, was_rejected=False): + user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page') + course = get_object_or_404(Course, id=course_id) + if not course.is_active_enrollment(): msg = ( 'Enrollment for this course has been closed,' @@ -1126,23 +1171,31 @@ def enroll(request, course_id, user_id=None, was_rejected=False): 'instructor/administrator.' ) messages.warning(request, msg) - return my_redirect(reverse('yaksh:course_students', args=[course_id])) + return redirect('yaksh:course_students', 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') if request.method == 'POST': - enroll_ids = request.POST.getlist('check') - else: - enroll_ids = [user_id] - if not enroll_ids: - messages.warning(request, "Please select atleast one student") - return my_redirect(reverse('yaksh:course_students', args=[course_id])) - - users = User.objects.filter(id__in=enroll_ids) - course.enroll(was_rejected, *users) - messages.success(request, "Enrolled student(s) successfully") - return my_redirect(reverse('yaksh:course_students', args=[course_id])) + if 'enroll' in request.POST: + enroll_ids = request.POST.getlist('check') + if not enroll_ids: + messages.warning(request, "Please select atleast one student") + return redirect('yaksh:course_students', course_id=course_id) + users = User.objects.filter(id__in=enroll_ids) + course.enroll(was_rejected, *users) + messages.success(request, "Enrolled student(s) successfully") + return redirect('yaksh:course_students', course_id=course_id) + if 'reject' in request.POST: + reject_ids = request.POST.getlist('check') + if not reject_ids: + messages.warning(request, "Please select atleast one student") + return redirect('yaksh:course_students', course_id=course_id) + users = User.objects.filter(id__in=reject_ids) + course.reject(was_enrolled, *users) + messages.success(request, "Rejected students successfully") + return redirect('yaksh:course_students', course_id=course_id) + return redirect('yaksh:course_students', course_id=course_id) @login_required @@ -1178,31 +1231,6 @@ def send_mail(request, course_id, user_id=None): @login_required @email_verified -def reject(request, course_id, user_id=None, was_enrolled=False): - 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') - - if request.method == 'POST': - reject_ids = request.POST.getlist('check') - else: - reject_ids = [user_id] - if not reject_ids: - messages.warning(request, "Please select atleast one student") - return my_redirect(reverse('yaksh:course_students', args=[course_id])) - - users = User.objects.filter(id__in=reject_ids) - course.reject(was_enrolled, *users) - messages.success(request, "Rejected students successfully") - return my_redirect(reverse('yaksh:course_students', args=[course_id])) - - -@login_required -@email_verified def toggle_course_status(request, course_id): user = request.user if not is_moderator(user): @@ -1267,18 +1295,6 @@ def monitor(request, quiz_id=None, course_id=None): if not is_moderator(user): raise Http404('You are not allowed to view this page!') - if quiz_id is None: - courses = Course.objects.filter( - Q(creator=user) | Q(teachers=user), - is_trial=False - ).order_by("-active").distinct() - paginator = Paginator(courses, 30) - page = request.GET.get('page') - courses = paginator.get_page(page) - context = { - "papers": [], "objects": courses, "msg": "Monitor" - } - return my_render_to_response(request, 'yaksh/monitor.html', context) # quiz_id is not None. try: quiz = get_object_or_404(Quiz, id=quiz_id) @@ -1801,7 +1817,7 @@ def download_quiz_csv(request, course_id, quiz_id): @login_required @email_verified def grade_user(request, quiz_id=None, user_id=None, attempt_number=None, - course_id=None): + course_id=None, extra_context=None): """Present an interface with which we can easily grade a user's papers and update all their marks and also give comments for each paper. """ @@ -1860,6 +1876,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None, context = { "data": data, "quiz_id": quiz_id, + "quiz": quiz, "users": user_details, "attempts": attempts, "user_id": user_id, @@ -1886,7 +1903,8 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None, course_id=course.id, user_id=user.id) if course_status.exists(): course_status.first().set_grade() - + if extra_context: + context.update(extra_context) return my_render_to_response(request, 'yaksh/grade_user.html', context) @@ -2142,56 +2160,31 @@ def create_demo_course(request): @login_required @email_verified -def grader(request, extra_context=None): - user = request.user - if not is_moderator(user): - raise Http404('You are not allowed to view this page!') - courses = Course.objects.filter(is_trial=False) - user_courses = list(courses.filter(creator=user)) + \ - list(courses.filter(teachers=user)) - context = {'courses': user_courses} - if extra_context: - context.update(extra_context) - return my_render_to_response(request, 'yaksh/regrade.html', context) - - -@login_required -@email_verified -def regrade(request, course_id, question_id=None, answerpaper_id=None, - questionpaper_id=None): +def regrade(request, course_id, questionpaper_id, question_id=None, + answerpaper_id=None): user = request.user course = get_object_or_404(Course, pk=course_id) if not is_moderator(user) or (course.is_creator(user) and course.is_teacher(user)): raise Http404('You are not allowed to view this page!') + questionpaper = get_object_or_404(QuestionPaper, pk=questionpaper_id) details = [] - if answerpaper_id is not None and question_id is None: - answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id) - for question in answerpaper.questions.all(): - details.append(answerpaper.regrade(question.id)) - course_status = CourseStatus.objects.filter( - user=answerpaper.user, course=answerpaper.course) - if course_status.exists(): - course_status.first().set_grade() - if questionpaper_id is not None and question_id is not None: - answerpapers = AnswerPaper.objects.filter( - questions=question_id, - question_paper_id=questionpaper_id, course_id=course_id) - for answerpaper in answerpapers: - details.append(answerpaper.regrade(question_id)) - course_status = CourseStatus.objects.filter( - user=answerpaper.user, course=answerpaper.course) - if course_status.exists(): - course_status.first().set_grade() - if answerpaper_id is not None and question_id is not None: - answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id) - details.append(answerpaper.regrade(question_id)) - course_status = CourseStatus.objects.filter(user=answerpaper.user, - course=answerpaper.course) - if course_status.exists(): - course_status.first().set_grade() - - return grader(request, extra_context={'details': details}) + quiz = questionpaper.quiz + data = {"user_id": user.id, "course_id": course_id, + "questionpaper_id": questionpaper_id, "question_id": question_id, + "answerpaper_id": answerpaper_id, "quiz_id": quiz.id, + "quiz_name": quiz.description, "course_name": course.name + } + regrade_papers.delay(data) + msg = dedent(""" + {0} is submitted for re-evaluation. You will receive a + notification for the re-evaluation status + """.format(quiz.description) + ) + messages.info(request, msg) + return redirect( + reverse("yaksh:grade_user", args=[quiz.id, course_id]) + ) @login_required @@ -3215,11 +3208,16 @@ def course_students(request, course_id): if not course.is_creator(user) and not course.is_teacher(user): raise Http404("You are not allowed to view {0}".format( course.name)) - enrolled = course.get_enrolled() - requested = course.get_requests() - rejected = course.get_rejected() - context = {"enrolled": enrolled, "requested": requested, "course": course, - "rejected": rejected, "is_students": True} + enrolled_users = course.get_enrolled() + requested_users = course.get_requests() + rejected_users = course.get_rejected() + context = { + "enrolled_users": enrolled_users, + "requested_users": requested_users, + "course": course, + "rejected_users": rejected_users, + "is_students": True + } return my_render_to_response(request, 'yaksh/course_detail.html', context) @@ -3280,6 +3278,41 @@ def download_course_progress(request, course_id): @login_required @email_verified +def view_notifications(request): + user = request.user + notifcations = Notification.objects.get_unread_receiver_notifications( + user.id + ) + if is_moderator(user): + template = "manage.html" + else: + template = "user.html" + context = {"template": template, "notifications": notifcations, + "current_date_time": timezone.now()} + return my_render_to_response( + request, 'yaksh/view_notifications.html', context + ) + + +@login_required +@email_verified +def mark_notification(request, message_uid=None): + user = request.user + if message_uid: + Notification.objects.mark_single_notification( + user.id, message_uid, True + ) + else: + if request.method == 'POST': + msg_uuids = request.POST.getlist("uid") + Notification.objects.mark_bulk_msg_notifications( + user.id, msg_uuids, True) + messages.success(request, "Marked notifcation(s) as read") + return redirect(reverse("yaksh:view_notifications")) + + +@login_required +@email_verified def course_forum(request, course_id): user = request.user base_template = 'user.html' @@ -3291,12 +3324,14 @@ def course_forum(request, course_id): if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) - if 'search' in request.GET: - search_term = request.GET['search'] - posts = course.post.filter(active=True, title__icontains=search_term) + search_term = request.GET.get('search_post') + if search_term: + posts = course.post.get_queryset().filter( + active=True, title__icontains=search_term) else: - posts = course.post.filter(active=True).order_by('-modified_at') - paginator = Paginator(posts, 10) + posts = course.post.get_queryset().filter( + active=True).order_by('-modified_at') + paginator = Paginator(posts, 1) page = request.GET.get('page') posts = paginator.get_page(page) if request.method == "POST": @@ -3314,7 +3349,6 @@ def course_forum(request, course_id): 'user': user, 'course': course, 'base_template': base_template, - 'posts': posts, 'moderator': moderator, 'objects': posts, 'form': form, |