summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/middleware/get_notifications.py21
-rw-r--r--yaksh/models.py16
-rw-r--r--yaksh/tasks.py82
-rw-r--r--yaksh/templates/manage.html30
-rw-r--r--yaksh/templates/yaksh/course_forum.html52
-rw-r--r--yaksh/templates/yaksh/course_students.html154
-rw-r--r--yaksh/templates/yaksh/grade_user.html109
-rw-r--r--yaksh/templates/yaksh/monitor.html366
-rw-r--r--yaksh/templates/yaksh/paginator.html6
-rw-r--r--yaksh/templates/yaksh/post_comments.html7
-rw-r--r--yaksh/templates/yaksh/regrade.html175
-rw-r--r--yaksh/templates/yaksh/user_data.html5
-rw-r--r--yaksh/templates/yaksh/view_notifications.html62
-rw-r--r--yaksh/test_views.py1053
-rw-r--r--yaksh/urls.py60
-rw-r--r--yaksh/views.py250
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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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&nbsp;<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&nbsp;<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"/>&nbsp;
- <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&nbsp;<i class="fa fa-sort"></i></th>
- <th>Email&nbsp;<i class="fa fa-sort"></i></th>
- <th>Roll Number&nbsp;<i class="fa fa-sort"></i></th>
- <th>Institute&nbsp;<i class="fa fa-sort"></i></th>
- <th>Department&nbsp;<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&nbsp;<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"/>&nbsp;
- <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&nbsp;<i class="fa fa-sort"></i></th>
- <th>Email&nbsp;<i class="fa fa-sort"></i></th>
- <th>Roll Number&nbsp;<i class="fa fa-sort"></i></th>
- <th>Institute&nbsp;<i class="fa fa-sort"></i></th>
- <th>Department&nbsp;<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&nbsp;<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"/>&nbsp;
- <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&nbsp;<i class="fa fa-sort"></i></th>
- <th>Email&nbsp;<i class="fa fa-sort"></i></th>
- <th>Roll Number&nbsp;<i class="fa fa-sort"></i></th>
- <th>Institute&nbsp;<i class="fa fa-sort"></i></th>
- <th>Department&nbsp;<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&nbsp;<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>&nbsp;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>&nbsp;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>&nbsp;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">&times;</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:&nbsp</b></td>
- <td>{{course.name}}</td>
- </tr>
- <tr>
- <td><b>Quiz Name:&nbsp</b></td>
- <td>{{quiz.description}}</td>
- </tr>
- <tr>
- <td><b>Number of papers: &nbsp</b></td>
- <td>{{papers|length}}</td>
- </tr>
- <tr>
- <td><b>Papers Completed: &nbsp</b></td>
- <td>
- {% completed papers as completed_papers %}
- <b>{{completed_papers}}</b>
- </td>
- </tr>
- <tr>
- <td><b>Papers in progress: &nbsp</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:&nbsp</b></td>
+ <td>{{course.name}}</td>
+ </tr>
+ <tr>
+ <td><b>Quiz Name:&nbsp</b></td>
+ <td>{{quiz.description}}</td>
+ </tr>
+ <tr>
+ <td><b>Number of papers: &nbsp</b></td>
+ <td>{{papers|length}}</td>
+ </tr>
+ <tr>
+ <td><b>Papers Completed: &nbsp</b></td>
+ <td>
+ {% completed papers as completed_papers %}
+ <b>{{completed_papers}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td><b>Papers in progress: &nbsp</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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;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&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Username&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Roll No&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Institute&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Marks&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Attempts&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Time&nbsp;<i class="fa fa-sort"></i> </th>
- <th> Status&nbsp;<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&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Username&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Roll No&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Institute&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Marks&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Attempts&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Time&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Status&nbsp;<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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;<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>&nbsp;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,