--- loncom/homework/grades.pm 2001/08/06 16:37:58 1.12
+++ loncom/homework/grades.pm 2002/08/02 21:10:03 1.44
@@ -1,14 +1,37 @@
-# The LON-CAPA grading handler.
+# The LearningOnline Network with CAPA
+# The LON-CAPA Grading handler
#
-# Handles the viewing of grades.
+# $Id: grades.pm,v 1.44 2002/08/02 21:10:03 ng Exp $
#
-# YEAR=2001
-# 2/7,2/9,2/13,4/16,4/17,5/1 Guy Albertelli
+# Copyright Michigan State University Board of Trustees
+#
+# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
+#
+# LON-CAPA is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LON-CAPA is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LON-CAPA; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# /home/httpd/html/adm/gpl.txt
+#
+# http://www.lon-capa.org/
+#
+# 2/9,2/13 Guy Albertelli
# 6/8 Gerd Kortemeyer
-# 7/26 Guy Albertelli
-# 7/27 H.K. Ng
-# 7/30 Guy Albertelli
-# 8/6 Scott Harrison
+# 7/26 H.K. Ng
+# 8/20 Gerd Kortemeyer
+# Year 2002
+# June-August H.K. Ng
+#
package Apache::grades;
use strict;
@@ -17,415 +40,2190 @@ use Apache::lonxml;
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonhomework;
+use Apache::lonmsg qw(:user_normal_msg);
use Apache::Constants qw(:common);
-# ======================================================== Get more information
+# ----- 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_([0-9]+)_.*/) {
+ push(@parts,$key);
+ }
+ }
+ return @parts;
+}
+
+# --- 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);
+ 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'};
+ }
+ 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+.*/) {
+ 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;
+}
+
+#--- Prints a message on screen if a user did something wrong
+#--- Operator error ---
+sub userError {
+ my ($request, $reason, $step) = @_;
+ $request->print('
LON-CAPA User Error
'."\n");
+ $request->print('Reason: '.$reason.'
'."\n");
+ $request->print('Step: '.($step ne '' ? $step : 'Use your browser back button to correct')
+ .'
'."\n");
+ return '';
+}
+
+#--- Dumps the class list with usernames,list of sections,
+#--- section, ids and fullnames for each user.
+sub getclasslist {
+ my ($getsec,$hideexpired) = @_;
+ my $now = time;
+ my %classlist=&Apache::lonnet::dump('classlist',
+ $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+ $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+ # codes to check for fields in the classlist
+ # should contain end:start:id:section:fullname
+ for (keys %classlist) {
+ my (@fields) = split(/:/,$classlist{$_});
+ %classlist = &reformat_classlist(\%classlist) if (scalar(@fields) <= 2);
+ last;
+ }
+
+ my (@holdsec,@sections,%allids,%stusec,%fullname);
+ foreach (keys(%classlist)) {
+ my ($end,$start,$id,$section,$fullname)=split(/:/,$classlist{$_});
+ # still a student?
+ if (($hideexpired) && ($end) && ($end < $now)) {
+ next;
+ }
+ $section = ($section ne '' ? $section : 'no');
+ push @holdsec,$section;
+ if ($getsec eq 'all' || $getsec eq $section) {
+ push (@{ $classlist{$getsec} }, $_);
+ $allids{$_} =$id;
+ $stusec{$_} =$section;
+ $fullname{$_}=$fullname;
+ }
+ }
+ my %seen = ();
+ foreach my $item (@holdsec) {
+ push (@sections, $item) unless $seen{$item}++;
+ }
+ return (\%classlist,\@sections,\%allids,\%stusec,\%fullname);
+}
+
+# add id, section and fullname to the classlist.db
+# done to maintain backward compatibility with older versions
+sub reformat_classlist {
+ my ($classlist) = shift;
+ foreach (sort keys(%$classlist)) {
+ my ($unam,$udom) = split(/:/);
+ my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});
+ my $fullname = &get_fullname ($unam,$udom);
+ my %userid = &Apache::lonnet::idrget($udom,($unam));
+ $$classlist{$_} = $$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname;
+ }
+ my $putresult = &Apache::lonnet::put
+ ('classlist',\%$classlist,
+ $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+ $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+
+ return %$classlist;
+}
+
+#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 {
+ return ($ENV{'user.name'},$ENV{'user.domain'});
+ }
+}
+
+#--- Prompts a user to enter a username.
sub moreinfo {
my ($request,$reason) = @_;
$request->print("Unable to process request: $reason");
if ( $Apache::grades::viewgrades eq 'F' ) {
$request->print('');
}
return '';
}
-# ========================================= Displays the class list of students
+#--- Retrieve the grade status of a student for all the parts
+sub student_gradeStatus {
+ my ($url,$symb,$udom,$uname,$partlist) = @_;
+ 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;
+ my $subkey = "resource.$_.submitted_by";
+ $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
+ }
+ return %partstatus;
+}
+
+
+#------------------ End of general use routines --------------------
+#-------------------------------------------------------------------
+
+#------------------------------------ Receipt Verification Routines
+#--- Check whether a receipt number is valid.---
+sub verifyreceipt {
+ my $request = shift;
+
+ my $courseid = $ENV{'request.course.id'};
+ my $receipt = unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.
+ $ENV{'form.receipt'};
+ $receipt =~ s/[^\-\d]//g;
+ my $url = $ENV{'form.url'};
+ my $symb = $ENV{'form.symb'};
+ unless ($symb) {
+ $symb = &Apache::lonnet::symbread($url);
+ }
+
+ my $jscript=''."\n";
+ $jscript.= ''."\n";
+
+ my $title.='
Verifying Submission Receipt '.
+ $receipt.'
'."\n".
+ 'Resource: '.$ENV{'form.url'}.'
'."\n";
+
+ my ($string,$contents,$matches) = ('','',0);
+ my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
+
+ foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+ my ($uname,$udom)=split(/\:/);
+ if ($receipt eq
+ &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {
+ $contents.='
'."\n";
+
+ $matches++;
+ }
+ }
+ if ($matches == 0) {
+ $string = $title.'No match found for the above receipt.';
+ } else {
+ $string = $jscript.$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;
- my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});
- my $chome = $ENV{"course.$ENV{'request.course.id'}.home"};
- $request->print ("Found $cdom:$cnum:$chome ");
- my (%classlist) = &getclasslist($cdom,$cnum,$chome,'0');
- foreach my $student ( sort(@{ $classlist{'allids'} }) ) {
- my ($sname,$sdom) = split(/:/,$student);
- my $reply = &Apache::lonnet::reply('get:'.$sdom.':'.$sname.
- ':environment:lastname&generation&firstname&middle'.
- 'name',&Apache::lonnet::homeserver($sname,$sdom));
- print "reply=$reply ";
- my (@nameparts) = split /&/,$reply;
-# my $sfullname = $Apache::lonnet::unescape($nameparts[0]);
+ 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='
'.
+ '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{$_});
+ $ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');
+ $result.='
Part '.(split(/_/))[0].'
'.
+ '
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
Fullname
'.
+ '
Username
Domain
';
+ foreach (sort(@$partlist)) {
+ $result.='
Part '.(split(/_/))[0].' Status
';
+ }
+ $request->print($result.'
'."\n");
+
+ foreach my $student (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+ my ($uname,$udom) = split(/:/,$student);
+ my (%status) = &student_gradeStatus($ENV{'form.url'},
+ $ENV{'form.symb'},$udom,$uname,$partlist);
+ my $statusflg = '';
+ foreach (keys(%status)) {
+ $statusflg = 1 if ($status{$_} ne 'nothing');
+ my ($foo,$partid,$foo1) = split(/\./,$_);
+ if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
+ $statusflg = '';
+ $request->print('');
+ }
+ }
+ next if ($statusflg eq '' && $submitonly eq 'yes');
+
if ( $Apache::grades::viewgrades eq 'F' ) {
- $request->print('');
+ $result='
'.
+ '
'."\n".
+ '
'.$$fullname{$student}.'
'."\n".
+ '
'.$uname.'
'."\n".
+ '
'.$udom.'
'."\n";
+
+ foreach (sort keys(%status)) {
+ next if (/^resource.*?submitted_by$/);
+ $result.='
'.$status{$_}.'
'."\n";
+ }
+ $request->print($result.'
'."\n");
}
}
+ $request->print('
');
+ $request->print('');
+ return '';
}
-# ========== Finds a user based on a name substring (returns a 2 element array)
-#FIXME - needs to handle multiple matches
-sub finduser {
- my ($name) = @_;
- my $domain = '';
- if ( $Apache::grades::viewgrades eq 'F' ) {
- #get classlist
- my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});
- my $chome = $ENV{"course.$ENV{'request.course.id'}.home"};
- #print "Found $cdom:$cnum:$chome ";
- my (%classlist) = &getclasslist($cdom,$cnum,$chome,'0');
- foreach my $student ( sort(@{ $classlist{'allids'} }) ) {
- my ($posname,$posdomain) = split(/:/,$student);
- if ($posname =~ $name) {
- $name = $posname;
- $domain = $posdomain;
- last;
+#---- 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;
+ if ($stuchecked[0] eq '') {
+ &userError($request,'No student was selected for viewing/grading.');
+ return;
+ }
+ 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(<
+ function updateRadio(radioButton,formtextbox,formsel,scores,weight) {
+ 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);
+ 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) {
+ formtextbox.value = "";
+ return;
+ }
+ }
+
+ for (var i=0; i 600) {
+ height = 600;
+ scrollbar = "yes";
+ }
+/* if (window.pWin)
+ window.pWin.close(); */
+ pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx=70,screeny=75,width=600,height='+height);
+ pWin.document.write("");
+ pWin.document.write("Message Central");
+
+ pWin.document.write("
+SUBJAVASCRIPT
+}
+
+
+# --------------------------- 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) = ($ENV{'form.student'},$ENV{'form.userdom'});
+ ($uname,$udom) = &finduser($uname) if $udom eq '';
+ $ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq '';
+# 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' : '');
+ $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes';
+ my ($classlist,$seclist,$ids,$stusec,$fullname);
+
+ # 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.='
';
+ $result.='
';
+ $result.=' View of the problem for '.$ENV{'form.fullname'}.
+ '
';
- return $result;
+
+ # Keywords sorted in alphabatical order
+ my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};
+ my %keyhash = ();
+ $ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g;
+ $ENV{'form.keywords'} =~ s/^\s+|\s+$//;
+ my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'}));
+ $ENV{'form.keywords'} = join(' ',@keywords);
+ $keyhash{$symb.'_keywords'} = $ENV{'form.keywords'};
+ $keyhash{$symb.'_subject'} = $ENV{'form.msgsub'};
+ $keyhash{$loginuser.'_kwclr'} = $ENV{'form.kwclr'};
+ $keyhash{$loginuser.'_kwsize'} = $ENV{'form.kwsize'};
+ $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'};
+
+ # message center - Order of message gets changed. Blank line is eliminated.
+ # New messages are saved in ENV for the next student.
+ # All messages are saved in nohist_handgrade.db
+ my ($ctr,$idx) = (1,1);
+ while ($ctr <= $ENV{'form.savemsgN'}) {
+ if ($ENV{'form.savemsg'.$ctr} ne '') {
+ $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr};
+ $idx++;
+ }
+ $ctr++;
+ }
+ $ctr = 0;
+ while ($ctr < $ngrade) {
+ if ($ENV{'form.newmsg'.$ctr} ne '') {
+ $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};
+ $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};
+ $idx++;
+ }
+ $ctr++;
+ }
+ $ENV{'form.savemsgN'} = --$idx;
+ $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'};
+ my $putresult = &Apache::lonnet::put
+ ('nohist_handgrade',\%keyhash,
+ $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
+ $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+
+ # Called by Save & Refresh from Highlight Attribute Window
+ if ($ENV{'form.refresh'} eq 'on') {
+ my $ctr = 0;
+ $ENV{'form.NTSTU'}=$ngrade;
+ while ($ctr < $ngrade) {
+ ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$ENV{'form.unamedom'.$ctr});
+ &submission($request,$ctr,$ngrade-1);
+ $ctr++;
+ }
+ return '';
+ }
+
+ # Get the next/previous one or group of students
+ my $firststu = $ENV{'form.unamedom0'};
+ my $laststu = $ENV{'form.unamedom'.($ngrade-1)};
+ $ctr = 2;
+ while ($laststu eq '') {
+ $laststu = $ENV{'form.unamedom'.($ngrade-$ctr)};
+ $ctr++;
+ $laststu = $firststu if ($ctr > $ngrade);
+ }
+
+ my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
+ my (@parsedlist,@nextlist);
+ my ($nextflg) = 0;
+ foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+ if ($nextflg == 1 && $button =~ /Next$/) {
+ push @parsedlist,$_;
+ }
+ $nextflg = 1 if ($_ eq $laststu);
+ if ($button eq 'Previous') {
+ last if ($_ eq $firststu);
+ push @parsedlist,$_;
+ }
+ }
+ $ctr = 0;
+ my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
+ @parsedlist = reverse @parsedlist if ($button eq 'Previous');
+ foreach my $student (@parsedlist) {
+ my ($uname,$udom) = split(/:/,$student);
+ if ($ENV{'form.submitonly'} eq 'yes') {
+ my (%status) = &student_gradeStatus($ENV{'form.url'},$symb,$udom,$uname,$partlist) ;
+ my $statusflg = '';
+ foreach (keys(%status)) {
+ $statusflg = 1 if ($status{$_} ne 'nothing');
+ my ($foo,$partid,$foo1) = split(/\./);
+ $statusflg = '' if ($status{'resource.'.$partid.'.submitted_by'} ne '');
+ }
+ next if ($statusflg eq '');
+ }
+ push @nextlist,$student if ($ctr < $ntstu);
+ $ctr++;
+ }
+
+ $ctr = 0;
+ my $total = scalar(@nextlist)-1;
+
+ foreach (sort @nextlist) {
+ my ($uname,$udom,$submitter) = split(/:/);
+ $ENV{'form.student'} = $uname;
+ $ENV{'form.userdom'} = $udom;
+ $ENV{'form.fullname'} = $$fullname{$_};
+# $ENV{'form.'.$_.':submitted_by'} = $submitter;
+# print "submitter=$ENV{'form.'.$_.':submitted_by'}= $submitter: ";
+ &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 '';
}
-# ========================================================== Sets student grade
-#FIXME need to look at the meatdata 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) = @_;
- my $result ='';
- my ($stuname,$domain) = split(/:/,$student);
- my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname);
+#---- Save the score and award for each student, if changed
+sub saveHandGrade {
+ my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter) = @_;
+ my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$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"};
- 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'; }
- } else {
- #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore: \n";
- }
- if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }
+ foreach (split(/:/,$ENV{'form.partlist'.$newflg})) {
+ if ($ENV{'form.GD_SEL'.$newflg.'_'.$_} eq 'excused') {
+ $newrecord{'resource.'.$_.'.solved'} = 'excused'
+ if ($record{'resource.'.$_.'.solved'} ne 'excused');
} else {
- if ($oldscore ne $newscore) {
- $newrecord{"resource.$part.$type"}=$newscore;
- $result .= "Updating $student"."'s status for $part.$type to ".
- "$newscore \n";
+ my $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ?
+ $ENV{'form.GD_BOX'.$newflg.'_'.$_} :
+ $ENV{'form.RADVAL'.$newflg.'_'.$_});
+ if ($pts eq '') {
+ &userError($request,'No point was assigned for part '.$_.
+ ' and for username '.$stuname.'.');
+ return 'error';
+ }
+ my $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 :
+ $ENV{'form.WGT'.$newflg.'_'.$_};
+ my $partial= $pts/$wgt;
+ $newrecord{'resource.'.$_.'.awarded'} = $partial
+ if ($record{'resource.'.$_.'.awarded'} ne $partial);
+ my $reckey = 'resource.'.$_.'.solved';
+ if ($partial == 0) {
+ $newrecord{$reckey} = 'incorrect_by_override'
+ if ($record{$reckey} ne 'incorrect_by_override');
} else {
- #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore: \n";
+ $newrecord{$reckey} = 'correct_by_override'
+ if ($record{$reckey} ne 'correct_by_override');
}
+ $newrecord{'resource.'.$_.'.submitted_by'} = $submitter
+ if ($submitter && ($record{'resource.'.$_.'.submitted_by'} ne $submitter));
}
}
- 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";
+
+ if (scalar(keys(%newrecord)) > 0) {
+ $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
+ &Apache::lonnet::cstore(\%newrecord,$symb,
+ $ENV{'request.course.id'},$domain,$stuname);
}
- return $result;
+ return '';
}
+#--------------------------------------------------------------------------------------
+#
+#-------------------------- Next few routines handles grading by section or whole class
+#
+#--- Javascript to handle grading by section or whole class
+sub viewgrades_js {
+ my ($request) = shift;
-# ========================================================== Attempt submission
-sub submission {
- my ($request) = @_;
- my $url = $ENV{'form.url'};
- $url =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
- if ($ENV{'form.student'} eq '') {
- &moreinfo($request,"Need student login id"); return ''; }
-# if ($ENV{'form.student'} eq '') { &listStudents($request); return ''; }
- my ($uname,$udom) = &finduser($ENV{'form.student'});
- if ($uname eq '') {
- &moreinfo($request,"Unable to find student"); return ''; }
- my $symb=&Apache::lonnet::symbread($url);
- if ($symb eq '') {
- $request->print("Unable to handle ambiguous references:$url:.");
- return ''; }
- my $answer = &Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
- $ENV{'request.course.id'});
- my $result = "
Submission Record
$uname:$udom for $url ".
- $answer;
- return $result;
+ $request->print(<
+ function viewOneStudent(user,domain) {
+ document.onestudent.student.value = user;
+ document.onestudent.userdom.value = domain;
+ document.onestudent.submit();
+ }
+
+ function writePoint(partid,weight,point) {
+ var radioButton = eval("document.classgrade.RADVAL_"+partid);
+ var textbox = eval("document.classgrade.TEXTVAL_"+partid);
+ if (point == "textval") {
+ var point = eval("document.classgrade.TEXTVAL_"+partid+".value");
+ if (isNaN(point) || point < 0) {
+ alert("A number equal or greater than 0 is expected. Entered value = "+point);
+ var resetbox = false;
+ for (var i=0; i weight) {
+ var resp = confirm("You entered a value ("+point+
+ ") greater than the weight for the part. Accept?");
+ if (resp == false) {
+ textbox.value = "";
+ return;
+ }
+ }
+ for (var i=0; i weight) {
+ var resp = confirm("You entered a value ("+point+
+ ") greater than the weight of the part. Accept?");
+ if (resp == false) {
+ textbox.value = "";
+ return;
+ }
+ }
+ selval[0].selected = true;
+ }
+
+ function changeOneScore(partid,user) {
+ var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_sv");
+ if (selval[1].selected) {
+ var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_aw");
+ boxval.value = "";
+ }
+ }
+
+ function resetEntry(numpart) {
+ for (ctpart=0;ctpart
+VIEWJAVASCRIPT
}
-# ================================================================= View grades
+#--- show scores for a section or whole class w/ option to change/update a score
sub viewgrades {
- my ($request) = @_;
- my $result = '';
-
- #get resource reference
- my $url = $ENV{'form.url'};
- $url =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
- my $symb=$ENV{'form.symb'};
- if (!$symb) { $symb = &Apache::lonnet::symbread($url); }
- if ($symb eq '') {
- $request->print("Unable to handle ambiguous references:$url:.");
- return '';
- }
-
- #get classlist
- my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});
- my $chome = $ENV{"course.$ENV{'request.course.id'}.home"};
- #print "Found $cdom:$cnum:$chome ";
- my (%classlist) = &getclasslist($cdom,$cnum,$chome,'0');
- my $headerclr = '"#ccffff"';
- my $cellclr = '"#ffffcc"';
+ my ($request) = shift;
+ &viewgrades_js($request);
- #get list of parts for this problem
- my (@parts) = &getpartlist($url);
+ my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'});
+ my $result='
Manual Grading
';
- $request->print("
Manual Grading
");
-
- #start the form
- $result = '';
+ $result.=&show_grading_menu_form($symb,$url);
return $result;
}
-# ================================================================= Edit grades
+#--- call by previous routine to display each student
+sub viewstudentgrade {
+ my ($url,$symb,$courseid,$student,$fullname,$parts,$weight) = @_;
+ my ($uname,$udom) = split(/:/,$student);
+ my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
+ my $result='
';
+ return $result;
+}
+
+#--- change scores for all the students in a section/class
+# record does not get update if unchanged
sub editgrades {
my ($request) = @_;
- my $result='';
- my $symb = $ENV{'form.symb'};
- if ($symb eq '') {
- $request->print("Unable to handle ambiguous references:$symb:".
- $ENV{'form.url'});
- return '';
+ my $symb=$ENV{'form.symb'};
+ my $url =$ENV{'form.url'};
+ my $title='
'."\n";
+
+ my %scoreptr = (
+ 'correct' =>'correct_by_override',
+ 'incorrect'=>'incorrect_by_override',
+ 'excused' =>'excused',
+ 'ungraded' =>'ungraded_attempted',
+ 'nothing' => '',
+ );
+ my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
+
+ my (@partid);
+ my %weight = ();
+ my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
+ while ($ctr < $ENV{'form.totalparts'}) {
+ my $partid = $ENV{'form.partid_'.$ctr};
+ push @partid,$partid;
+ $weight{$partid} = $ENV{'form.weight_'.$partid};
+ $ctr++;
+ $result .= '
Part '.$partid.
+ ' (Weight = '.$weight{$partid}.')
';
}
- my $url = $ENV{'form.url'};
- #get classlist
- my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'});
- my $chome = $ENV{"course.$ENV{'request.course.id'}.home"};
- #print "Found $cdom:$cnum:$chome ";
- my (%classlist) = &getclasslist($cdom,$cnum,$chome,'0');
-
- #get list of parts for this problem
+ $result .= '
';
+ foreach (@partid) {
+ $result .= '
Old Score
'.
+ '
New Score
';
+ }
+ $result .= '
'."\n";
+
+ for ($i=0; $i<$ENV{'form.total'}; $i++) {
+ my $user = $ENV{'form.ctr'.$i};
+ my %newrecord;
+ my $updateflag = 0;
+ my @userdom = grep /^$user:/,keys %$classlist;
+ my ($foo,$udom) = split(/:/,$userdom[0]);
+
+ $result .= '
'."\n";
+ my $msg = 'Number of records updated = '.$rec_update.
+ ' for '.$count.' student'.($count <= 1 ? '' : 's').'. '.
+ 'Total number of students = '.$ENV{'form.total'}.' ';
+ return $title.$msg.$result;
+}
+#------------- end of section for handling grading by section/class ---------
+#
+#----------------------------------------------------------------------------
+
+
+#----------------------------------------------------------------------------
+#
+#-------------------------- Next few routines handles grading by csv upload
+#
+#--- Javascript to handle csv upload
+sub csvupload_javascript_reverse_associate {
+ return(<2) { foundsomething=1; }
+ }
+ if (founduname==0 || founddomain==0) {
+ alert('You need to specify at both the username and domain');
+ return;
+ }
+ if (foundsomething==0) {
+ alert('You need to specify at least one grading field');
+ return;
+ }
+ vf.submit();
+ }
+ function flip(vf,tf) {
+ var nw=eval('vf.f'+tf+'.selectedIndex');
+ var i;
+ //can not pick the same destination field twice
+ for (i=0;i<=vf.nfields.value;i++) {
+ if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
+ eval('vf.f'+i+'.selectedIndex=0;')
+ }
+ }
+ }
+ENDPICK
+}
+
+sub csvuploadmap_header {
+ my ($request,$symb,$url,$datatoken,$distotal)= @_;
+ my $result;
+ my $javascript;
+ if ($ENV{'form.upfile_associate'} eq 'reverse') {
+ $javascript=&csvupload_javascript_reverse_associate();
+ } else {
+ $javascript=&csvupload_javascript_forward_associate();
+ }
+ $request->print(<
+
Uploading Class Grades for resource $url
+
+
Identify fields
+Total number of records found in file: $distotal
+Enter as many fields as you can. The system will inform you and bring you back
+to this page if the data selected is insufficient to run your class.
+
+
+
+
+
+
+
+
+
+
+
+
+ENDPICK
+return '';
+
+}
+
+sub csvupload_fields {
+ my ($url) = @_;
my (@parts) = &getpartlist($url);
+ my @fields=(['username','Student Username'],['domain','Student Domain']);
+ foreach my $part (sort(@parts)) {
+ my @datum;
+ my $display=&Apache::lonnet::metadata($url,$part.'.display');
+ my $name=$part;
+ if (!$display) { $display = $name; }
+ @datum=($name,$display);
+ push(@fields,\@datum);
+ }
+ return (@fields);
+}
+
+sub csvuploadmap_footer {
+ my ($request,$i,$keyfields) =@_;
+ $request->print(<
+
+
+
+
+ENDPICK
+}
- $result .= '
'."\n";
+ return $result;
+}
+
+#--- Menu to upload a csv scores ---
+sub upcsvScores_form {
+ my ($symb,$url) = @_;
+ if (!$symb) {return '';}
+ my $result.='
'."\n";
+ $result.='
'."\n";
+ $result.=' Specify a file containing the class scores for above resource
'."\n";
+ return $result;
+}
+
+#--- Handgrading problems ---
+sub viewGradeaStu_form {
+ my ($symb,$url,$response,$handgrade) = @_;
+ my ($classlist,$sections) = &getclasslist('all','0');
+ my $result.='
'."\n";
+ $result.='
'."\n";
+ $result.=' View/Grade an Individual Student\'s Submission
'."\n";
+ $result.='
'."\n";
+ $result.='
'."\n".
+ ''."\n".
+ ''."\n".
+ ''."\n".
+ ''."\n".
+ ''."\n".
+ ''."\n";
+
+ $result.=' Select section: '."\n";
+ $result.=' Display students who has: '.
+ ' submitted'.
+ ' everybody ';
+ $result.=' (Section "no" implies the students were not assigned a section.) '
+ if (grep /no/,@$sections);
+
+ $result.=' '."\n".
+ '
'."\n";
+ $result.='
'."\n";
+ $result.='
'."\n";
+ return $result;
+}
+
+#--- Form to input a receipt number ---
+sub verifyReceipt_form {
+ my ($symb,$url) = @_;
+ my $cdom=$ENV{"course.$ENV{'request.course.id'}.domain"};
+ my $cnum=$ENV{"course.$ENV{'request.course.id'}.num"};
+ my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});
+
+ my $result.='
'."\n";
+ $result.='
'."\n";
+ $result.=' Verify a Submission Receipt Issued by this Server