Removed unused imports from openedx/core/djangoapps/{content_libraries, coursegraph,courseware_api, crawlers, credentials, credit}
163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
"""
|
|
This file contains signal handlers for credentials-related functionality.
|
|
"""
|
|
|
|
|
|
from logging import getLogger
|
|
|
|
from django.contrib.sites.models import Site
|
|
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
|
|
from lms.djangoapps.grades.api import CourseGradeFactory
|
|
from openedx.core.djangoapps.catalog.utils import get_programs
|
|
from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
|
|
|
|
from .helpers import is_learner_records_enabled_for_org # lint-amnesty, pylint: disable=unused-import
|
|
from .tasks.v1.tasks import send_grade_to_credentials
|
|
|
|
log = getLogger(__name__)
|
|
|
|
|
|
# "interesting" here means "credentials will want to know about it"
|
|
INTERESTING_MODES = CourseMode.CERTIFICATE_RELEVANT_MODES
|
|
INTERESTING_STATUSES = [
|
|
CertificateStatuses.notpassing,
|
|
CertificateStatuses.downloadable,
|
|
]
|
|
|
|
|
|
# These handlers have Credentials business logic that has bled into the LMS. But we want to filter here in order to
|
|
# not flood our task queue with a bunch of signals. So we put up with it.
|
|
|
|
def is_course_run_in_a_program(course_run_key):
|
|
""" Returns true if the given course key is in any program at all. """
|
|
|
|
# We don't have an easy way to go from course_run_key to a specific site that owns it. So just search each site.
|
|
sites = Site.objects.all()
|
|
str_key = str(course_run_key)
|
|
for site in sites:
|
|
for program in get_programs(site):
|
|
for course in program['courses']:
|
|
for course_run in course['course_runs']:
|
|
if str_key == course_run['key']:
|
|
return True
|
|
return False
|
|
|
|
|
|
def send_grade_if_interesting(user, course_run_key, mode, status, letter_grade, percent_grade, verbose=False):
|
|
""" Checks if grade is interesting to Credentials and schedules a Celery task if so. """
|
|
|
|
if verbose:
|
|
msg = "Starting send_grade_if_interesting with params: "\
|
|
"user [{username}], "\
|
|
"course_run_key [{key}], "\
|
|
"mode [{mode}], "\
|
|
"status [{status}], "\
|
|
"letter_grade [{letter_grade}], "\
|
|
"percent_grade [{percent_grade}], "\
|
|
"verbose [{verbose}]"\
|
|
.format(
|
|
username=getattr(user, 'username', None),
|
|
key=str(course_run_key),
|
|
mode=mode,
|
|
status=status,
|
|
letter_grade=letter_grade,
|
|
percent_grade=percent_grade,
|
|
verbose=verbose
|
|
)
|
|
log.info(msg)
|
|
# Avoid scheduling new tasks if certification is disabled. (Grades are a part of the records/cert story)
|
|
if not CredentialsApiConfig.current().is_learner_issuance_enabled:
|
|
if verbose:
|
|
log.info("Skipping send grade: is_learner_issuance_enabled False")
|
|
return
|
|
|
|
# Avoid scheduling new tasks if learner records are disabled for this site.
|
|
if not is_learner_records_enabled_for_org(course_run_key.org):
|
|
if verbose:
|
|
log.info(
|
|
"Skipping send grade: ENABLE_LEARNER_RECORDS False for org [{org}]".format(
|
|
org=course_run_key.org
|
|
)
|
|
)
|
|
return
|
|
|
|
# Grab mode/status if we don't have them in hand
|
|
if mode is None or status is None:
|
|
try:
|
|
cert = GeneratedCertificate.objects.get(user=user, course_id=course_run_key) # pylint: disable=no-member
|
|
mode = cert.mode
|
|
status = cert.status
|
|
except GeneratedCertificate.DoesNotExist:
|
|
# We only care about grades for which there is a certificate.
|
|
if verbose:
|
|
log.info(
|
|
"Skipping send grade: no cert for user [{username}] & course_id [{course_id}]".format(
|
|
username=getattr(user, 'username', None),
|
|
course_id=str(course_run_key)
|
|
)
|
|
)
|
|
return
|
|
|
|
# Don't worry about whether it's available as well as awarded. Just awarded is good enough to record a verified
|
|
# attempt at a course. We want even the grades that didn't pass the class because Credentials wants to know about
|
|
# those too.
|
|
if mode not in INTERESTING_MODES or status not in INTERESTING_STATUSES:
|
|
if verbose:
|
|
log.info(
|
|
"Skipping send grade: mode/status uninteresting for mode [{mode}] & status [{status}]".format(
|
|
mode=mode,
|
|
status=status
|
|
)
|
|
)
|
|
return
|
|
|
|
# If the course isn't in any program, don't bother telling Credentials about it. When Credentials grows support
|
|
# for course records as well as program records, we'll need to open this up.
|
|
if not is_course_run_in_a_program(course_run_key):
|
|
if verbose:
|
|
log.info(
|
|
"Skipping send grade: course run not in a program. [{course_id}]".format(course_id=str(course_run_key))
|
|
)
|
|
return
|
|
|
|
# Grab grades if we don't have them in hand
|
|
if letter_grade is None or percent_grade is None:
|
|
grade = CourseGradeFactory().read(user, course_key=course_run_key, create_if_needed=False)
|
|
if grade is None:
|
|
if verbose:
|
|
log.info(
|
|
"Skipping send grade: No grade found for user [{username}] & course_id [{course_id}]".format(
|
|
username=getattr(user, 'username', None),
|
|
course_id=str(course_run_key)
|
|
)
|
|
)
|
|
return
|
|
letter_grade = grade.letter_grade
|
|
percent_grade = grade.percent
|
|
|
|
send_grade_to_credentials.delay(user.username, str(course_run_key), True, letter_grade, percent_grade)
|
|
|
|
|
|
def handle_grade_change(user, course_grade, course_key, **kwargs):
|
|
"""
|
|
Notifies the Credentials IDA about certain grades it needs for its records, when a grade changes.
|
|
"""
|
|
send_grade_if_interesting(
|
|
user,
|
|
course_key,
|
|
None,
|
|
None,
|
|
course_grade.letter_grade,
|
|
course_grade.percent,
|
|
verbose=kwargs.get('verbose', False)
|
|
)
|
|
|
|
|
|
def handle_cert_change(user, course_key, mode, status, **kwargs):
|
|
"""
|
|
Notifies the Credentials IDA about certain grades it needs for its records, when a cert changes.
|
|
"""
|
|
send_grade_if_interesting(user, course_key, mode, status, None, None, verbose=kwargs.get('verbose', False))
|