Skip to content

Commit

Permalink
Refactored signals to their own module
Browse files Browse the repository at this point in the history
  • Loading branch information
Dave St.Germain committed Jan 3, 2019
1 parent f238f04 commit 0a8669a
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 202 deletions.
26 changes: 0 additions & 26 deletions edx_proctoring/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from django.utils.translation import ugettext as _, ugettext_noop
from django.conf import settings
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.template import loader
from django.urls import reverse, NoReverseMatch
from django.core.mail.message import EmailMessage
Expand Down Expand Up @@ -201,30 +199,6 @@ def remove_review_policy(exam_id):
exam_review_policy.delete()


@receiver(post_save, sender=ProctoredExamReviewPolicy)
@receiver(post_save, sender=ProctoredExam)
def _save_exam_on_backend(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Save the exam to the backend provider when our model changes.
It also combines the review policy into the exam when saving to the backend
"""
if sender == ProctoredExam:
exam_obj = instance
review_policy = ProctoredExamReviewPolicy.get_review_policy_for_exam(instance.id)
else:
exam_obj = instance.proctored_exam
review_policy = instance
if exam_obj.is_proctored:
exam = ProctoredExamSerializer(exam_obj).data
if review_policy:
exam['rule_summary'] = review_policy.review_policy
backend = get_backend_provider(exam)
external_id = backend.on_exam_saved(exam)
if external_id and external_id != exam_obj.external_id:
exam_obj.external_id = external_id
exam_obj.save()


def get_review_policy_by_exam_id(exam_id):
"""
Looks up exam by the Primary Key. Raises exception if not found.
Expand Down
1 change: 1 addition & 0 deletions edx_proctoring/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def ready(self):
"""
Loads the available proctoring backends
"""
from edx_proctoring import signals # pylint: disable=unused-variable
config = settings.PROCTORING_BACKENDS

self.backends = {} # pylint: disable=W0201
Expand Down
189 changes: 13 additions & 176 deletions edx_proctoring/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from django.db import models
from django.db.models import Q
from django.db.models.base import ObjectDoesNotExist
from django.db.models.signals import pre_save, pre_delete
from django.dispatch import receiver
from django.utils.translation import ugettext_noop

from model_utils.models import TimeStampedModel
Expand Down Expand Up @@ -187,44 +185,6 @@ def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
raise NotImplementedError()


# Hook up the pre_save signal to record creations in the ProctoredExamReviewPolicyHistory table.
@receiver(pre_save, sender=ProctoredExamReviewPolicy)
def on_review_policy_saved(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Student Allowance.
Will only archive on update, and not on new entries created.
"""

if instance.id:
# only for update cases
original = ProctoredExamReviewPolicy.objects.get(id=instance.id)
_make_review_policy_archive_copy(original)


# Hook up the pre_delete signal to record creations in the ProctoredExamReviewPolicyHistory table.
@receiver(pre_delete, sender=ProctoredExamReviewPolicy)
def on_review_policy_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archive the allowance when the item is about to be deleted
"""

_make_review_policy_archive_copy(instance)


def _make_review_policy_archive_copy(instance):
"""
Do the copying into the history table
"""

archive_object = ProctoredExamReviewPolicyHistory(
original_id=instance.id,
set_by_user_id=instance.set_by_user_id,
proctored_exam=instance.proctored_exam,
review_policy=instance.review_policy,
)
archive_object.save()


class ProctoredExamStudentAttemptManager(models.Manager):
"""
Custom manager
Expand Down Expand Up @@ -473,67 +433,20 @@ class Meta:
verbose_name = 'proctored exam attempt history'


@receiver(pre_delete, sender=ProctoredExamStudentAttempt)
def on_attempt_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archive the exam attempt when the item is about to be deleted
Make a clone and populate in the History table
"""

archive_object = ProctoredExamStudentAttemptHistory(
user=instance.user,
attempt_id=instance.id,
proctored_exam=instance.proctored_exam,
started_at=instance.started_at,
completed_at=instance.completed_at,
attempt_code=instance.attempt_code,
external_id=instance.external_id,
allowed_time_limit_mins=instance.allowed_time_limit_mins,
status=instance.status,
taking_as_proctored=instance.taking_as_proctored,
is_sample_attempt=instance.is_sample_attempt,
student_name=instance.student_name,
review_policy_id=instance.review_policy_id,
last_poll_timestamp=instance.last_poll_timestamp,
last_poll_ipaddr=instance.last_poll_ipaddr,

)
archive_object.save()


@receiver(pre_save, sender=ProctoredExamStudentAttempt)
def on_attempt_updated(sender, instance, **kwargs): # pylint: disable=unused-argument
def archive_model(model, instance, **mapping):
"""
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.
Archives the instance to the given history model
optionally maps field names from the instance model to the history model
"""

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()
archive = model()
# timestampedmodels automatically create these
mapping['created'] = mapping['modified'] = None
for field in instance._meta.get_fields():
to_name = mapping.get(field.name, field.name)
if to_name is not None:
setattr(archive, to_name, getattr(instance, field.name, None))
archive.save()
return archive


class QuerySetWithUpdateOverride(models.QuerySet):
Expand All @@ -543,7 +456,7 @@ class QuerySetWithUpdateOverride(models.QuerySet):
"""
def update(self, **kwargs):
super(QuerySetWithUpdateOverride, self).update(**kwargs)
_make_archive_copy(self.get())
archive_model(ProctoredExamStudentAllowanceHistory, self.get(), id='allowance_id')


class ProctoredExamStudentAllowanceManager(models.Manager):
Expand Down Expand Up @@ -717,43 +630,6 @@ class Meta:
verbose_name = 'proctored allowance history'


# Hook up the post_save signal to record creations in the ProctoredExamStudentAllowanceHistory table.
@receiver(pre_save, sender=ProctoredExamStudentAllowance)
def on_allowance_saved(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Student Allowance.
Will only archive on update, and not on new entries created.
"""

if instance.id:
original = ProctoredExamStudentAllowance.objects.get(id=instance.id)
_make_archive_copy(original)


@receiver(pre_delete, sender=ProctoredExamStudentAllowance)
def on_allowance_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archive the allowance when the item is about to be deleted
"""

_make_archive_copy(instance)


def _make_archive_copy(item):
"""
Make a clone and populate in the History table
"""

archive_object = ProctoredExamStudentAllowanceHistory(
allowance_id=item.id,
user=item.user,
proctored_exam=item.proctored_exam,
key=item.key,
value=item.value
)
archive_object.save()


class ProctoredExamSoftwareSecureReview(TimeStampedModel):
"""
This is where we store the proctored exam review feedback
Expand Down Expand Up @@ -842,45 +718,6 @@ class Meta:
verbose_name = 'Proctored exam review archive'


# Hook up the post_save signal to record creations in the ProctoredExamStudentAllowanceHistory table.
@receiver(pre_save, sender=ProctoredExamSoftwareSecureReview)
def on_review_saved(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Student Allowance.
Will only archive on update, and not on new entries created.
"""

if instance.id:
# only for update cases
original = ProctoredExamSoftwareSecureReview.objects.get(id=instance.id)
_make_review_archive_copy(original)


@receiver(pre_delete, sender=ProctoredExamSoftwareSecureReview)
def on_review_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Archive the allowance when the item is about to be deleted
"""

_make_review_archive_copy(instance)


def _make_review_archive_copy(instance):
"""
Do the copying into the history table
"""

archive_object = ProctoredExamSoftwareSecureReviewHistory(
attempt_code=instance.attempt_code,
review_status=instance.review_status,
raw_data=instance.raw_data,
reviewed_by=instance.reviewed_by,
student=instance.student,
exam=instance.exam,
)
archive_object.save()


class ProctoredExamSoftwareSecureComment(TimeStampedModel):
"""
This is where we store the proctored exam review comments
Expand Down
106 changes: 106 additions & 0 deletions edx_proctoring/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"edx-proctoring signals"
from django.db.models.signals import pre_save, post_save, pre_delete
from django.dispatch import receiver
from edx_proctoring import models
from edx_proctoring.backends import get_backend_provider


@receiver(post_save, sender=models.ProctoredExamReviewPolicy)
@receiver(post_save, sender=models.ProctoredExam)
def _save_exam_on_backend(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Save the exam to the backend provider when our model changes.
It also combines the review policy into the exam when saving to the backend
"""
if sender == models.ProctoredExam:
exam_obj = instance
review_policy = models.ProctoredExamReviewPolicy.get_review_policy_for_exam(instance.id)
else:
exam_obj = instance.proctored_exam
review_policy = instance
if exam_obj.is_proctored:
from edx_proctoring.serializers import ProctoredExamSerializer
exam = ProctoredExamSerializer(exam_obj).data
if review_policy:
exam['rule_summary'] = review_policy.review_policy
backend = get_backend_provider(exam)
external_id = backend.on_exam_saved(exam)
if external_id and external_id != exam_obj.external_id:
exam_obj.external_id = external_id
exam_obj.save()


# Hook up the pre_save signal to record creations in the ProctoredExamReviewPolicyHistory table.
@receiver(pre_save, sender=models.ProctoredExamReviewPolicy)
@receiver(pre_delete, sender=models.ProctoredExamReviewPolicy)
def on_review_policy_changed(sender, instance, signal, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Review Policy.
Will only archive on update/delete, and not on new entries created.
"""
if signal is pre_save:
if instance.id:
instance = sender.objects.get(id=instance.id)
else:
return
models.archive_model(models.ProctoredExamReviewPolicyHistory, instance, id='original_id')


# Hook up the post_save signal to record creations in the ProctoredExamStudentAllowanceHistory table.
@receiver(pre_save, sender=models.ProctoredExamStudentAllowance)
@receiver(pre_delete, sender=models.ProctoredExamStudentAllowance)
def on_allowance_saved(sender, instance, signal, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Student Allowance.
Will only archive on update/delete, and not on new entries created.
"""

if signal is pre_save:
if instance.id:
instance = sender.objects.get(id=instance.id)
else:
return
models.archive_model(models.ProctoredExamStudentAllowanceHistory, instance, id='allowance_id')


@receiver(pre_save, sender=models.ProctoredExamStudentAttempt)
@receiver(pre_delete, sender=models.ProctoredExamStudentAttempt)
def on_attempt_updated(sender, instance, signal, **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 signal is pre_save:
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 = sender.objects.get(id=instance.id)

if original.status != instance.status:
instance = original
else:
return
else:
return
models.archive_model(models.ProctoredExamStudentAttemptHistory, instance, id='attempt_id')


# Hook up the signals to record updates/deletions in the ProctoredExamStudentAllowanceHistory table.
@receiver(pre_save, sender=models.ProctoredExamSoftwareSecureReview)
@receiver(pre_delete, sender=models.ProctoredExamSoftwareSecureReview)
def on_review_saved(sender, instance, signal, **kwargs): # pylint: disable=unused-argument
"""
Archiving all changes made to the Review.
Will only archive on update/delete, and not on new entries created.
"""
if signal is pre_save:
if instance.id:
# only for update cases
instance = sender.objects.get(id=instance.id)
else:
# don't archive on create
return
models.archive_model(models.ProctoredExamSoftwareSecureReviewHistory, instance, id='review_id')

0 comments on commit 0a8669a

Please sign in to comment.