summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/models.py127
-rw-r--r--yaksh/templates/yaksh/micromanaged.html22
-rw-r--r--yaksh/templates/yaksh/micromonitor.html9
-rw-r--r--yaksh/templates/yaksh/monitor.html31
-rw-r--r--yaksh/templates/yaksh/quit.html4
-rw-r--r--yaksh/templates/yaksh/quizzes_user.html2
-rw-r--r--yaksh/templatetags/custom_filters.py20
-rw-r--r--yaksh/test_models.py141
-rw-r--r--yaksh/urls.py9
-rw-r--r--yaksh/views.py135
10 files changed, 491 insertions, 9 deletions
diff --git a/yaksh/models.py b/yaksh/models.py
index 1094bb0..84cad1e 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1045,6 +1045,14 @@ class Course(models.Model):
def get_learning_modules(self):
return self.learning_module.order_by("order")
+ def get_learning_module(self, quiz):
+ modules = self.get_learning_modules()
+ for module in modules:
+ for unit in module.get_learning_units():
+ if unit.quiz == quiz:
+ break
+ return module
+
def get_unit_completion_status(self, module, user, unit):
course_module = self.learning_module.get(id=module.id)
learning_unit = course_module.learning_unit.get(id=unit.id)
@@ -1776,7 +1784,7 @@ class QuestionPaper(models.Model):
all_questions = questions
return all_questions
- def make_answerpaper(self, user, ip, attempt_num, course_id):
+ def make_answerpaper(self, user, ip, attempt_num, course_id, special=False):
"""Creates an answer paper for the user to attempt the quiz"""
try:
ans_paper = AnswerPaper.objects.get(user=user,
@@ -1795,6 +1803,7 @@ class QuestionPaper(models.Model):
ans_paper.end_time = ans_paper.start_time + \
timedelta(minutes=self.quiz.duration)
ans_paper.question_paper = self
+ ans_paper.is_special = special
ans_paper.save()
questions = self._get_questions_for_answerpaper()
ans_paper.questions.add(*questions)
@@ -2139,6 +2148,10 @@ class AnswerPaper(models.Model):
# set question order
questions_order = models.TextField(blank=True, default='')
+ extra_time = models.FloatField('Additional time in mins', default=0.0)
+
+ is_special = models.BooleanField(default=False)
+
objects = AnswerPaperManager()
class Meta:
@@ -2221,11 +2234,16 @@ class AnswerPaper(models.Model):
questions = list(self.questions.all())
return questions
+ def set_extra_time(self, time=0):
+ self.extra_time = time
+ self.save()
+
def time_left(self):
"""Return the time remaining for the user in seconds."""
secs = self._get_total_seconds()
+ extra_time = self.extra_time * 60
total = self.question_paper.quiz.duration*60.0
- remain = max(total - secs, 0)
+ remain = max(total - (secs - extra_time), 0)
return int(remain)
def time_left_on_question(self, question):
@@ -2710,3 +2728,108 @@ class Comment(ForumBase):
def __str__(self):
return 'Comment by {0}: {1}'.format(self.creator.username,
self.post_field.title)
+
+
+class MicroManager(models.Model):
+ manager = models.ForeignKey(User, on_delete=models.CASCADE,
+ related_name='micromanaging', null=True)
+ student = models.ForeignKey(User, on_delete=models.CASCADE,
+ related_name='micromanaged')
+ course = models.ForeignKey(Course, on_delete=models.CASCADE)
+ quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, null=True)
+ special_attempt = models.BooleanField(default=False)
+ attempts_permitted = models.IntegerField(default=0)
+ permitted_time = models.DateTimeField(default=timezone.now)
+ attempts_utilised = models.IntegerField(default=0)
+ wait_time = models.IntegerField('Days to wait before special attempt',
+ default=0)
+ attempt_valid_for = models.IntegerField('Validity days', default=90)
+
+ class Meta:
+ unique_together = ('student', 'course', 'quiz')
+
+ def set_wait_time(self, days=0):
+ self.wait_time = days
+ self.save()
+
+ def increment_attempts_permitted(self):
+ self.attempts_permitted += 1
+ self.save()
+
+ def update_permitted_time(self, permit_time=None):
+ time_now = timezone.now()
+ self.permitted_time = time_now if not permit_time else permit_time
+ self.save()
+
+ def has_student_attempts_exhausted(self):
+ if self.quiz.attempts_allowed == -1:
+ return False
+ question_paper = self.quiz.questionpaper_set.first()
+ attempts = AnswerPaper.objects.get_total_attempt(
+ question_paper, self.student, course_id=self.course.id
+ )
+ last_attempt = AnswerPaper.objects.get_user_last_attempt(
+ question_paper, self.student, self.course.id
+ )
+ if last_attempt:
+ if last_attempt.is_attempt_inprogress():
+ return False
+ return attempts >= self.quiz.attempts_allowed
+
+ def is_last_attempt_inprogress(self):
+ question_paper = self.quiz.questionpaper_set.first()
+ last_attempt = AnswerPaper.objects.get_user_last_attempt(
+ question_paper, self.student, self.course.id
+ )
+ if last_attempt:
+ return last_attempt.is_attempt_inprogress()
+ return False
+
+ def has_quiz_time_exhausted(self):
+ return not self.quiz.active or self.quiz.is_expired()
+
+ def is_course_exhausted(self):
+ return not self.course.active or not self.course.is_active_enrollment()
+
+ def is_special_attempt_required(self):
+ return (self.has_student_attempts_exhausted() or
+ self.has_quiz_time_exhausted() or self.is_course_exhausted())
+
+ def allow_special_attempt(self, wait_time=0):
+ if (self.is_special_attempt_required() and
+ not self.is_last_attempt_inprogress()):
+ self.special_attempt = True
+ if self.attempts_utilised >= self.attempts_permitted:
+ self.increment_attempts_permitted()
+ self.update_permitted_time()
+ self.set_wait_time(days=wait_time)
+ self.save()
+
+ def has_special_attempt(self):
+ return (self.special_attempt and
+ (self.attempts_utilised < self.attempts_permitted))
+
+ def is_attempt_time_valid(self):
+ permit_time = self.permitted_time
+ wait_time = permit_time + timezone.timedelta(days=self.wait_time)
+ valid_time = permit_time + timezone.timedelta(
+ days=self.attempt_valid_for)
+ return wait_time <= timezone.now() <= valid_time
+
+ def can_student_attempt(self):
+ return self.has_special_attempt() and self.is_attempt_time_valid()
+
+ def get_attempt_number(self):
+ return self.quiz.attempts_allowed + self.attempts_utilised + 1
+
+ def increment_attempts_utilised(self):
+ self.attempts_utilised += 1
+ self.save()
+
+ def revoke_special_attempt(self):
+ self.special_attempt = False
+ self.save()
+
+ def __str__(self):
+ return 'MicroManager for {0} - {1}'.format(self.student.username,
+ self.course.name)
diff --git a/yaksh/templates/yaksh/micromanaged.html b/yaksh/templates/yaksh/micromanaged.html
new file mode 100644
index 0000000..336feec
--- /dev/null
+++ b/yaksh/templates/yaksh/micromanaged.html
@@ -0,0 +1,22 @@
+{% if micromanagers %}
+<hr>
+<div class="row">
+ {% for micromanager in micromanagers %}
+ {% if micromanager.attempts_permitted > 0 %}
+ <div class="col-md-8">
+ <p> You have been given a special attempt to the {{ micromanager.quiz.description }} by the course creator</p>
+ </div>
+ <div class="col-md-3">
+ {% if micromanager.can_student_attempt %}
+ <a class="btn btn-success" href="{% url 'yaksh:special_start' micromanager.id %}">
+ Start Special Attempt
+ </a>
+ {% else %}
+ <span class="badge badge-secondary">Exhausted</span>
+ {% endif %}
+ </div>
+ {% endif %}
+ {% endfor %}
+{% endif %}
+</div>
+
diff --git a/yaksh/templates/yaksh/micromonitor.html b/yaksh/templates/yaksh/micromonitor.html
new file mode 100644
index 0000000..cc059aa
--- /dev/null
+++ b/yaksh/templates/yaksh/micromonitor.html
@@ -0,0 +1,9 @@
+{% if micromanager %}
+ {% if micromanager.can_student_attempt %}
+ <a class="btn btn-danger" href="{% url 'yaksh:revoke_special_attempt' micromanager.id %}">Revoke</a>
+ {% else %}
+ <a class="btn btn-success" href="{% url 'yaksh:allow_special_attempt' user_id course_id quiz_id %}">Allow </a>
+ {% endif %}
+{% else %}
+ <a class="btn btn-success" href="{% url 'yaksh:allow_special_attempt' user_id course_id quiz_id %}">Allow </a>
+{% endif %}
diff --git a/yaksh/templates/yaksh/monitor.html b/yaksh/templates/yaksh/monitor.html
index ef7b033..183ba99 100644
--- a/yaksh/templates/yaksh/monitor.html
+++ b/yaksh/templates/yaksh/monitor.html
@@ -74,6 +74,18 @@ $(document).ready(function()
</div>
</div>
<br>
+ <br>
+ {% if messages %}
+ {% for message in messages %}
+ <div class="alert alert-dismissible alert-{{ message.tags }}">
+ <button type="button" class="close" data-dismiss="alert">
+ <i class="fa fa-close"></i>
+ </button>
+ <strong>{{ message }}</strong>
+ </div>
+ {% endfor %}
+ {% endif %}
+ <br>
<div class="row">
<div class="col-md-4">
<a href="{% url 'yaksh:show_statistics' papers.0.question_paper.id course.id %}" class="btn btn-primary">
@@ -102,8 +114,9 @@ $(document).ready(function()
<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> Time Left&nbsp;<i class="fa fa-sort"></i> </th>
<th> Status&nbsp;<i class="fa fa-sort"></i> </th>
+ <th> Special Attempt&nbsp;<i class="fa fa-sort"></i> </th>
</tr>
</thead>
<tbody>
@@ -118,7 +131,20 @@ $(document).ready(function()
<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>
+ <td> {% if paper.is_attempt_inprogress %}
+ <form method="post" action="{% url 'yaksh:extend_time' paper.id %}">
+ {% csrf_token %}
+ <div class="form-group">
+ <label for="extra_time"> Time in mins </label>
+ <input type="number" class="form-control" id="extra_time" name="extra_time" required>
+ </div>
+ <button type="submit" class="btn btn-primary">Extend Time</button>
+ </form>
+ {% else %}
+ <span class="badge badge-secondary"> Completed </span>
+ {% endif %}
+ </td>
+ <td>{% specail_attempt_monitor paper.user.id course.id quiz.id %}</td>
</tr>
{% endfor %}
</tbody>
@@ -126,7 +152,6 @@ $(document).ready(function()
<!-- CSV Modal -->
<div class="modal fade" id="csvModal" role="dialog">
<div class="modal-dialog">
-
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
diff --git a/yaksh/templates/yaksh/quit.html b/yaksh/templates/yaksh/quit.html
index ccb0893..a801ea8 100644
--- a/yaksh/templates/yaksh/quit.html
+++ b/yaksh/templates/yaksh/quit.html
@@ -56,7 +56,11 @@
{% csrf_token %}
<center>
<button class="btn btn-outline-success btn-lg" type="submit" name="yes">Yes</button>
+ {% if paper.is_special %}
+ <a class="btn btn-outline-danger btn-lg" name="no" href="{% url 'yaksh:skip_question' paper.questions.first.id paper.attempt_number module_id paper.question_paper.id course_id %}">No</a>
+ {% else %}
<a class="btn btn-outline-danger btn-lg" name="no" href="{% url 'yaksh:start_quiz' paper.attempt_number module_id paper.question_paper.id course_id %}">No</a>
+ {% endif %}
</center>
</form>
{% endblock content %}
diff --git a/yaksh/templates/yaksh/quizzes_user.html b/yaksh/templates/yaksh/quizzes_user.html
index a9f5a43..e28cb69 100644
--- a/yaksh/templates/yaksh/quizzes_user.html
+++ b/yaksh/templates/yaksh/quizzes_user.html
@@ -1,4 +1,5 @@
{% extends "user.html" %}
+{% load custom_filters %}
{% block title %} Student Dashboard {% endblock %}
{% block script %}
@@ -104,6 +105,7 @@
{% endif %}
</div>
</div>
+ {% show_special_attempt user.id course.data.id %}
</div>
<div id="collapse{{course.data.id}}" class="collapse hide" data-parent="#accordion">
<div class="card-body">
diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py
index cdbf4d0..2a01787 100644
--- a/yaksh/templatetags/custom_filters.py
+++ b/yaksh/templatetags/custom_filters.py
@@ -10,6 +10,7 @@ except ImportError:
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
+from yaksh.models import User, Course, Quiz
register = template.Library()
@@ -145,3 +146,22 @@ def to_float(text):
@register.filter(name="to_str")
def to_str(text):
return text.decode("utf-8")
+
+
+@register.inclusion_tag('yaksh/micromanaged.html')
+def show_special_attempt(user_id, course_id):
+ user = User.objects.get(pk=user_id)
+ micromanagers = user.micromanaged.filter(course_id=course_id)
+ context = {'micromanagers': micromanagers}
+ return context
+
+
+@register.inclusion_tag('yaksh/micromonitor.html')
+def specail_attempt_monitor(user_id, course_id, quiz_id):
+ user = User.objects.get(pk=user_id)
+ micromanagers = user.micromanaged.filter(course_id=course_id,
+ quiz_id=quiz_id)
+ context = {'user_id': user_id, 'course_id': course_id, 'quiz_id': quiz_id}
+ if micromanagers.exists():
+ context['micromanager'] = micromanagers.first()
+ return context
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index a77c8bf..e24a38e 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -5,7 +5,7 @@ from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload,\
LearningModule, LearningUnit, Lesson, LessonFile, CourseStatus, \
- create_group, legend_display_types, Post, Comment
+ create_group, legend_display_types, Post, Comment, MicroManager
from yaksh.code_server import (
ServerPool, get_result as get_result_from_code_server
)
@@ -103,7 +103,8 @@ def setUpModule():
course.save()
LessonFile.objects.create(lesson=lesson)
CourseStatus.objects.create(course=course, user=course_user)
-
+ MicroManager.objects.create(manager=user, course=course, quiz=quiz,
+ student=course_user)
def tearDownModule():
User.objects.all().delete()
@@ -116,6 +117,7 @@ def tearDownModule():
LearningUnit.objects.all().delete()
LearningModule.objects.all().delete()
AnswerPaper.objects.all().delete()
+ MicroManager.objects.all().delete()
Group.objects.all().delete()
@@ -129,6 +131,141 @@ class GlobalMethodsTestCases(unittest.TestCase):
###############################################################################
+class MicroManagerTestCase(unittest.TestCase):
+ def setUp(self):
+ self.micromanager = MicroManager.objects.first()
+ self.course = self.micromanager.course
+ quiz = self.micromanager.quiz
+ self.questionpaper = QuestionPaper.objects.create(quiz=quiz)
+ question = Question.objects.get(summary='Q1')
+ self.questionpaper.fixed_questions.add(question)
+ self.questionpaper.update_total_marks()
+ self.student = User.objects.get(username='course_user')
+
+ def tearDown(self):
+ self.questionpaper.delete()
+
+ def test_micromanager(self):
+ # Given
+ user = User.objects.get(username='creator')
+ course = Course.objects.get(name='Python Course', creator=user)
+ quiz = Quiz.objects.get(description='demo quiz 1')
+ student = User.objects.get(username='course_user')
+
+ # When
+ micromanager = MicroManager.objects.first()
+
+ # Then
+ self.assertIsNotNone(micromanager)
+ self.assertEqual(micromanager.manager, user)
+ self.assertEqual(micromanager.student, student)
+ self.assertEqual(micromanager.course, course)
+ self.assertEqual(micromanager.quiz, quiz)
+ self.assertFalse(micromanager.special_attempt)
+ self.assertEqual(micromanager.attempts_permitted, 0)
+ self.assertEqual(micromanager.attempts_utilised, 0)
+ self.assertEqual(micromanager.wait_time, 0)
+ self.assertEqual(micromanager.attempt_valid_for, 90)
+ self.assertEqual(user.micromanaging.first(), micromanager)
+ self.assertEqual(student.micromanaged.first(), micromanager)
+
+ def test_set_wait_time(self):
+ # Given
+ micromanager = self.micromanager
+
+ # When
+ micromanager.set_wait_time(days=2)
+
+ # Then
+ self.assertEqual(micromanager.wait_time, 2)
+
+ def self_increment_attempts_permitted(self):
+ # Given
+ micromanager = self.micromanager
+
+ # When
+ micromanager.increment_attempts_permitted()
+
+ # Then
+ self.assertEqual(micromanager.attempts_permitted, 1)
+
+ def test_update_permitted_time(self):
+ # Given
+ micromanager = self.micromanager
+ permit_time = timezone.now()
+
+ # When
+ micromanager.update_permitted_time(permit_time)
+
+ # Then
+ self.assertEqual(micromanager.permitted_time, permit_time)
+
+ def test_has_student_attempts_exhausted(self):
+ # Given
+ micromanager = self.micromanager
+
+ # Then
+ self.assertFalse(micromanager.has_student_attempts_exhausted())
+
+ def test_has_quiz_time_exhausted(self):
+ # Given
+ micromanager = self.micromanager
+
+ # Then
+ self.assertFalse(micromanager.has_quiz_time_exhausted())
+
+ def test_is_special_attempt_required(self):
+ # Given
+ micromanager = self.micromanager
+ attempt = 1
+ ip = '127.0.0.1'
+
+ # Then
+ self.assertFalse(micromanager.is_special_attempt_required())
+
+ # When
+ answerpaper = self.questionpaper.make_answerpaper(self.student, ip,
+ attempt,
+ self.course.id)
+ answerpaper.update_marks(state='completed')
+
+ # Then
+ self.assertTrue(micromanager.is_special_attempt_required())
+
+ answerpaper.delete()
+
+ def test_allow_special_attempt(self):
+ # Given
+ micromanager = self.micromanager
+
+ # When
+ micromanager.allow_special_attempt()
+
+ # Then
+ self.assertFalse(micromanager.special_attempt)
+
+ def test_has_special_attempt(self):
+ # Given
+ micromanager = self.micromanager
+
+ # Then
+ self.assertFalse(micromanager.has_special_attempt())
+
+ def test_is_attempt_time_valid(self):
+ # Given
+ micromanager = self.micromanager
+
+ # Then
+ self.assertTrue(micromanager.is_attempt_time_valid())
+
+ def test_can_student_attempt(self):
+ # Given
+ micromanager = self.micromanager
+
+ # Then
+ self.assertFalse(micromanager.can_student_attempt())
+
+
class LessonTestCases(unittest.TestCase):
def setUp(self):
self.lesson = Lesson.objects.get(name='L1')
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 0639b25..13e46fc 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -237,4 +237,13 @@ urlpatterns = [
views.mark_notification, name="mark_notification"),
path('mark/notifications', views.mark_notification,
name="mark_notification"),
+ url(r'^manage/micromanager/allow_special_attempt/(?P<user_id>\d+)/'
+ '(?P<course_id>\d+)/(?P<quiz_id>\d+)/$',
+ views.allow_special_attempt, name='allow_special_attempt'),
+ url(r'^micromanager/special_start/(?P<micromanager_id>\d+)/$',
+ views.special_start, name='special_start'),
+ url(r'^manage/micromanager/special_revoke/(?P<micromanager_id>\d+)/$',
+ views.revoke_special_attempt, name='revoke_special_attempt'),
+ url(r'^manage/extend_time/(?P<paper_id>\d+)/$',
+ views.extend_time, name='extend_time'),
]
diff --git a/yaksh/views.py b/yaksh/views.py
index 8413889..01f5e4e 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -37,7 +37,8 @@ from yaksh.models import (
QuestionPaper, QuestionSet, Quiz, Question, StandardTestCase,
StdIOBasedTestCase, StringTestCase, TestCase, User,
get_model_class, FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile,
- LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment
+ LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment,
+ MicroManager
)
from yaksh.forms import (
UserRegisterForm, UserLoginForm, QuizForm, QuestionForm,
@@ -483,6 +484,46 @@ def user_login(request):
@login_required
@email_verified
+def special_start(request, micromanager_id=None):
+ user = request.user
+ micromanager = get_object_or_404(MicroManager, pk=micromanager_id,
+ student=user)
+ course = micromanager.course
+ quiz = micromanager.quiz
+ module = course.get_learning_module(quiz)
+ quest_paper = get_object_or_404(QuestionPaper, quiz=quiz)
+
+ if not course.is_enrolled(user):
+ msg = 'You are not enrolled in {0} course'.format(course.name)
+ return quizlist_user(request, msg=msg)
+
+ if not micromanager.can_student_attempt():
+ msg = 'Your special attempts are exhausted for {0}'.format(
+ quiz.description)
+ return quizlist_user(request, msg=msg)
+
+ last_attempt = AnswerPaper.objects.get_user_last_attempt(
+ quest_paper, user, course.id)
+
+ if last_attempt:
+ if last_attempt.is_attempt_inprogress():
+ return show_question(
+ request, last_attempt.current_question(), last_attempt,
+ course_id=course.id, module_id=module.id,
+ previous_question=last_attempt.current_question()
+ )
+
+ attempt_num = micromanager.get_attempt_number()
+ ip = request.META['REMOTE_ADDR']
+ new_paper = quest_paper.make_answerpaper(user, ip, attempt_num, course.id,
+ special=True)
+ micromanager.increment_attempts_utilised()
+ return show_question(request, new_paper.current_question(), new_paper,
+ course_id=course.id, module_id=module.id)
+
+
+@login_required
+@email_verified
def start(request, questionpaper_id=None, attempt_num=None, course_id=None,
module_id=None):
"""Check the user cedentials and if any quiz is available,
@@ -643,7 +684,7 @@ def show_question(request, question, paper, error_message=None,
request, msg, paper.attempt_number, paper.question_paper.id,
course_id=course_id, module_id=module_id
)
- if not quiz.active:
+ if not quiz.active and not paper.is_special:
reason = 'The quiz has been deactivated!'
return complete(
request, reason, paper.attempt_number, paper.question_paper.id,
@@ -3449,3 +3490,93 @@ def hide_comment(request, course_id, uuid):
comment.active = False
comment.save()
return redirect('yaksh:post_comments', course_id, post_uid)
+
+
+@login_required
+@email_verified
+def allow_special_attempt(request, user_id, course_id, quiz_id):
+ user = request.user
+
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ course = get_object_or_404(Course, pk=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+
+ quiz = get_object_or_404(Quiz, pk=quiz_id)
+ student = get_object_or_404(User, pk=user_id)
+
+ if not course.is_enrolled(student):
+ raise Http404('The student is not enrolled for this course')
+
+ micromanager, created = MicroManager.objects.get_or_create(
+ course=course, student=student, quiz=quiz
+ )
+ micromanager.manager = user
+ micromanager.save()
+
+ if (not micromanager.is_special_attempt_required() or
+ micromanager.is_last_attempt_inprogress()):
+ name = student.get_full_name()
+ msg = '{} can attempt normally. No special attempt required!'.format(
+ name)
+ elif micromanager.can_student_attempt():
+ msg = '{} already has a special attempt!'.format(
+ student.get_full_name())
+ else:
+ micromanager.allow_special_attempt()
+ msg = 'A special attempt is provided to {}!'.format(
+ student.get_full_name())
+
+ messages.info(request, msg)
+ return my_redirect('/exam/manage/monitor/{0}/{1}/'.format(quiz_id,
+ course_id))
+
+
+@login_required
+@email_verified
+def revoke_special_attempt(request, micromanager_id):
+ user = request.user
+
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ micromanager = get_object_or_404(MicroManager, pk=micromanager_id)
+ course = micromanager.course
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ micromanager.revoke_special_attempt()
+ msg = 'Revoked special attempt for {}'.format(
+ micromanager.student.get_full_name())
+ messages.info(request, msg)
+ return my_redirect('/exam/manage/monitor/{0}/{1}/'.format(
+ micromanager.quiz.id, course.id))
+
+
+@login_required
+@email_verified
+def extend_time(request, paper_id):
+ user = request.user
+
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ anspaper = get_object_or_404(AnswerPaper, pk=paper_id)
+ course = anspaper.course
+ 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":
+ extra_time = request.POST.get('extra_time', None)
+ if extra_time is None:
+ msg = 'Please provide time'
+ else:
+ anspaper.set_extra_time(extra_time)
+ msg = 'Extra {0} minutes given to {1}'.format(
+ extra_time, anspaper.user.get_full_name())
+ else:
+ msg = 'Bad Request'
+ messages.info(request, msg)
+ return my_redirect('/exam/manage/monitor/{0}/{1}/'.format(
+ anspaper.question_paper.quiz.id, course.id))