Files
edx-platform/lms/djangoapps/lti_provider/tasks.py
Kyle McCormick d1a775d3cd Use full names for lms.djangoapps imports (#25401)
* Use full LMS imports paths in LMS settings and urls modules
* Use full LMS import paths in Studio settings and urls modules
* Import from lms.djangoapps.badges instead of badges
* Import from lms.djangoapps.branding instead of branding
* Import from lms.djangoapps.bulk_email instead of bulk_email
* Import from lms.djangoapps.bulk_enroll instead of bulk_enroll
* Import from lms.djangoapps.ccx instead of ccx
* Import from lms.djangoapps.course_api instead of course_api
* Import from lms.djangoapps.course_blocks instead of course_blocks
* Import from lms.djangoapps.course_wiki instead of course_wiki
* Import from lms.djangoapps.courseware instead of courseware
* Import from lms.djangoapps.dashboard instead of dashboard
* Import from lms.djangoapps.discussion import discussion
* Import from lms.djangoapps.email_marketing instead of email_marketing
* Import from lms.djangoapps.experiments instead of experiments
* Import from lms.djangoapps.gating instead of gating
* Import from lms.djangoapps.grades instead of grades
* Import from lms.djangoapps.instructor_analytics instead of instructor_analytics
* Import form lms.djangoapps.lms_xblock instead of lms_xblock
* Import from lms.djangoapps.lti_provider instead of lti_provider
* Import from lms.djangoapps.mobile_api instead of mobile_api
* Import from lms.djangoapps.rss_proxy instead of rss_proxy
* Import from lms.djangoapps.static_template_view instead of static_template_view
* Import from lms.djangoapps.survey instead of survey
* Import from lms.djangoapps.verify_student instead of verify_student
* Stop suppressing EdxPlatformDeprecatedImportWarnings
2020-11-04 08:48:33 -05:00

84 lines
3.5 KiB
Python

"""
Asynchronous tasks for the LTI provider app.
"""
import logging
from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey
import lms.djangoapps.lti_provider.outcomes as outcomes
from lms import CELERY_APP
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.lti_provider.models import GradedAssignment
from xmodule.modulestore.django import modulestore
log = logging.getLogger(__name__)
@CELERY_APP.task(name='lms.djangoapps.lti_provider.tasks.send_composite_outcome')
def send_composite_outcome(user_id, course_id, assignment_id, version):
"""
Calculate and transmit the score for a composite module (such as a
vertical).
A composite module may contain multiple problems, so we need to
calculate the total points earned and possible for all child problems. This
requires calculating the scores for the whole course, which is an expensive
operation.
Callers should be aware that the score calculation code accesses the latest
scores from the database. This can lead to a race condition between a view
that updates a user's score and the calculation of the grade. If the Celery
task attempts to read the score from the database before the view exits (and
its transaction is committed), it will see a stale value. Care should be
taken that this task is not triggered until the view exits.
The GradedAssignment model has a version_number field that is incremented
whenever the score is updated. It is used by this method for two purposes.
First, it allows the task to exit if it detects that it has been superseded
by another task that will transmit the score for the same assignment.
Second, it prevents a race condition where two tasks calculate different
scores for a single assignment, and may potentially update the campus LMS
in the wrong order.
"""
assignment = GradedAssignment.objects.get(id=assignment_id)
if version != assignment.version_number:
log.info(
u"Score passback for GradedAssignment %s skipped. More recent score available.",
assignment.id
)
return
course_key = CourseKey.from_string(course_id)
mapped_usage_key = assignment.usage_key.map_into_course(course_key)
user = User.objects.get(id=user_id)
course = modulestore().get_course(course_key, depth=0)
course_grade = CourseGradeFactory().read(user, course)
earned, possible = course_grade.score_for_module(mapped_usage_key)
if possible == 0:
weighted_score = 0
else:
weighted_score = float(earned) / float(possible)
assignment = GradedAssignment.objects.get(id=assignment_id)
if assignment.version_number == version:
outcomes.send_score_update(assignment, weighted_score)
@CELERY_APP.task
def send_leaf_outcome(assignment_id, points_earned, points_possible):
"""
Calculate and transmit the score for a single problem. This method assumes
that the individual problem was the source of a score update, and so it
directly takes the points earned and possible values. As such it does not
have to calculate the scores for the course, making this method far faster
than send_outcome_for_composite_assignment.
"""
assignment = GradedAssignment.objects.get(id=assignment_id)
if points_possible == 0:
weighted_score = 0
else:
weighted_score = float(points_earned) / float(points_possible)
outcomes.send_score_update(assignment, weighted_score)