Initial changes to gate section based on completion percentage code refactoring and added evaluation of completion milestone Fixed broken unit tests and added new tests Fixed broken tests and quality violations Fixed Pep8 violation Fixed eslint quality violations Test changes as suggested by reviewer changes after feedbacy from reviewer Update the docstring with suggested changes excluding chapter from the blocks Disallow empty values for min score and min completion Changes afte feedback from UX/Accessibility removed blank line
126 lines
4.9 KiB
Python
126 lines
4.9 KiB
Python
""" receivers of course_published and library_updated events in order to trigger indexing task """
|
|
|
|
from datetime import datetime
|
|
from functools import wraps
|
|
import logging
|
|
|
|
from django.core.cache import cache
|
|
from django.dispatch import receiver
|
|
from pytz import UTC
|
|
|
|
from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer
|
|
from contentstore.proctoring import register_special_exams
|
|
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
|
|
from openedx.core.djangoapps.credit.signals import on_course_publish
|
|
from openedx.core.lib.gating import api as gating_api
|
|
from track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type
|
|
from util.module_utils import yield_dynamic_descriptor_descendants
|
|
from .signals import GRADING_POLICY_CHANGED
|
|
from xmodule.modulestore.django import SignalHandler, modulestore
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
GRADING_POLICY_COUNTDOWN_SECONDS = 3600
|
|
|
|
|
|
def locked(expiry_seconds, key):
|
|
def task_decorator(func):
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
cache_key = '{}-{}'.format(func.__name__, kwargs[key])
|
|
if cache.add(cache_key, "true", expiry_seconds):
|
|
log.info('Locking task in cache with key: %s for %s seconds', cache_key, expiry_seconds)
|
|
return func(*args, **kwargs)
|
|
return wrapper
|
|
return task_decorator
|
|
|
|
|
|
@receiver(SignalHandler.course_published)
|
|
def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Receives publishing signal and performs publishing related workflows, such as
|
|
registering proctored exams, building up credit requirements, and performing
|
|
search indexing
|
|
"""
|
|
|
|
# first is to registered exams, the credit subsystem will assume that
|
|
# all proctored exams have already been registered, so we have to do that first
|
|
try:
|
|
register_special_exams(course_key)
|
|
# pylint: disable=broad-except
|
|
except Exception as exception:
|
|
log.exception(exception)
|
|
|
|
# then call into the credit subsystem (in /openedx/djangoapps/credit)
|
|
# to perform any 'on_publish' workflow
|
|
on_course_publish(course_key)
|
|
|
|
# Finally call into the course search subsystem
|
|
# to kick off an indexing action
|
|
if CoursewareSearchIndexer.indexing_is_enabled():
|
|
# import here, because signal is registered at startup, but items in tasks are not yet able to be loaded
|
|
from contentstore.tasks import update_search_index
|
|
|
|
update_search_index.delay(unicode(course_key), datetime.now(UTC).isoformat())
|
|
|
|
|
|
@receiver(SignalHandler.library_updated)
|
|
def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Receives signal and kicks off celery task to update search index
|
|
"""
|
|
|
|
if LibrarySearchIndexer.indexing_is_enabled():
|
|
# import here, because signal is registered at startup, but items in tasks are not yet able to be loaded
|
|
from contentstore.tasks import update_library_index
|
|
|
|
update_library_index.delay(unicode(library_key), datetime.now(UTC).isoformat())
|
|
|
|
|
|
@receiver(SignalHandler.item_deleted)
|
|
def handle_item_deleted(**kwargs):
|
|
"""
|
|
Receives the item_deleted signal sent by Studio when an XBlock is removed from
|
|
the course structure and removes any gating milestone data associated with it or
|
|
its descendants.
|
|
|
|
Arguments:
|
|
kwargs (dict): Contains the content usage key of the item deleted
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
usage_key = kwargs.get('usage_key')
|
|
if usage_key:
|
|
# Strip branch info
|
|
usage_key = usage_key.for_branch(None)
|
|
course_key = usage_key.course_key
|
|
deleted_module = modulestore().get_item(usage_key)
|
|
for module in yield_dynamic_descriptor_descendants(deleted_module, kwargs.get('user_id')):
|
|
# Remove prerequisite milestone data
|
|
gating_api.remove_prerequisite(module.location)
|
|
# Remove any 'requires' course content milestone relationships
|
|
gating_api.set_required_content(course_key, module.location, None, None, None)
|
|
|
|
|
|
@receiver(GRADING_POLICY_CHANGED)
|
|
@locked(expiry_seconds=GRADING_POLICY_COUNTDOWN_SECONDS, key='course_key')
|
|
def handle_grading_policy_changed(sender, **kwargs):
|
|
# pylint: disable=unused-argument
|
|
"""
|
|
Receives signal and kicks off celery task to recalculate grades
|
|
"""
|
|
kwargs = {
|
|
'course_key': unicode(kwargs.get('course_key')),
|
|
'event_transaction_id': unicode(get_event_transaction_id()),
|
|
'event_transaction_type': unicode(get_event_transaction_type()),
|
|
}
|
|
result = compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS)
|
|
log.info("Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format(
|
|
task_name=compute_all_grades_for_course.name,
|
|
task_id=result.task_id,
|
|
kwargs=kwargs,
|
|
))
|