MDL-42425 modify mod_assign_save_grade to process advanced grading data

This commit is contained in:
Paul Charsley 2014-01-16 11:03:48 +13:00
parent 183515da23
commit 40c3dacf44
4 changed files with 514 additions and 19 deletions

View File

@ -122,6 +122,14 @@ $functions = array(
'type' => 'write'
),
'mod_assign_save_grades' => array(
'classname' => 'mod_assign_external',
'methodname' => 'save_grades',
'classpath' => 'mod/assign/externallib.php',
'description' => 'Save multiple grade updates for an assignment.',
'type' => 'write'
),
'mod_assign_save_user_extensions' => array(
'classname' => 'mod_assign_external',
'methodname' => 'save_user_extensions',

View File

@ -1665,6 +1665,7 @@ class mod_assign_external extends external_api {
public static function save_grade_parameters() {
global $CFG;
require_once("$CFG->dirroot/mod/assign/locallib.php");
require_once("$CFG->dirroot/grade/grading/lib.php");
$instance = new assign(null, null, null);
$pluginfeedbackparams = array();
@ -1675,20 +1676,41 @@ class mod_assign_external extends external_api {
}
}
$advancedgradingdata = array();
$methods = array_keys(grading_manager::available_methods(false));
foreach ($methods as $method) {
require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
$details = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
if (!empty($details)) {
$items = array();
foreach ($details as $key => $value) {
$value->required = VALUE_OPTIONAL;
unset($value->content->keys['id']);
$items[$key] = new external_multiple_structure (new external_single_structure(
array(
'criterionid' => new external_value(PARAM_INT, 'criterion id'),
'fillings' => $value
)
));
}
$advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
}
}
return new external_function_parameters(
array(
'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user'),
'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
'to all members ' .
'of the group (for group assignments).'),
'plugindata' => new external_single_structure(
$pluginfeedbackparams
)
'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
VALUE_DEFAULT, array())
)
);
}
@ -1698,12 +1720,13 @@ class mod_assign_external extends external_api {
*
* @param int $assignmentid The id of the assignment
* @param int $userid The id of the user
* @param float $grade The grade
* @param float $grade The grade (ignored if the assignment uses advanced grading)
* @param int $attemptnumber The attempt number
* @param bool $addattempt Allow another attempt
* @param string $workflowstate New workflow state
* @param bool $applytoall Apply the grade to all members of the group
* @param array $plugindata Custom data used by plugins
* @param array $advancedgradingdata Advanced grading data
* @return null
* @since Moodle 2.6
*/
@ -1714,7 +1737,8 @@ class mod_assign_external extends external_api {
$addattempt,
$workflowstate,
$applytoall,
$plugindata) {
$plugindata = array(),
$advancedgradingdata = array()) {
global $CFG, $USER;
require_once("$CFG->dirroot/mod/assign/locallib.php");
@ -1726,22 +1750,38 @@ class mod_assign_external extends external_api {
'workflowstate' => $workflowstate,
'addattempt' => $addattempt,
'applytoall' => $applytoall,
'plugindata' => $plugindata));
'plugindata' => $plugindata,
'advancedgradingdata' => $advancedgradingdata));
$cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
$cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
self::validate_context($context);
$assignment = new assign($context, $cm, null);
$gradedata = (object)$plugindata;
$gradedata = (object)$params['plugindata'];
$gradedata->addattempt = $addattempt;
$gradedata->attemptnumber = $attemptnumber;
$gradedata->workflowstate = $workflowstate;
$gradedata->applytoall = $applytoall;
$gradedata->grade = $grade;
$gradedata->addattempt = $params['addattempt'];
$gradedata->attemptnumber = $params['attemptnumber'];
$gradedata->workflowstate = $params['workflowstate'];
$gradedata->applytoall = $params['applytoall'];
$gradedata->grade = $params['grade'];
$assignment->save_grade($userid, $gradedata);
if (!empty($params['advancedgradingdata'])) {
$advancedgrading = array();
$criteria = reset($params['advancedgradingdata']);
foreach ($criteria as $key => $criterion) {
$details = array();
foreach ($criterion as $value) {
foreach ($value['fillings'] as $filling) {
$details[$value['criterionid']] = $filling;
}
}
$advancedgrading[$key] = $details;
}
$gradedata->advancedgrading = $advancedgrading;
}
$assignment->save_grade($params['userid'], $gradedata);
return null;
}
@ -1756,6 +1796,157 @@ class mod_assign_external extends external_api {
return null;
}
/**
* Describes the parameters for save_grades
* @return external_external_function_parameters
* @since Moodle 2.7
*/
public static function save_grades_parameters() {
global $CFG;
require_once("$CFG->dirroot/mod/assign/locallib.php");
require_once("$CFG->dirroot/grade/grading/lib.php");
$instance = new assign(null, null, null);
$pluginfeedbackparams = array();
foreach ($instance->get_feedback_plugins() as $plugin) {
$pluginparams = $plugin->get_external_parameters();
if (!empty($pluginparams)) {
$pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
}
}
$advancedgradingdata = array();
$methods = array_keys(grading_manager::available_methods(false));
foreach ($methods as $method) {
require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
$details = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
if (!empty($details)) {
$items = array();
foreach ($details as $key => $value) {
$value->required = VALUE_OPTIONAL;
unset($value->content->keys['id']);
$items[$key] = new external_multiple_structure (new external_single_structure(
array(
'criterionid' => new external_value(PARAM_INT, 'criterion id'),
'fillings' => $value
)
));
}
$advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
}
}
return new external_function_parameters(
array(
'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
'to all members ' .
'of the group (for group assignments).'),
'grades' => new external_multiple_structure(
new external_single_structure(
array (
'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
'Ignored if advanced grading used'),
'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
VALUE_DEFAULT, array()),
'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
VALUE_DEFAULT, array())
)
)
)
)
);
}
/**
* Save multiple student grades for a single assignment.
*
* @param int $assignmentid The id of the assignment
* @param boolean $applytoall If set to true and this is a team assignment,
* apply the grade to all members of the group
* @param array $grades grade data for one or more students that includes
* userid - The id of the student being graded
* grade - The grade (ignored if the assignment uses advanced grading)
* attemptnumber - The attempt number
* addattempt - Allow another attempt
* workflowstate - New workflow state
* plugindata - Custom data used by plugins
* advancedgradingdata - Optional Advanced grading data
* @throws invalid_parameter_exception if multiple grades are supplied for
* a team assignment that has $applytoall set to true
* @return null
* @since Moodle 2.7
*/
public static function save_grades($assignmentid, $applytoall = false, $grades) {
global $CFG, $USER;
require_once("$CFG->dirroot/mod/assign/locallib.php");
$params = self::validate_parameters(self::save_grades_parameters(),
array('assignmentid' => $assignmentid,
'applytoall' => $applytoall,
'grades' => $grades));
$cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
self::validate_context($context);
$assignment = new assign($context, $cm, null);
if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
// Check that only 1 user per submission group is provided.
$groupids = array();
foreach ($params['grades'] as $gradeinfo) {
$group = $assignment->get_submission_group($gradeinfo['userid']);
if (in_array($group->id, $groupids)) {
throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
.' this is not permitted when the applytoall flag is set');
} else {
$groupids[] = $group->id;
}
}
}
foreach ($params['grades'] as $gradeinfo) {
$gradedata = (object)$gradeinfo['plugindata'];
$gradedata->addattempt = $gradeinfo['addattempt'];
$gradedata->attemptnumber = $gradeinfo['attemptnumber'];
$gradedata->workflowstate = $gradeinfo['workflowstate'];
$gradedata->applytoall = $params['applytoall'];
$gradedata->grade = $gradeinfo['grade'];
if (!empty($gradeinfo['advancedgradingdata'])) {
$advancedgrading = array();
$criteria = reset($gradeinfo['advancedgradingdata']);
foreach ($criteria as $key => $criterion) {
$details = array();
foreach ($criterion as $value) {
foreach ($value['fillings'] as $filling) {
$details[$value['criterionid']] = $filling;
}
}
$advancedgrading[$key] = $details;
}
$gradedata->advancedgrading = $advancedgrading;
}
$assignment->save_grade($gradeinfo['userid'], $gradedata);
}
return null;
}
/**
* Describes the return value for save_grades
*
* @return external_single_structure
* @since Moodle 2.7
*/
public static function save_grades_returns() {
return null;
}
/**
* Describes the parameters for copy_previous_attempt
* @return external_external_function_parameters

View File

@ -916,7 +916,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
$student1 = self::getDataGenerator()->create_user();
$student2 = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student1->id,
$course->id,
$studentrole->id);
@ -945,8 +945,8 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
// Now try a grade.
$feedbackpluginparams = array();
$feedbackpluginparams['files_filemanager'] = $draftidfile;
$feedbackeditorparams = array('text'=>'Yeeha!',
'format'=>1);
$feedbackeditorparams = array('text' => 'Yeeha!',
'format' => 1);
$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
$result = mod_assign_external::save_grade($instance->id,
$student1->id,
@ -965,6 +965,296 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
$this->assertEquals($result['assignments'][0]['grades'][0]['grade'], '50.0');
}
/**
* Test save grades with advanced grading data
*/
public function test_save_grades_with_advanced_grading() {
global $DB, $USER;
$this->resetAfterTest(true);
// Create a course and assignment and users.
$course = self::getDataGenerator()->create_course();
$teacher = self::getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($teacher->id,
$course->id,
$teacherrole->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params['course'] = $course->id;
$params['assignfeedback_file_enabled'] = 0;
$params['assignfeedback_comments_enabled'] = 0;
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = context_module::instance($cm->id);
$assign = new assign($context, $cm, $course);
$student1 = self::getDataGenerator()->create_user();
$student2 = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student1->id,
$course->id,
$studentrole->id);
$this->getDataGenerator()->enrol_user($student2->id,
$course->id,
$studentrole->id);
$this->setUser($teacher);
$feedbackpluginparams = array();
$feedbackpluginparams['files_filemanager'] = 0;
$feedbackeditorparams = array('text' => '', 'format' => 1);
$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
// Create advanced grading data.
// Create grading area.
$gradingarea = array(
'contextid' => $context->id,
'component' => 'mod_assign',
'areaname' => 'submissions',
'activemethod' => 'rubric'
);
$areaid = $DB->insert_record('grading_areas', $gradingarea);
// Create a rubric grading definition.
$rubricdefinition = array (
'areaid' => $areaid,
'method' => 'rubric',
'name' => 'test',
'status' => 20,
'copiedfromid' => 1,
'timecreated' => 1,
'usercreated' => $teacher->id,
'timemodified' => 1,
'usermodified' => $teacher->id,
'timecopied' => 0
);
$definitionid = $DB->insert_record('grading_definitions', $rubricdefinition);
// Create a criterion with a level.
$rubriccriteria = array (
'definitionid' => $definitionid,
'sortorder' => 1,
'description' => 'Demonstrate an understanding of disease control',
'descriptionformat' => 0
);
$criterionid = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria);
$rubriclevel1 = array (
'criterionid' => $criterionid,
'score' => 50,
'definition' => 'pass',
'definitionformat' => 0
);
$rubriclevel2 = array (
'criterionid' => $criterionid,
'score' => 100,
'definition' => 'excellent',
'definitionformat' => 0
);
$rubriclevel3 = array (
'criterionid' => $criterionid,
'score' => 0,
'definition' => 'fail',
'definitionformat' => 0
);
$levelid1 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel1);
$levelid2 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel2);
$levelid3 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel3);
// Create the filling.
$student1filling = array (
'criterionid' => $criterionid,
'levelid' => $levelid1,
'remark' => 'well done you passed',
'remarkformat' => 0
);
$student2filling = array (
'criterionid' => $criterionid,
'levelid' => $levelid2,
'remark' => 'Excellent work',
'remarkformat' => 0
);
$student1criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student1filling)));
$student1advancedgradingdata = array('rubric' => array('criteria' => $student1criteria));
$student2criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student2filling)));
$student2advancedgradingdata = array('rubric' => array('criteria' => $student2criteria));
$grades = array();
$student1gradeinfo = array();
$student1gradeinfo['userid'] = $student1->id;
$student1gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
$student1gradeinfo['attemptnumber'] = -1;
$student1gradeinfo['addattempt'] = true;
$student1gradeinfo['workflowstate'] = 'released';
$student1gradeinfo['plugindata'] = $feedbackpluginparams;
$student1gradeinfo['advancedgradingdata'] = $student1advancedgradingdata;
$grades[] = $student1gradeinfo;
$student2gradeinfo = array();
$student2gradeinfo['userid'] = $student2->id;
$student2gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
$student2gradeinfo['attemptnumber'] = -1;
$student2gradeinfo['addattempt'] = true;
$student2gradeinfo['workflowstate'] = 'released';
$student2gradeinfo['plugindata'] = $feedbackpluginparams;
$student2gradeinfo['advancedgradingdata'] = $student2advancedgradingdata;
$grades[] = $student2gradeinfo;
$result = mod_assign_external::save_grades($instance->id, false, $grades);
// No warnings.
$this->assertEquals(0, count($result));
$student1grade = $DB->get_record('assign_grades',
array('userid' => $student1->id, 'assignment' => $instance->id),
'*',
MUST_EXIST);
$this->assertEquals($student1grade->grade, '50.0');
$student2grade = $DB->get_record('assign_grades',
array('userid' => $student2->id, 'assignment' => $instance->id),
'*',
MUST_EXIST);
$this->assertEquals($student2grade->grade, '100.0');
}
/**
* Test save grades for a team submission
*/
public function test_save_grades_with_group_submission() {
global $DB, $USER, $CFG;
require_once($CFG->dirroot . '/group/lib.php');
$this->resetAfterTest(true);
// Create a course and assignment and users.
$course = self::getDataGenerator()->create_course();
$teacher = self::getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($teacher->id,
$course->id,
$teacherrole->id);
$groupingdata = array();
$groupingdata['courseid'] = $course->id;
$groupingdata['name'] = 'Group assignment grouping';
$grouping = self::getDataGenerator()->create_grouping($groupingdata);
$group1data = array();
$group1data['courseid'] = $course->id;
$group1data['name'] = 'Team 1';
$group2data = array();
$group2data['courseid'] = $course->id;
$group2data['name'] = 'Team 2';
$group1 = self::getDataGenerator()->create_group($group1data);
$group2 = self::getDataGenerator()->create_group($group2data);
groups_assign_grouping($grouping->id, $group1->id);
groups_assign_grouping($grouping->id, $group2->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params['course'] = $course->id;
$params['teamsubmission'] = 1;
$params['teamsubmissiongroupingid'] = $grouping->id;
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = context_module::instance($cm->id);
$assign = new assign($context, $cm, $course);
$student1 = self::getDataGenerator()->create_user();
$student2 = self::getDataGenerator()->create_user();
$student3 = self::getDataGenerator()->create_user();
$student4 = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student1->id,
$course->id,
$studentrole->id);
$this->getDataGenerator()->enrol_user($student2->id,
$course->id,
$studentrole->id);
$this->getDataGenerator()->enrol_user($student3->id,
$course->id,
$studentrole->id);
$this->getDataGenerator()->enrol_user($student4->id,
$course->id,
$studentrole->id);
groups_add_member($group1->id, $student1->id);
groups_add_member($group1->id, $student2->id);
groups_add_member($group1->id, $student3->id);
groups_add_member($group2->id, $student4->id);
$this->setUser($teacher);
$feedbackpluginparams = array();
$feedbackpluginparams['files_filemanager'] = 0;
$feedbackeditorparams = array('text' => '', 'format' => 1);
$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
$grades1 = array();
$student1gradeinfo = array();
$student1gradeinfo['userid'] = $student1->id;
$student1gradeinfo['grade'] = 50;
$student1gradeinfo['attemptnumber'] = -1;
$student1gradeinfo['addattempt'] = true;
$student1gradeinfo['workflowstate'] = 'released';
$student1gradeinfo['plugindata'] = $feedbackpluginparams;
$grades1[] = $student1gradeinfo;
$student2gradeinfo = array();
$student2gradeinfo['userid'] = $student2->id;
$student2gradeinfo['grade'] = 75;
$student2gradeinfo['attemptnumber'] = -1;
$student2gradeinfo['addattempt'] = true;
$student2gradeinfo['workflowstate'] = 'released';
$student2gradeinfo['plugindata'] = $feedbackpluginparams;
$grades1[] = $student2gradeinfo;
$this->setExpectedException('invalid_parameter_exception');
// Expect an exception since 2 grades have been submitted for the same team.
$result = mod_assign_external::save_grades($instance->id, true, $grades1);
$grades2 = array();
$student3gradeinfo = array();
$student3gradeinfo['userid'] = $student3->id;
$student3gradeinfo['grade'] = 50;
$student3gradeinfo['attemptnumber'] = -1;
$student3gradeinfo['addattempt'] = true;
$student3gradeinfo['workflowstate'] = 'released';
$student3gradeinfo['plugindata'] = $feedbackpluginparams;
$grades2[] = $student3gradeinfo;
$student4gradeinfo = array();
$student4gradeinfo['userid'] = $student4->id;
$student4gradeinfo['grade'] = 75;
$student4gradeinfo['attemptnumber'] = -1;
$student4gradeinfo['addattempt'] = true;
$student4gradeinfo['workflowstate'] = 'released';
$student4gradeinfo['plugindata'] = $feedbackpluginparams;
$grades2[] = $student4gradeinfo;
$result = mod_assign_external::save_grades($instance->id, true, $grades2);
// There should be no warnings.
$this->assertEquals(0, count($result));
$student3grade = $DB->get_record('assign_grades',
array('userid' => $student3->id, 'assignment' => $instance->id),
'*',
MUST_EXIST);
$this->assertEquals($student3grade->grade, '50.0');
$student4grade = $DB->get_record('assign_grades',
array('userid' => $student4->id, 'assignment' => $instance->id),
'*',
MUST_EXIST);
$this->assertEquals($student4grade->grade, '75.0');
}
/**
* Test copy_previous_attempt
*/

View File

@ -3,6 +3,12 @@ This files describes API changes in the assign code.
* Added setting sendstudentnotifications to assign DB table with admin defaults. This sets the default value for the
"Notify students" option on the grading forms. This setting can be retrieved via webservices.
=== 2.7 ===
* Web service function mod_assign_save_grade has an additional optional parameter $advancedgradingdata which allows
advanced grading data to be used.
* A new web service function mod_assign_save_grades has been added which allows multiple grades to be processed.
=== 2.6.1 ===
* format_text() is no longer used for formating assignment content to be used in events (assign_submission_onlinetext::save()) or