Commit d30a3a91 by chrisndodge

Merge pull request #193 from edx/cdodge/archive-attempt-updates

archive any change to the attempt status
parents 00819219 8ac77ad7
...@@ -803,7 +803,9 @@ class SoftwareSecureTests(TestCase): ...@@ -803,7 +803,9 @@ class SoftwareSecureTests(TestCase):
# look at the attempt again, since it moved into Archived state # look at the attempt again, since it moved into Archived state
# then it should still remain unchanged # then it should still remain unchanged
archived_attempt = ProctoredExamStudentAttemptHistory.objects.get(attempt_code=attempt['attempt_code']) archived_attempt = ProctoredExamStudentAttemptHistory.objects.filter(
attempt_code=attempt['attempt_code']
).latest('created')
self.assertEqual(archived_attempt.status, attempt['status']) self.assertEqual(archived_attempt.status, attempt['status'])
......
...@@ -525,6 +525,9 @@ class ProctoredExamStudentAttemptHistory(TimeStampedModel): ...@@ -525,6 +525,9 @@ class ProctoredExamStudentAttemptHistory(TimeStampedModel):
# this ID might point to a record that is in the History table # this ID might point to a record that is in the History table
review_policy_id = models.IntegerField(null=True) review_policy_id = models.IntegerField(null=True)
last_poll_timestamp = models.DateTimeField(null=True)
last_poll_ipaddr = models.CharField(max_length=32, null=True)
@classmethod @classmethod
def get_exam_attempt_by_code(cls, attempt_code): def get_exam_attempt_by_code(cls, attempt_code):
""" """
...@@ -537,9 +540,10 @@ class ProctoredExamStudentAttemptHistory(TimeStampedModel): ...@@ -537,9 +540,10 @@ class ProctoredExamStudentAttemptHistory(TimeStampedModel):
# there are any # there are any
exam_attempt_obj = None exam_attempt_obj = None
items = cls.objects.filter(attempt_code=attempt_code).order_by("-created") try:
if items: exam_attempt_obj = cls.objects.filter(attempt_code=attempt_code).latest("created")
exam_attempt_obj = items[0] except cls.DoesNotExist: # pylint: disable=no-member
pass
return exam_attempt_obj return exam_attempt_obj
...@@ -570,10 +574,48 @@ def on_attempt_deleted(sender, instance, **kwargs): # pylint: disable=unused-ar ...@@ -570,10 +574,48 @@ def on_attempt_deleted(sender, instance, **kwargs): # pylint: disable=unused-ar
is_sample_attempt=instance.is_sample_attempt, is_sample_attempt=instance.is_sample_attempt,
student_name=instance.student_name, student_name=instance.student_name,
review_policy_id=instance.review_policy_id, review_policy_id=instance.review_policy_id,
last_poll_timestamp=instance.last_poll_timestamp,
last_poll_ipaddr=instance.last_poll_ipaddr,
) )
archive_object.save() archive_object.save()
@receiver(pre_save, sender=ProctoredExamStudentAttempt)
def on_attempt_updated(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archive the exam attempt whenever the attempt status is about to be
modified. Make a new entry with the previous value of the status in the
ProctoredExamStudentAttemptHistory table.
"""
if instance.id:
# on an update case, get the original
# and see if the status has changed, if so, then we need
# to archive it
original = ProctoredExamStudentAttempt.objects.get(id=instance.id)
if original.status != instance.status:
archive_object = ProctoredExamStudentAttemptHistory(
user=original.user,
attempt_id=original.id,
proctored_exam=original.proctored_exam,
started_at=original.started_at,
completed_at=original.completed_at,
attempt_code=original.attempt_code,
external_id=original.external_id,
allowed_time_limit_mins=original.allowed_time_limit_mins,
status=original.status,
taking_as_proctored=original.taking_as_proctored,
is_sample_attempt=original.is_sample_attempt,
student_name=original.student_name,
review_policy_id=original.review_policy_id,
last_poll_timestamp=original.last_poll_timestamp,
last_poll_ipaddr=original.last_poll_ipaddr,
)
archive_object.save()
class QuerySetWithUpdateOverride(models.query.QuerySet): class QuerySetWithUpdateOverride(models.query.QuerySet):
""" """
Custom QuerySet class to make an archive copy Custom QuerySet class to make an archive copy
......
""" """
All tests for the models.py All tests for the models.py
""" """
# pylint: disable=invalid-name
from edx_proctoring.models import ( from edx_proctoring.models import (
ProctoredExam, ProctoredExam,
ProctoredExamStudentAllowance, ProctoredExamStudentAllowance,
...@@ -9,6 +10,7 @@ from edx_proctoring.models import ( ...@@ -9,6 +10,7 @@ from edx_proctoring.models import (
ProctoredExamStudentAttemptHistory, ProctoredExamStudentAttemptHistory,
ProctoredExamReviewPolicy, ProctoredExamReviewPolicy,
ProctoredExamReviewPolicyHistory, ProctoredExamReviewPolicyHistory,
ProctoredExamStudentAttemptStatus,
) )
from .utils import ( from .utils import (
...@@ -174,6 +176,52 @@ class ProctoredExamStudentAttemptTests(LoggedInTestCase): ...@@ -174,6 +176,52 @@ class ProctoredExamStudentAttemptTests(LoggedInTestCase):
deleted_item = ProctoredExamStudentAttemptHistory.get_exam_attempt_by_code("123456") deleted_item = ProctoredExamStudentAttemptHistory.get_exam_attempt_by_code("123456")
self.assertEqual(deleted_item.student_name, "John. D Updated") self.assertEqual(deleted_item.student_name, "John. D Updated")
def test_update_proctored_exam_attempt(self):
"""
Deleting the proctored exam attempt creates an entry in the history table.
"""
proctored_exam = ProctoredExam.objects.create(
course_id='test_course',
content_id='test_content',
exam_name='Test Exam',
external_id='123aXqe3',
time_limit_mins=90
)
attempt = ProctoredExamStudentAttempt.objects.create(
proctored_exam_id=proctored_exam.id,
user_id=1,
status=ProctoredExamStudentAttemptStatus.created,
student_name="John. D",
allowed_time_limit_mins=10,
attempt_code="123456",
taking_as_proctored=True,
is_sample_attempt=True,
external_id=1
)
# No entry in the History table on creation of the Allowance entry.
attempt_history = ProctoredExamStudentAttemptHistory.objects.filter(user_id=1)
self.assertEqual(len(attempt_history), 0)
# re-saving, but not changing status should not make an archive copy
attempt.student_name = 'John. D Updated'
attempt.save()
attempt_history = ProctoredExamStudentAttemptHistory.objects.filter(user_id=1)
self.assertEqual(len(attempt_history), 0)
# change status...
attempt.status = ProctoredExamStudentAttemptStatus.started
attempt.save()
attempt_history = ProctoredExamStudentAttemptHistory.objects.filter(user_id=1)
self.assertEqual(len(attempt_history), 1)
# make sure we can ready it back with helper class method
updated_item = ProctoredExamStudentAttemptHistory.get_exam_attempt_by_code("123456")
self.assertEqual(updated_item.student_name, "John. D Updated")
self.assertEqual(updated_item.status, ProctoredExamStudentAttemptStatus.created)
def test_get_exam_attempts(self): def test_get_exam_attempts(self):
""" """
Test to get all the exam attempts for a course Test to get all the exam attempts for a course
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment