signals.py 4.42 KB
Newer Older
1 2
"""
Signal handler for enabling/disabling self-generated certificates based on the course-pacing.
3
"""
4 5 6
import logging

from django.db.models.signals import post_save
7
from django.dispatch import receiver
8

Gregory Martin committed
9 10 11
from certificates.models import (
    CertificateWhitelist,
    CertificateStatuses,
12
    GeneratedCertificate
Gregory Martin committed
13
)
14
from certificates.tasks import generate_certificate
15
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
16
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
17
from openedx.core.djangoapps.certificates.api import auto_certificate_generation_enabled
18
from openedx.core.djangoapps.certificates.config import waffle
19
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
20 21
from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED, LEARNER_NOW_VERIFIED
from student.models import CourseEnrollment
22 23


24
log = logging.getLogger(__name__)
25
CERTIFICATE_DELAY_SECONDS = 2
26 27 28 29


@receiver(post_save, sender=CertificateWhitelist, dispatch_uid="append_certificate_whitelist")
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs):  # pylint: disable=unused-argument
30
    course = CourseOverview.get_from_id(instance.course_id)
31
    if not auto_certificate_generation_enabled():
32 33
        return

34
    fire_ungenerated_certificate_task(instance.user, instance.course_id)
35 36 37 38 39 40
    log.info(u'Certificate generation task initiated for {user} : {course} via whitelist'.format(
        user=instance.user.id,
        course=instance.course_id
    ))


41 42 43 44 45 46
@receiver(COURSE_GRADE_NOW_PASSED, dispatch_uid="new_passing_learner")
def _listen_for_passing_grade(sender, user, course_id, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner passing a course, send cert generation task,
    downstream signal from COURSE_GRADE_CHANGED
    """
47
    course = CourseOverview.get_from_id(course_id)
48
    if not auto_certificate_generation_enabled():
49 50
        return

51
    if fire_ungenerated_certificate_task(user, course_id):
52 53 54 55
        log.info(u'Certificate generation task initiated for {user} : {course} via passing grade'.format(
            user=user.id,
            course=course_id
        ))
56 57 58


@receiver(LEARNER_NOW_VERIFIED, dispatch_uid="learner_track_changed")
59
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
60 61 62 63
    """
    Catches a track change signal, determines user status,
    calls fire_ungenerated_certificate_task for passing grades
    """
64
    if not auto_certificate_generation_enabled():
65
        return
66

67 68
    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)
    grade_factory = CourseGradeFactory()
69
    expected_verification_status, _ = SoftwareSecurePhotoVerification.user_status(user)
70
    for enrollment in user_enrollments:
71
        if grade_factory.read(user=user, course=enrollment.course_overview).passed:
72 73 74 75 76 77
            if fire_ungenerated_certificate_task(user, enrollment.course_id, expected_verification_status):
                message = (
                    u'Certificate generation task initiated for {user} : {course} via track change ' +
                    u'with verification status of {status}'
                )
                log.info(message.format(
78
                    user=user.id,
79 80
                    course=enrollment.course_id,
                    status=expected_verification_status
81 82 83
                ))


84
def fire_ungenerated_certificate_task(user, course_key, expected_verification_status=None):
85 86
    """
    Helper function to fire un-generated certificate tasks
Gregory Martin committed
87 88 89 90 91

    The 'mode_is_verified' query is copied from the GeneratedCertificate model,
    but is done here in an attempt to reduce traffic to the workers.
    If the learner is verified and their cert has the 'unverified' status,
    we regenerate the cert.
92
    """
Gregory Martin committed
93 94 95 96
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
    cert = GeneratedCertificate.certificate_for_student(user, course_key)
    if mode_is_verified and (cert is None or cert.status == 'unverified'):
97
        kwargs = {
98
            'student': unicode(user.id),
99 100 101 102 103
            'course_key': unicode(course_key)
        }
        if expected_verification_status:
            kwargs['expected_verification_status'] = unicode(expected_verification_status)
        generate_certificate.apply_async(countdown=CERTIFICATE_DELAY_SECONDS, kwargs=kwargs)
104
        return True