--- loncom/homework/grades.pm 2002/07/19 20:42:18 1.40
+++ loncom/homework/grades.pm 2003/03/17 19:08:50 1.73
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.40 2002/07/19 20:42:18 ng Exp $
+# $Id: grades.pm,v 1.73 2003/03/17 19:08:50 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -30,7 +30,9 @@
# 7/26 H.K. Ng
# 8/20 Gerd Kortemeyer
# Year 2002
-# June, July 2002 H.K. Ng
+# June-August H.K. Ng
+# Year 2003
+# February, March H.K. Ng
#
package Apache::grades;
@@ -39,1126 +41,593 @@ use Apache::style;
use Apache::lonxml;
use Apache::lonnet;
use Apache::loncommon;
+use Apache::lonnavmaps;
use Apache::lonhomework;
+use Apache::loncoursedata;
use Apache::lonmsg qw(:user_normal_msg);
use Apache::Constants qw(:common);
-#use Time::HiRes qw( gettimeofday tv_interval );
-sub moreinfo {
- my ($request,$reason) = @_;
- $request->print("Unable to process request: $reason");
- if ( $Apache::grades::viewgrades eq 'F' ) {
- $request->print('
');
- }
- return '';
-}
-
-sub verifyreceipt {
- my $request=shift;
- my $courseid=$ENV{'request.course.id'};
-# my $cdom=$ENV{"course.$courseid.domain"};
-# my $cnum=$ENV{"course.$courseid.num"};
- my $receipt=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.
- $ENV{'form.receipt'};
- $receipt=~s/[^\-\d]//g;
- my $symb=$ENV{'form.symb'};
- unless ($symb) {
- $symb=&Apache::lonnet::symbread($ENV{'form.url'});
- }
- if ((&Apache::lonnet::allowed('mgr',$courseid)) && ($symb)) {
- $request->print('
Verifying Submission Receipt '.$receipt.'
');
- my $matches=0;
- my ($classlist) = &getclasslist('all','0');
- foreach my $student ( sort(@{ $$classlist{'all'} }) ) {
- my ($uname,$udom)=split(/\:/,$student);
- if ($receipt eq
- &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {
- $request->print('Matching '.$student.' ');
- $matches++;
- }
- }
- $request->printf('
'.$matches." match%s
",$matches <= 1 ? '' : 'es');
-# needs to print who is matched
+# ----- These first few routines are general use routines.----
+#
+# --- Retrieve the parts that matches stores_\d+ from the metadata file.---
+sub getpartlist {
+ my ($url) = @_;
+ my @parts =();
+ my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+ foreach my $key (@metakeys) {
+ if ( $key =~ m/stores_(\w+)_.*/) {
+ push(@parts,$key);
+ }
}
- return '';
+ return @parts;
}
-sub student_gradeStatus {
- my ($url,$udom,$uname,$partlist) = @_;
- my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
- my %record= &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);
- my %partstatus = ();
- foreach (@$partlist) {
- my ($status,$foo)=split(/_/,$record{"resource.$_.solved"},2);
- $status = 'nothing' if ($status eq '');
- $partstatus{$_} = $status;
- }
- return %partstatus;
+# --- Get the symbolic name of a problem and the url
+sub get_symb_and_url {
+ my ($request) = @_;
+ (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
+ my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
+ if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
+ return ($symb,$url);
}
+# --- Retrieve the fullname for a user. Return lastname, first middle ---
+# --- Generation is attached next to the lastname if it exists. ---
sub get_fullname {
my ($uname,$udom) = @_;
my %name=&Apache::lonnet::get('environment', ['lastname','generation',
'firstname','middlename'],
- $udom,$uname);
+ $udom,$uname);
my $fullname;
my ($tmp) = keys(%name);
if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
- $fullname=$name{'lastname'}.$name{'generation'};
- if ($fullname =~ /[^\s]+/) { $fullname.=', '; }
- $fullname.=$name{'firstname'}.' '.$name{'middlename'};
+ $fullname = &Apache::loncoursedata::ProcessFullName
+ (@name{qw/lastname generation firstname middlename/});
+ } else {
+ &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.
+ '@'.$udom.':'.$tmp);
}
return $fullname;
}
+#--- Get the partlist and the response type for a given problem. ---
+#--- Indicate if a response type is coded handgraded or not. ---
sub response_type {
- my ($url) = shift;
- my $allkeys = &Apache::lonnet::metadata($url,'keys');
- my %seen = ();
- my (@partlist,%handgrade);
- foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
- if (/^\w+response_\d{1,2}.*/) {
- my ($responsetype,$part) = split(/_/,$_,2);
- my ($partid,$respid) = split(/_/,$part);
- $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no');
- next if ($seen{$partid} > 0);
- $seen{$partid}++;
- push @partlist,$partid;
- }
- }
- return \@partlist,\%handgrade;
-}
-
-
-sub listStudents {
- my ($request) = shift;
- my $cdom =$ENV{"course.$ENV{'request.course.id'}.domain"};
- my $cnum =$ENV{"course.$ENV{'request.course.id'}.num"};
- my $getsec =$ENV{'form.section'};
- my $submitonly=$ENV{'form.submitonly'};
-
- my $result='
View Submissions for a Student or a Group of Students
';
- $result.='
';
- $result.='
Resource: '.$ENV{'form.url'}.'
';
- my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
- for (sort keys(%$handgrade)) {
- my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
- $result.='
Part id: '.$_.'
'.
- '
Type: '.$responsetype.'
'.
- '
Handgrade: '.$handgrade.'
';
- }
- $result.='
';
- $request->print($result);
-
- $request->print(<
- View Problem: no
- yes
- Submissions:
- handgrade only
- last sub only
- last sub & parts info
- all details
-
-
-
-
-
-ENDTABLEST
- if ($ENV{'form.url'}) {
- $request->print(''."\n");
- }
- if ($ENV{'form.symb'}) {
- $request->print(''."\n");
- }
- $request->print(''."\n");
-
- my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($getsec,'0');
-
- $result='
'.
- '
'.
- '
Select
Username
'.
- '
Fullname
Domain
';
- foreach (sort(@$partlist)) {
- $result.='
Part ID '.$_.' Status
';
- }
- $request->print($result.'
'."\n");
-
- foreach my $student (sort(@{ $$classlist{$getsec} }) ) {
- my ($uname,$udom) = split(/:/,$student);
- my (%status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname,$partlist);
- my $statusflg = '';
- foreach (keys(%status)) {
- $statusflg = 1 if ($status{$_} ne 'nothing');
- }
- next if ($statusflg eq '' && $submitonly eq 'yes');
-
- if ( $Apache::grades::viewgrades eq 'F' ) {
- $result='
\n";
+#--- Dumps the class list with usernames,list of sections,
+#--- section, ids and fullnames for each user.
+sub getclasslist {
+ my ($getsec,$hideexpired) = @_;
+ my $classlist=&Apache::loncoursedata::get_classlist();
+ # Bail out if we were unable to get the classlist
+ return if (! defined($classlist));
+ #
+ my %sections;
+ my %fullnames;
+ foreach (keys(%$classlist)) {
+ # the following undefs are for 'domain', and 'username' respectively.
+ my (undef,undef,$end,$start,$id,$section,$fullname,$status)=
+ @{$classlist->{$_}};
+ # still a student?
+ if (($hideexpired) && ($status ne 'Active')) {
+ delete ($classlist->{$_});
+ next;
+ }
+ $section = ($section ne '' ? $section : 'no');
+ if ($getsec eq 'all' || $getsec eq $section) {
+ $sections{$section}++;
+ $fullnames{$_}=$fullname;
+ } else {
+ delete($classlist->{$_});
+ }
}
- }
- $result.='
';
- return $result;
+ my %seen = ();
+ my @sections = sort(keys(%sections));
+ return ($classlist,\@sections,\%fullnames);
}
-#FIXME need to look at the metadata spec on what type of data to accept and provide an
-#interface based on that, also do that to above function.
-sub setstudentgrade {
- my ($url,$symb,$courseid,$student,@parts) = @_;
- print "set student grade parts=@parts ";
- my $result ='';
- my ($stuname,$domain) = split(/:/,$student);
- my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname);
- my %newrecord;
-
- foreach my $part (@parts) {
- my ($temp,$part,$type)=split(/_/,$part);
- my $oldscore=$record{"resource.$part.$type"};
- my $newscore=$ENV{"form.GRADE.$student.$part.$type"};
- print "old=$oldscore:new=$newscore: ";
- if ($type eq 'solved') {
- my $update=0;
- if ($newscore eq 'nothing' ) {
- if ($oldscore ne '') {
- $update=1;
- $newscore = '';
- }
- } elsif ($oldscore !~ m/^$newscore/) {
- $update=1;
- $result.="Updating $stuname to $newscore \n";
- if ($newscore eq 'correct') { $newscore = 'correct_by_override'; }
- if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }
- if ($newscore eq 'excused') { $newscore = 'excused'; }
- if ($newscore eq 'ungraded') { $newscore = 'ungraded_attempted'; }
-# if ($newscore eq 'partial') { $newscore = 'correct_partially_by_override'; }
- } else {
- #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore: \n";
- }
- if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }
+#find user domain
+sub finduser {
+ my ($name) = @_;
+ my $domain = '';
+ if ( $Apache::grades::viewgrades eq 'F' ) {
+ my %classlist=&Apache::lonnet::dump('classlist',
+ $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+ $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+ my (@fields) = grep /^$name:/, keys %classlist;
+ ($name, $domain) = split(/:/,$fields[0]);
+ return ($name,$domain);
} else {
- if ($oldscore ne $newscore) {
- $newrecord{"resource.$part.$type"}=$newscore;
- $result.="Updating $student"."'s status for $part.$type to $newscore \n";
- } else {
- #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore: \n";
- }
+ return ($ENV{'user.name'},$ENV{'user.domain'});
}
- }
- if ( scalar(keys(%newrecord)) > 0 ) {
- $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
-# &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$stuname);
-
- $result.="Stored away ".scalar(keys(%newrecord))." elements. \n";
- }
- return $result;
}
-#
-# --------------------------- show submissions of a student, option to grade --------
-sub submission {
- my ($request,$counter,$total) = @_;
-
- (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
- if ($ENV{'form.student'} eq '') { &moreinfo($request,'Need student login id'); return ''; }
- my ($uname,$udom) = &finduser($ENV{'form.student'});
- if ($uname eq '') { &moreinfo($request,'Unable to find student'); return ''; }
-
- my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
- if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
- my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');
-
- # header info
- if ($counter == 0) {
- &sub_page_js($request);
- $request->print('
Submission Record
'.
- ' Resource: '.$url.'');
-
- # option to display problem, only once else it cause problems with the form later
- # since the problem has a form.
- if ($ENV{'form.vProb'} eq 'yes') {
- my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
- $ENV{'request.course.id'});
- my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,
- $ENV{'request.course.id'});
- my $result.='
'."\n";
+
+ $matches++;
+ }
+ }
+ if ($matches == 0) {
+ $string = $title.'No match found for the above receipt.';
+ } else {
+ $string = &jscriptNform($url,$symb).$title.
+ 'The above receipt matches the following student'.
+ ($matches <= 1 ? '.' : 's.')."\n".
+ '
'."\n".
+ '
'."\n".
+ '
Fullname
'."\n".
+ '
Username
'."\n".
+ '
Domain
'."\n".
+ $contents.
+ '
'."\n";
+ }
+ return $string.&show_grading_menu_form($symb,$url);
+}
+
+#--- This is called by a number of programs.
+#--- Called from the Grading Menu - View/Grade an individual student
+#--- Also called directly when one clicks on the subm button
+# on the problem page.
+sub listStudents {
+ my ($request) = shift;
- $ctr = 0;
- my $total = scalar(@nextlist)-1;
- foreach (sort @nextlist) {
- my ($uname,$udom) = split(/:/);
- $ENV{'form.student'} = $uname;
- $ENV{'form.fullname'} = $$fullname{$_};
- &submission($request,$ctr,$total);
- $ctr++;
- }
- if ($total < 0) {
- my $the_end = '
LON-CAPA User Message
'."\n";
- $the_end.='Message: No more students for this section or class.
'."\n";
- $the_end.='Click on the button below to return to the grading menu.
'."\n";
- $the_end.=&show_grading_menu_form ($symb,$url);
- $request->print($the_end);
- }
- return '';
-}
+ my ($symb,$url) = &get_symb_and_url($request);
+ my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"};
+ my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"};
+ my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
+ my $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'};
+
+ my $result;
+ my ($partlist,$handgrade) = &response_type($url);
+ for (sort keys(%$handgrade)) {
+ my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
+ $ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');
+ $result.='
';
- return $result;
+#---- Called from the listStudents routine
+# Displays the submissions for one student or a group of students
+sub processGroup {
+ my ($request) = shift;
+ my $ctr = 0;
+ my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}}
+ : ($ENV{'form.stuinfo'}));
+ my $total = scalar(@stuchecked)-1;
+
+ foreach (@stuchecked) {
+ my ($uname,$udom,$fullname) = split(/:/);
+ $ENV{'form.student'} = $uname;
+ $ENV{'form.userdom'} = $udom;
+ $ENV{'form.fullname'} = $fullname;
+ &submission($request,$ctr,$total);
+ $ctr++;
+ }
+ return '';
}
+#------------------------------------------------------------------------------------
+#
+#-------------------------- Next few routines handles grading by student, essentially
+# handles essay response type problem/part
+#
+#--- Javascript to handle the submission page functionality ---
sub sub_page_js {
- my $request = shift;
- $request->print(<print(<
- function updateRadio(radioButton,formtextbox,formsel,scores) {
- var pts = formtextbox.value;
- var resetbox =false;
- if (isNaN(pts) || pts < 0) {
- alert("A number equal or greater than 0 is expected. Entered value = "+pts);
+ function updateRadio(formname,id,weight) {
+ var gradeBox = eval("formname.GD_BOX"+id);
+ var radioButton = eval("formname.RADVAL"+id);
+ var oldpts = eval("formname.oldpts"+id+".value");
+ var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
+ gradeBox.value = pts;
+ var resetbox = false;
+ if (isNaN(pts) || pts < 0) {
+ alert("A number equal or greater than 0 is expected. Entered value = "+pts);
+ for (var i=0; i weight) {
+ var resp = confirm("You entered a value ("+pts+
+ ") greater than the weight for the part. Accept?");
+ if (resp == false) {
+ gradeBox.value = "";
+ return;
+ }
+ }
+
for (var i=0; i
+SUBJAVASCRIPT
+}
- function updateSelect(formsel) {
- formsel[0].selected = true;
- return;
- }
+#--- javascript for essay type problem --
+sub sub_page_kw_js {
+ my $request = shift;
+ $request->print(<
//===================== Show list of keywords ====================
function keywords(keyform) {
@@ -1166,6 +635,20 @@ sub sub_page_js {
var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",keywds);
if (nret==null) return;
keyform.value = nret;
+
+ document.SCORE.refresh.value = "on";
+ if (document.SCORE.keywords.value != "") {
+ document.SCORE.submit();
+ }
+ return;
+ }
+
+//===================== Script to view submitted by ==================
+ function viewSubmitter(submitter) {
+ document.SCORE.refresh.value = "on";
+ document.SCORE.NCT.value = "1";
+ document.SCORE.unamedom0.value = submitter;
+ document.SCORE.submit();
return;
}
@@ -1176,13 +659,17 @@ sub sub_page_js {
else return;
var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
if (cleantxt=="") {
- alert("Select a word or group of words from document and then click this link.");
+ alert("Please select a word or group of words from document and then click this link.");
return;
}
var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
if (nret==null) return;
var curlist = document.SCORE.keywords.value;
document.SCORE.keywords.value = curlist+" "+nret;
+ document.SCORE.refresh.value = "on";
+ if (document.SCORE.keywords.value != "") {
+ document.SCORE.submit();
+ }
return;
}
@@ -1193,7 +680,6 @@ sub sub_page_js {
var subject = msgform.msgsub.value;
var rtrchk = eval("document.SCORE.includemsg"+usrctr);
var msgchk = rtrchk.value;
-// alert("checked=>"+msgchk);
re = /msgsub/;
var shwsel = "";
if (re.test(msgchk)) { shwsel = "checked" }
@@ -1216,7 +702,7 @@ sub sub_page_js {
}
function savedMsgHeader(Nmsg,usrctr,fullname) {
- var height = 30*Nmsg+250;
+ var height = 50*Nmsg+250;
var scrollbar = "no";
if (height > 600) {
height = 600;
@@ -1255,7 +741,6 @@ sub sub_page_js {
pWin.document.write(" var includemsg = eval(\\"opener.document.SCORE.includemsg\\"+usrctr);");
pWin.document.write(" includemsg.value = msgchk;");
-// pWin.document.write(" alert(\\"slected=\\"+msgchk)");
pWin.document.write(" self.close()");
pWin.document.write("}");
@@ -1277,21 +762,21 @@ sub sub_page_js {
pWin.document.write("
");
pWin.document.write("
Subject
");
pWin.document.write("
");
- pWin.document.write("
");
+ pWin.document.write("
");
}
-function displaySavedMsg(ctr,msg,shwsel) {
+ function displaySavedMsg(ctr,msg,shwsel) {
pWin.document.write("
");
pWin.document.write("
"+ctr+"
");
pWin.document.write("
");
- pWin.document.write("
");
+ pWin.document.write("
");
}
function newMsg(newmsg,shwsel) {
pWin.document.write("