MDL-38105 gradingform_rubric: option to lock 0 level for rubrics

this also allows using negative scores
This commit is contained in:
Marina Glancy 2016-08-09 13:43:02 +08:00
parent 35d5053ba2
commit dfc261be66
8 changed files with 190 additions and 35 deletions

View File

@ -56,8 +56,8 @@ if ($mform->is_cancelled()) {
// If we do not go back to management url and the minscore warning needs to be displayed, display it during redirection.
$warning = null;
if (!empty($data->returnurl)) {
if (($scores = $controller->get_min_max_score()) && $scores['minscore'] <> 0) {
if (!empty($data->returnurl) && $data->returnurl !== $manager->get_management_url()->out(false)) {
if (empty($data->rubric['options']['lockzeropoints']) && ($scores = $controller->get_min_max_score()) && $scores['minscore'] <> 0) {
$warning = get_string('zerolevelsabsent', 'gradingform_rubric').'<br>'.
html_writer::link($manager->get_management_url(), get_string('back'));
}

View File

@ -45,7 +45,7 @@ $string['err_nocriteria'] = 'Rubric must contain at least one criterion';
$string['err_nodefinition'] = 'Level definition can not be empty';
$string['err_nodescription'] = 'Criterion description can not be empty';
$string['err_novariations'] = 'Criterion levels cannot all be worth the same number of points';
$string['err_scoreformat'] = 'Number of points for each level must be a valid non-negative number';
$string['err_scoreformat'] = 'Number of points for each level must be a valid number';
$string['err_totalscore'] = 'Maximum number of points possible when graded by the rubric must be more than zero';
$string['gradingof'] = '{$a} grading';
$string['level'] = 'Level {$a->definition}, {$a->score} points.';
@ -53,6 +53,11 @@ $string['leveldelete'] = 'Delete level {$a}';
$string['leveldefinition'] = 'Level {$a} definition';
$string['levelempty'] = 'Click to edit level';
$string['levelsgroup'] = 'Levels group';
$string['lockzeropoints'] = 'When converting rubric score to points/scale assume that minimum number of points is 0';
$string['lockzeropoints_help'] = 'Locking the minimum number of points will allow to create rubrics without 0-levels. This may also mean that 0% grade on this rubric is not possible to achieve.<br />
The maximum score in the rubric is always converted to the maximum grade.<br />
When this setting is unchecked, the minimum possible score for this rubric will be converted to the minimum grade available in the module (which is zero unless the scale is used).<br />
<a href="https://docs.moodle.org/en/Rubrics#Grade_calculation">Explanation of rubrics grade calculation</a>';
$string['name'] = 'Name';
$string['needregrademessage'] = 'The rubric definition was changed after this student had been graded. The student can not see this rubric until you check the rubric and update the grade.';
$string['pluginname'] = 'Rubric';
@ -66,8 +71,9 @@ $string['rubric'] = 'Rubric';
$string['rubricmapping'] = 'Score to grade mapping rules';
$string['rubricmappingexplained'] = 'The minimum possible score for this rubric is <b>{$a->minscore} points</b> and it will be converted to the minimum grade available in this module (which is zero unless the scale is used).
The maximum score <b>{$a->maxscore} points</b> will be converted to the maximum grade.<br />
Intermediate scores will be converted respectively and rounded to the nearest available grade.<br />
If a scale is used instead of a grade, the score will be converted to the scale elements as if they were consecutive integers.';
Intermediate scores will be converted respectively.<br />
If a scale is used for grading, the score will be rounded and converted to the scale elements as if they were consecutive integers.<br><br>
You can change the method of grade calculation in the Rubic options section below.';
$string['rubricnotcompleted'] = 'Please choose something for each criterion';
$string['rubricoptions'] = 'Rubric options';
$string['rubricstatus'] = 'Current rubric status';

View File

@ -143,8 +143,16 @@ class gradingform_rubric_controller extends gradingform_controller {
// reload the definition from the database
$currentdefinition = $this->get_definition(true);
// update rubric data
$haschanges = array();
// Check if 'lockzeropoints' option has changed.
$newlockzeropoints = $newdefinition->rubric['options']['lockzeropoints'];
$currentoptions = $this->get_options();
if ((bool)$newlockzeropoints != (bool)$currentoptions['lockzeropoints']) {
$haschanges[3] = true;
}
// update rubric data
if (empty($newdefinition->rubric['criteria'])) {
$newcriteria = array();
} else {
@ -203,10 +211,7 @@ class gradingform_rubric_controller extends gradingform_controller {
}
foreach ($levelsdata as $levelid => $level) {
if (isset($level['score'])) {
$level['score'] = (float)$level['score'];
if ($level['score']<0) {
$level['score'] = 0;
}
$level['score'] = unformat_float($level['score']);
}
if (preg_match('/^NEWID\d+$/', $levelid)) {
// insert level into DB
@ -354,6 +359,7 @@ class gradingform_rubric_controller extends gradingform_controller {
public static function get_default_options() {
$options = array(
'sortlevelsasc' => 1,
'lockzeropoints' => 1,
'alwaysshowdefinition' => 1,
'showdescriptionteacher' => 1,
'showdescriptionstudent' => 1,
@ -368,6 +374,9 @@ class gradingform_rubric_controller extends gradingform_controller {
/**
* Gets the options of this rubric definition, fills the missing options with default values
*
* The only exception is 'lockzeropoints' - if other options are present in the json string but this
* one is absent, this means that the rubric was created before Moodle 3.2 and the 0 value should be used.
*
* @return array
*/
public function get_options() {
@ -377,6 +386,11 @@ class gradingform_rubric_controller extends gradingform_controller {
foreach ($thisoptions as $option => $value) {
$options[$option] = $value;
}
if (!array_key_exists('lockzeropoints', $thisoptions)) {
// Rubrics created before Moodle 3.2 don't have 'lockzeropoints' option. In this case they should not
// assume default value 1 but use "legacy" value 0.
$options['lockzeropoints'] = 0;
}
}
return $options;
}
@ -522,7 +536,10 @@ class gradingform_rubric_controller extends gradingform_controller {
$rubric .= $output->box($this->get_formatted_description(), 'gradingform_rubric-description');
}
if (has_capability('moodle/grade:managegradingforms', $page->context)) {
$rubric .= $output->display_rubric_mapping_explained($this->get_min_max_score());
if (!$options['lockzeropoints']) {
// Warn about using grade calculation method where minimum number of points is flexible.
$rubric .= $output->display_rubric_mapping_explained($this->get_min_max_score());
}
$rubric .= $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW, 'rubric');
} else {
$rubric .= $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW_GRADED, 'rubric');
@ -889,11 +906,19 @@ class gradingform_rubric_instance extends gradingform_instance {
foreach ($grade['criteria'] as $id => $record) {
$curscore += $this->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$record['levelid']]['score'];
}
$gradeoffset = ($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*($maxgrade-$mingrade);
if ($this->get_controller()->get_allow_grade_decimals()) {
return $gradeoffset + $mingrade;
$allowdecimals = $this->get_controller()->get_allow_grade_decimals();
$options = $this->get_controller()->get_options();
if ($options['lockzeropoints']) {
// Grade calculation method when 0-level is locked.
$grade = max($mingrade, $curscore / $scores['maxscore'] * $maxgrade);
return $allowdecimals ? $grade : round($grade, 0);
} else {
// Alternative grade calculation method.
$gradeoffset = ($curscore - $scores['minscore']) / ($scores['maxscore'] - $scores['minscore']) * ($maxgrade - $mingrade);
return ($allowdecimals ? $gradeoffset : round($gradeoffset, 0)) + $mingrade;
}
return round($gradeoffset, 0) + $mingrade;
}
/**

View File

@ -470,6 +470,9 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
$html .= html_writer::tag('label', get_string($option, 'gradingform_rubric'), array('for' => $attrs['id']));
break;
}
if (get_string_manager()->string_exists($option.'_help', 'gradingform_rubric')) {
$html .= $this->help_icon($option, 'gradingform_rubric');
}
$html .= html_writer::end_tag('div'); // .option
}
$html .= html_writer::end_tag('div'); // .options
@ -636,10 +639,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
if ($scores['minscore'] <> 0) {
$html .= $this->output->notification(get_string('zerolevelsabsent', 'gradingform_rubric'), 'error');
}
$html .= $this->box(
html_writer::tag('h4', get_string('rubricmapping', 'gradingform_rubric')).
html_writer::tag('div', get_string('rubricmappingexplained', 'gradingform_rubric', (object)$scores))
, 'generalbox rubricmappingexplained');
$html .= $this->output->notification(get_string('rubricmappingexplained', 'gradingform_rubric', (object)$scores), 'info');
return $html;
}
}

View File

@ -232,29 +232,30 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
'score' => 0,
);
foreach ($criterion['levels'] as $lastlevel) {
if (isset($lastlevel['score']) && $level['score'] < $lastlevel['score'] + 1) {
$level['score'] = $lastlevel['score'] + 1;
if (isset($lastlevel['score'])) {
$level['score'] = max($level['score'], ceil(unformat_float($lastlevel['score'])) + 1);
}
}
$this->nonjsbuttonpressed = true;
}
if (!array_key_exists('delete', $level)) {
$score = unformat_float($level['score'], true);
if ($withvalidation) {
if (!strlen(trim($level['definition']))) {
$errors['err_nodefinition'] = 1;
$level['error_definition'] = true;
}
if (!preg_match('#^[\+]?\d*$#', trim($level['score'])) && !preg_match('#^[\+]?\d*[\.,]\d+$#', trim($level['score']))) {
if ($score === null || $score === false) {
$errors['err_scoreformat'] = 1;
$level['error_score'] = true;
}
}
$levels[$levelid] = $level;
if ($minscore === null || (float)$level['score'] < $minscore) {
$minscore = (float)$level['score'];
if ($minscore === null || $score < $minscore) {
$minscore = $score;
}
if ($maxscore === null || (float)$level['score'] > $maxscore) {
$maxscore = (float)$level['score'];
if ($maxscore === null || $score > $maxscore) {
$maxscore = $score;
}
} else {
$this->nonjsbuttonpressed = true;
@ -313,8 +314,10 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
// create validation error string (if needed)
if ($withvalidation) {
if ($overallminscore == $overallmaxscore) {
$errors['err_novariations'] = 1;
if (!$return['options']['lockzeropoints']) {
if ($overallminscore == $overallmaxscore) {
$errors['err_novariations'] = 1;
}
}
if (count($errors)) {
$rv = array();

View File

@ -58,7 +58,7 @@ Feature: Rubrics can be created and edited
And I complete the advanced grading form with these values:
| Feedback comments | In general... work harder... |
# Checking that the user grade is correct.
And I should see "58.33" in the "Student 1" "table_row"
And I should see "65" in the "Student 1" "table_row"
# Updating the user grade.
And I go to "Student 1" "Test assignment 1 name" activity advanced grading page
And I grade by filling the rubric with:
@ -68,13 +68,13 @@ Feature: Rubrics can be created and edited
#And the level with "50" points was previously selected for the rubric criterion "Criterion 1"
#And the level with "20" points is selected for the rubric criterion "Criterion 1"
And I save the advanced grading form
And I should see "22.62" in the "Student 1" "table_row"
And I should see "35" in the "Student 1" "table_row"
And I log out
# Viewing it as a student.
And I log in as "student1"
And I follow "Course 1"
And I follow "Test assignment 1 name"
And I should see "22.62" in the ".feedback" "css_element"
And I should see "35" in the ".feedback" "css_element"
And I should see "Rubric test description" in the ".feedback" "css_element"
And I should see "In general... work harder..."
And the level with "10" points is selected for the rubric criterion "Criterion 2"
@ -97,7 +97,7 @@ Feature: Rubrics can be created and edited
And I log in as "student1"
And I follow "Course 1"
And I follow "Test assignment 1 name"
And I should see "22.62" in the ".feedback" "css_element"
And I should see "35" in the ".feedback" "css_element"
And the level with "20" points is selected for the rubric criterion "Criterion 1"
And I log out
# Editing a rubric with significant changes.
@ -105,7 +105,7 @@ Feature: Rubrics can be created and edited
And I follow "Course 1"
And I go to "Test assignment 1 name" advanced grading definition page
And I click on "Move down" "button" in the "Criterion 2" "table_row"
And I replace "1" rubric level with "11" in "Criterion 1" criterion
And I replace "1" rubric level with "60" in "Criterion 1" criterion
And I press "Save"
And I should see "You are about to save significant changes to a rubric that has already been used for grading. The gradebook value will be unchanged, but the rubric will be hidden from students until their item is regraded."
And I press "Continue"
@ -114,7 +114,7 @@ Feature: Rubrics can be created and edited
And I log in as "student1"
And I follow "Course 1"
And I follow "Test assignment 1 name"
And I should see "22.62" in the ".feedback" "css_element"
And I should see "35" in the ".feedback" "css_element"
And the level with "20" points is not selected for the rubric criterion "Criterion 1"
And I log out
# Regrade student.
@ -129,7 +129,7 @@ Feature: Rubrics can be created and edited
And I log in as "student1"
And I follow "Course 1"
And I follow "Test assignment 1 name"
And I should see "12.16" in the ".feedback" "css_element"
And I should see "31.82" in the ".feedback" "css_element"
And the level with "20" points is not selected for the rubric criterion "Criterion 1"
# Hide all rubric info for students
And I log out

View File

@ -0,0 +1,56 @@
@gradingform @gradingform_rubric @javascript
Feature: Converting rubric score to grades
In order to use and refine rubrics to grade students
As a teacher
I need to be able to use different grade settings
Scenario Outline:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "scales" exist:
| name | scale |
| Test scale 1 | Disappointing, Good, Very good, Excellent |
And the following "activities" exist:
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
| assign | Test assignment 1 | Test | C1 | assign1 | <grade> | rubric |
When I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I go to "Test assignment 1" advanced grading definition page
And I set the following fields to these values:
| Name | Assignment 1 rubric |
| Description | Rubric test description |
| When converting rubric score to points/scale assume that minimum number of points is 0 | <lockzeropoints> |
And I define the following rubric:
| Criterion 1 | Level 11 | 20 | Level 12 | 25 | Level 13 | 40 | Level 14 | 50 |
| Criterion 2 | Level 21 | 20 | Level 22 | 25 | Level 23 | 30 | | |
| Criterion 3 | Level 31 | 10 | Level 32 | 20 | | | | |
And I press "Save rubric and make it ready"
# Grading a student.
And I go to "Student 1" "Test assignment 1" activity advanced grading page
And I grade by filling the rubric with:
| Criterion 1 | 25 | |
| Criterion 2 | 20 | |
| Criterion 3 | 10 | |
And I save the advanced grading form
# Checking that the user grade is correct.
And I should see "<studentgrade>" in the "student1@example.com" "table_row"
And I log out
Examples:
| grade | lockzeropoints | studentgrade |
| 100 | 1 | 55.00 |
| 70 | 1 | 38.50 |
| Test scale 1 | 1 | Good |
| 100 | | 10.00 |
| 70 | | 7.00 |
| Test scale 1 | | Disappointing |

View File

@ -0,0 +1,65 @@
@gradingform @gradingform_rubric @javascript
Feature: Rubrics can have levels with negative scores
In order to use and refine rubrics to grade students
As a teacher
I need to be able to penalise for very wrong submissions
Scenario: Using negative levels in rubrics
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "scales" exist:
| name | scale |
| Test scale 1 | Disappointing, Good, Very good, Excellent |
And the following "activities" exist:
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
| assign | Test assignment 1 | Test | C1 | assign1 | 100 | rubric |
When I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I go to "Test assignment 1" advanced grading definition page
And I set the following fields to these values:
| Name | Assignment 1 rubric |
| Description | Rubric test description |
And I define the following rubric:
| Criterion 1 | Did not try | -11 | Level 12 | 25 | Level 13 | 40 | Level 14 | 50 |
| Criterion 2 | Very bad | -20 | Level 22 | 25 | Level 23 | 30 | | |
| Criterion 3 | Level 31 | 10 | Level 32 | 20 | | | | |
And I press "Save rubric and make it ready"
# Grading a student.
And I go to "Student 1" "Test assignment 1" activity advanced grading page
And I grade by filling the rubric with:
| Criterion 1 | 25 | |
| Criterion 2 | 30 | |
| Criterion 3 | 10 | |
And I save the advanced grading form
And I go to "Student 2" "Test assignment 1" activity advanced grading page
And I grade by filling the rubric with:
| Criterion 1 | 25 | |
| Criterion 2 | -20 | |
| Criterion 3 | 10 | |
And I save the advanced grading form
And I go to "Student 3" "Test assignment 1" activity advanced grading page
And I grade by filling the rubric with:
| Criterion 1 | -11 | |
| Criterion 2 | -20 | |
| Criterion 3 | 10 | |
And I save the advanced grading form
# Checking that the user grade is correct.
And I should see "65.00" in the "student1@example.com" "table_row"
And I should see "15.00" in the "student2@example.com" "table_row"
And I should see "0.00" in the "student3@example.com" "table_row"
And I should not see "-" in the "student3@example.com" "table_row"
And I log out