Merge pull request #19177 from edx/schen/EDUCATOR-3619
Hook up the Datatable Gradebook with grade bulkupdate API
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="grade-override-info-container alert alert-info pattern-library-shim" role="alert"></div>
|
||||
<div class="grade-override-menu-buttons">
|
||||
<button class="btn grade-override-modal-save"><%- strLib.save %></button>
|
||||
<button class="btn grade-override-modal-close"><%- strLib.cancel %></button>
|
||||
|
||||
@@ -12,6 +12,7 @@ from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.pagination import CursorPagination
|
||||
from rest_framework.response import Response
|
||||
from six import text_type
|
||||
from util.date_utils import to_timestamp
|
||||
|
||||
from courseware.courses import get_course_with_access
|
||||
from edx_rest_framework_extensions import permissions
|
||||
@@ -22,16 +23,23 @@ from lms.djangoapps.grades.config.waffle import waffle_flags, WRITABLE_GRADEBOOK
|
||||
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
|
||||
from lms.djangoapps.grades.course_data import CourseData
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.grades.events import SUBSECTION_GRADE_CALCULATED, subsection_grade_calculated
|
||||
from lms.djangoapps.grades.models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride
|
||||
from lms.djangoapps.grades.signals import signals
|
||||
from lms.djangoapps.grades.subsection_grade import CreateSubsectionGrade
|
||||
from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
from track.event_transaction_utils import (
|
||||
create_new_event_transaction_id,
|
||||
get_event_transaction_id,
|
||||
get_event_transaction_type,
|
||||
set_event_transaction_type
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
USER_MODEL = get_user_model()
|
||||
@@ -767,17 +775,27 @@ class GradebookBulkUpdateView(GradeViewMixin, GenericAPIView):
|
||||
grade=subsection_grade_model,
|
||||
defaults=self._clean_override_data(override_data),
|
||||
)
|
||||
signals.SUBSECTION_OVERRIDE_CHANGED.send(
|
||||
sender=None,
|
||||
user_id=subsection_grade_model.user_id,
|
||||
course_id=text_type(subsection_grade_model.course_id),
|
||||
usage_id=text_type(subsection_grade_model.usage_key),
|
||||
only_if_higher=False,
|
||||
modified=override.modified,
|
||||
score_deleted=False,
|
||||
score_db_table=ScoreDatabaseTableEnum.overrides,
|
||||
force_update_subsections=True,
|
||||
|
||||
set_event_transaction_type(SUBSECTION_GRADE_CALCULATED)
|
||||
create_new_event_transaction_id()
|
||||
|
||||
recalculate_subsection_grade_v3.apply(
|
||||
kwargs=dict(
|
||||
user_id=subsection_grade_model.user_id,
|
||||
anonymous_user_id=None,
|
||||
course_id=text_type(subsection_grade_model.course_id),
|
||||
usage_id=text_type(subsection_grade_model.usage_key),
|
||||
only_if_higher=False,
|
||||
expected_modified_time=to_timestamp(override.modified),
|
||||
score_deleted=False,
|
||||
event_transaction_id=unicode(get_event_transaction_id()),
|
||||
event_transaction_type=unicode(get_event_transaction_type()),
|
||||
score_db_table=ScoreDatabaseTableEnum.overrides,
|
||||
force_update_subsections=True,
|
||||
)
|
||||
)
|
||||
# Emit events to let our tracking system to know we updated subsection grade
|
||||
subsection_grade_calculated(subsection_grade_model)
|
||||
|
||||
def _clean_override_data(self, override_data):
|
||||
"""
|
||||
|
||||
@@ -71,11 +71,18 @@ Decisions
|
||||
|
||||
d. A status code of ``422`` will be returned for requests that contain any failed item. This allows a client
|
||||
to easily tell if any item in their request payload was problematic and needs special handling. If all
|
||||
requested items succeed, a ``202 (accepted)`` is returned. This status code was chosen because an
|
||||
asynchronous celery task is enqueued for each subsection grade that needs to be updated.
|
||||
requested items succeed, a ``202 (accepted)`` is returned. This status code was chosen because a
|
||||
celery task is enqueued and waited for each subsection grade that needs to be updated.
|
||||
|
||||
e. We have to thread a ``force_update_subsections`` keyword argument through the Django signal invocation
|
||||
that enqueues the subsection update task. This is because we may be creating a new subsection grade
|
||||
with no score data available from either ``courseware.StudentModule`` records or from the `Submissions` API.
|
||||
In this case, the only score data available exists in the grade override record, and the subsection ``update()``
|
||||
call should be forced to read from this record.
|
||||
e. We have to thread a ``force_update_subsections`` keyword argument into the subsection update task that
|
||||
we enqueue. This is because we may be creating a new subsection grade with no score data available from
|
||||
either ``courseware.StudentModule`` records or from the `Submissions` API. In this case, the only score
|
||||
data available exists in the grade override record, and the subsection ``update()`` call should be forced
|
||||
to read from this record.
|
||||
|
||||
f. We have to synchronously update each grade record for each user in this endpoint. This means the request
|
||||
will be left open for longer period than we wanted. The reason is: the primary consumer gradebook UI
|
||||
would need to display the updated grade result for all users, after update is complete. If we do update
|
||||
asynchronously, the gradebook UI do not know how to update the table with new values, including agregations
|
||||
for the user's course grade. This is the lowest effort change to address the UI display problem. We will
|
||||
need to improve this mechanism as we continue to develop.
|
||||
|
||||
@@ -16,26 +16,21 @@ function _templateLoader(templateName, staticPath, callback, errorCallback) {
|
||||
}
|
||||
|
||||
function courseXblockUpdater(courseID, dataToSend, visibilityData, callback, errorCallback) {
|
||||
var cleanData = {'users' : {}};
|
||||
|
||||
if (dataToSend instanceof Array)
|
||||
for (var i = 0; i < dataToSend.length; i++) {
|
||||
cleanData.users['id_' + dataToSend.userID] = {
|
||||
'block_id' : dataToSend[i].blockID || '',
|
||||
'grade' : dataToSend[i].grade || '',
|
||||
'max_grade' : dataToSend[i].maxGrade || null,
|
||||
'state' : dataToSend[i].state || '{}',
|
||||
'user_id' : dataToSend[i].userID || ''
|
||||
};
|
||||
}
|
||||
else if (dataToSend instanceof Object)
|
||||
cleanData.users = dataToSend;
|
||||
|
||||
var postUrl = '/api/score/courses/' + courseID;
|
||||
|
||||
if (!_.isEmpty(visibilityData))
|
||||
cleanData.visibility = visibilityData;
|
||||
var cleanData = _.map(dataToSend, function (data) {
|
||||
return {
|
||||
user_id: data.user_id,
|
||||
usage_id: data.block_id,
|
||||
grade: {
|
||||
earned_all_override: data.grade || 0,
|
||||
possible_all_override: data.max_grade || 0,
|
||||
earned_graded_override: data.grade || 0,
|
||||
possible_graded_override: data.max_grade || 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var postUrl = '/api/grades/v1/gradebook/' + courseID + '/bulk-update';
|
||||
$('')
|
||||
$.ajax({
|
||||
url: postUrl,
|
||||
method: 'POST',
|
||||
@@ -314,6 +309,7 @@ $(document).ready(function() {
|
||||
isManualGrading = JSON.parse($(gradeOverrideObject).attr('data-manual-grading'));
|
||||
$modal.find('.assignment-name-placeholder').text(assignmentName);
|
||||
$modal.find('.block-id-placeholder').text(blockID);
|
||||
$modal.find('.grade-override-info-container').hide();
|
||||
if ( _.isEmpty(userAutoGrades) ) {
|
||||
$tableWrapper.hide();
|
||||
$manualGradeVisibilityWrapper.toggle(false);
|
||||
@@ -486,6 +482,18 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
|
||||
function setInfoMessage(messageText){
|
||||
var $messageField = $('.grade-override-modal').find('.grade-override-info-container');
|
||||
if(messageText) {
|
||||
$messageField.text(messageText);
|
||||
$messageField.show();
|
||||
}
|
||||
else {
|
||||
$messageField.empty();
|
||||
$messageField.hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on('click', '.grade-override-modal-save', function() {
|
||||
var visibilityData = {};
|
||||
if (isManualGrading) {
|
||||
@@ -499,12 +507,14 @@ $(document).ready(function() {
|
||||
return;
|
||||
var validStatus = ValidateAdjustedGradesData();
|
||||
if (validStatus) {
|
||||
setInfoMessage(gettext('Update in progress, please wait...'));
|
||||
courseXblockUpdater(
|
||||
courseID,
|
||||
adjustedGradesData,
|
||||
visibilityData,
|
||||
function(data){
|
||||
gradebookOverrideModalReset();
|
||||
setInfoMessage();
|
||||
renderAllGradebook = false;
|
||||
gradeBookData = [];
|
||||
$gradesTableWrapper.empty();
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
.grade-override-menu-buttons {
|
||||
padding: 10px;
|
||||
}
|
||||
.grade-override-info-container{
|
||||
margin: 10px;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user