Index: openacs-4/packages/xowf/tcl/test-item-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/test-item-procs.tcl,v
diff -u -N -r1.7.2.129 -r1.7.2.130
--- openacs-4/packages/xowf/tcl/test-item-procs.tcl 2 May 2021 11:46:16 -0000 1.7.2.129
+++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 5 May 2021 12:26:10 -0000 1.7.2.130
@@ -1115,6 +1115,9 @@
}
}
+ :method dict_value {dict key {default ""}} {
+ expr {[dict exists $dict $key] ? [dict get $dict $key] : $default}
+ }
}
}
namespace eval ::xowf::test_item {
@@ -1329,6 +1332,7 @@
#
# - runtime_panel
# - render_answers_with_edit_history
+ # - render_answers
#
# - marked_results
# - answers_panel
@@ -1344,6 +1348,10 @@
# - state_periods
#
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: create_workflow
+ #----------------------------------------------------------------------
:public method create_workflow {
{-answer_workflow /packages/xowf/lib/online-exam-answer.wf}
{-master_workflow en:Workflow.form}
@@ -1424,8 +1432,10 @@
}
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: get_label_from_options
+ #----------------------------------------------------------------------
:method get_label_from_options {value options} {
foreach option $options {
if {[lindex $option 1] eq $value} {
@@ -1435,7 +1445,10 @@
return ""
}
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: recutil_create
+ #----------------------------------------------------------------------
:public method recutil_create {
-exam_id:integer
{-fn "answers.rec"}
@@ -1464,19 +1477,23 @@
return [::xo::recutil new -file $export_dir$fn]
}
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: export_answer
+ #----------------------------------------------------------------------
:public method export_answer {
- -user_answers:object
- -html:required
-combined_form_info
+ -html:required
-recutil:object,required
+ -submission:object
} {
#
# Export the provided question and answer in GNU rectuil format.
#
- #ns_log notice "answers: [$user_answers serialize]"
+ #ns_log notice "answers: [$submission serialize]"
- if {[$user_answers exists __form_fields]} {
- set form_fields [$user_answers set __form_fields]
+ if {[$submission exists __form_fields]} {
+ set form_fields [$submission set __form_fields]
} else {
#
# We do not have the newest version of xowiki, so locate the
@@ -1493,16 +1510,16 @@
}
set export_dict ""
- set user [$user_answers set creation_user]
+ set user [$submission set creation_user]
if {![info exists ::__running_ids]} {
set ::__running_ids ""
}
if {![dict exists $::__running_ids $user]} {
dict set ::__running_ids $user [incr ::__running_id]
}
- set seeds [$user_answers property seeds]
- set instance_attributes [$user_answers set instance_attributes]
+ set seeds [$submission property seeds]
+ set instance_attributes [$submission set instance_attributes]
set answer_attributes [lmap a $instance_attributes {
if {![string match *_ $a]} {continue}
set a
@@ -1565,8 +1582,10 @@
}
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: time_window_setup
+ #----------------------------------------------------------------------
:method time_window_setup {parentObj:object {-time_window:required}} {
#
# Check the provided time_window values, adjust it if necessary,
@@ -1631,8 +1650,10 @@
}
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method delete_all_answer_data {obj:object} {
#
# Delete all instances of the answer workflow
@@ -1645,7 +1666,10 @@
return $wf
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method delete_scheduled_atjobs {obj:object} {
#
# Delete previously scheduled atjobs
@@ -1667,10 +1691,10 @@
}
}
-
-
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method get_answer_wf {obj:object} {
#
# return the workflow denoted by the property wfName in obj
@@ -1681,8 +1705,10 @@
-forms [$obj property wfName]]
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method get_wf_instances {
{-initialize false}
{-orderby ""}
@@ -1711,8 +1737,10 @@
-package_id [$wf package_id]]
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method get_answers {{-state ""} {-extra_attributes {}} wf:object} {
#
# Extracts wf instances as answers (e.g. extracting their
@@ -1741,8 +1769,10 @@
return $results
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method get_duration {{-exam_published_time ""} revision_sets} {
#
# Get the duration from a set of revisions and return a dict
@@ -1769,8 +1799,10 @@
return $r
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method get_IPs {revision_sets} {
#
# Get the IP addresses for the given revision set. Should be
@@ -1786,8 +1818,10 @@
return [dict keys $IPs]
}
- ########################################################################
-
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method revisions_up_to {revision_sets revision_id} {
#
# Return the revisions of the provided revision set up the
@@ -1803,7 +1837,10 @@
}]
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: last_time_in_state
+ #----------------------------------------------------------------------
:public method last_time_in_state {revision_sets -state:required -with_until:switch } {
#
# Loops through revision sets and retrieves the latest date
@@ -1824,7 +1861,10 @@
return $result
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: pretty_period
+ #----------------------------------------------------------------------
:method pretty_period {{-dayfmt %q} {-timefmt %H:%M} from to} {
set from_day [lc_time_fmt $from $dayfmt]
set from_time [lc_time_fmt $from $timefmt]
@@ -1845,7 +1885,10 @@
return $period
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: state_periods
+ #----------------------------------------------------------------------
:public method state_periods {revision_sets -state:required} {
#
# Return for the provided revision_sets the time ranges the
@@ -1878,8 +1921,11 @@
return $periods
}
- ########################################################################
- :public method achieved_points {-answer_object:object -answer_attributes:required } {
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: achieved_points
+ #----------------------------------------------------------------------
+ :public method achieved_points {-submission:object -answer_attributes:required } {
#
# This method has to be called after the instance was rendered,
# since it uses the produced form_fields.
@@ -1889,7 +1935,7 @@
set achievableTotalPoints 0
set details {}
foreach a [dict keys $answer_attributes] {
- set f [$answer_object lookup_form_field -name $a $all_form_fields]
+ set f [$submission lookup_form_field -name $a $all_form_fields]
set points {}
if {![$f exists test_item_points]} {
ns_log warning "question $f [$f name] [$f info precedence] HAS NO POINTS"
@@ -1917,7 +1963,10 @@
achievablePoints $achievableTotalPoints]
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: runtime_panel
+ #----------------------------------------------------------------------
:public method runtime_panel {
{-revision_id ""}
{-view default}
@@ -2041,8 +2090,147 @@
}]
return $HTML
}
- ########################################################################
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
+ # Method: render_submission=edit_history
+ #----------------------------------------------------------------------
+ :method render_submission=edit_history {
+ {-submission:object}
+ {-examWf:object}
+ {-nameToQuestionObj}
+ } {
+ set last_answers {}
+ set rev_nr 1
+ set q_nr 0
+ set qnames ""
+ set report ""
+ set student_href [$examWf pretty_link -query m=print-answers&id=[$submission set item_id]]
+
+ set revision_sets [$submission get_revision_sets -with_instance_attributes]
+ foreach s $revision_sets {
+ set msgs {}
+ set ia [ns_set get $s instance_attributes]
+ foreach key [dict keys $ia *_] {
+ if {![dict exists $qnames $key]} {
+ dict set qnames $key [incr q_nr]
+ }
+ set value [dict get $ia $key]
+ #
+ # Determine the question type
+ #
+ set form_obj [dict get $nameToQuestionObj $key]
+ set template_obj [$form_obj page_template]
+ if {[$template_obj name] eq "en:edit-interaction.wf"} {
+ set item_type [dict get [$form_obj instance_attributes] item_type]
+ } else {
+ switch [$template_obj name] {
+ en:TestItemShortText.form {set item_type ShortText}
+ en:TestItemText.form {set item_type Text}
+ default {set item_type unknown}
+ }
+ }
+ #ns_log notice "Template name = [$template_obj name] -> item_type '$item_type'"
+
+ #
+ # For the time being, compute the differences just for short text questions
+ #
+ if {$item_type in {ShortText}} {
+ foreach answer_key [dict keys $value] {
+ set answer_value [string trim [dict get $value $answer_key]]
+ set what ""
+ set last_value [:dict_value $last_answers $answer_key ""]
+ if {$last_value ne ""} {
+ if {$answer_value eq ""} {
+ set what cleared
+ ns_log notice " ==> $answer_key: answer_value '$last_value' cleared in revision $rev_nr"
+ } elseif {$answer_value ne $last_value} {
+ set what updated
+ }
+ } else {
+ # last answer was empty
+ if {$answer_value ne ""} {
+ set what added
+ }
+ }
+ #
+ # Remember last answer values
+ #
+ dict set last_answers $answer_key $answer_value
+ if {$what ne ""} {
+ if {$what eq "cleared"} {
+ set answer_value $last_value
+ }
+ lappend msgs [subst {
+
+ q[string map [list answer "" {*}$qnames] $answer_key] $what [ns_quotehtml '$answer_value']
+
+ }]
+ }
+ }
+ } else {
+ #
+ # Show the full content of the field
+ #
+ if {$value ne ""} {
+ lappend msgs [subst {
+ q[string map [list answer "" {*}$qnames] $key]:
+ [ns_quotehtml '$value']
+ }]
+ }
+ }
+ }
+ append report [subst {
+ [format %02d $rev_nr]:
+ [join $msgs {; }]
+ }]
+ incr rev_nr
+ }
+
+ append HTML [subst {
+
Name | Revisions |
---|
Name | Revisions | |
---|---|---|
[$i set online-exam-userName] | [$i set online-exam-fullName] | -$report | -
[:grading_table -csv ${:grade_csv} ${:grade_dict}]
+ } + + return [list do_stream $do_stream HTML $HTML] + } + + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: participant_result + #---------------------------------------------------------------------- :method participant_result { -obj:object answerObj:object @@ -2228,6 +2764,10 @@ return $answer } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: answer_form_field_objs + #---------------------------------------------------------------------- :method answer_form_field_objs {-clear:switch -wf:object form_info} { set key ::__test_item_answer_form_fields if {$clear} { @@ -2254,6 +2794,10 @@ } } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: grading_table + #---------------------------------------------------------------------- :public method grading_table {{-csv ""} grade_dict} { # # Produce HTML markup based on a dict with grades as keys and @@ -2279,6 +2823,10 @@ return $gradingTable } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: results_table + #---------------------------------------------------------------------- :public method results_table { -package_id:integer -items:object,required @@ -2288,6 +2836,11 @@ {-grading_scheme ::xowf::test_item::grading::wi1} wf:object } { + # + # Render the results in forma of a table and return HTML. + # Currently deactivated. + # + #set form_info [:combined_question_form -with_numbers $wf] set form_info [::xowf::test_item::question_manager combined_question_form $wf] set answer_form_field_objs [:answer_form_field_objs -wf $wf $form_info] @@ -2486,6 +3039,10 @@ return $HTML } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: participants_table + #---------------------------------------------------------------------- :public method participants_table { -package_id:integer -items:object,required @@ -2598,6 +3155,10 @@ return $dialogs$HTML } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: marked_results + #---------------------------------------------------------------------- :public method marked_results {-obj:object -wf:object form_info} { # # Return for every participant the individual results for an exam @@ -2617,6 +3178,10 @@ return $results } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: answers_panel + #---------------------------------------------------------------------- :public method answers_panel { {-polling:switch false} {-heading #xowf.submitted_answers#} @@ -2696,6 +3261,10 @@ return $answerStatus } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: prevent_multiple_tabs + #---------------------------------------------------------------------- :public method prevent_multiple_tabs { {-cookie_name multiple_tabs} } { @@ -2723,6 +3292,10 @@ }] } + #---------------------------------------------------------------------- + # Class: Answer_manager + # Method: countdown_timer + #---------------------------------------------------------------------- :public method countdown_timer { {-target_time:required} {-id:required} @@ -2739,7 +3312,8 @@ # client browser is set incorrectly. # set nowMs [clock milliseconds] - set nowIsoTime [clock format [expr {$nowMs/1000}] -format "%Y-%m-%dT%H:%M:%S"].[format %.3d [expr {$nowMs % 1000}]] + set nowIsoTime [clock format [expr {$nowMs/1000}] \ + -format "%Y-%m-%dT%H:%M:%S"].[format %.3d [expr {$nowMs % 1000}]] template::add_body_script -script [subst { var countdown_target_date = new Date('$target_time').getTime(); @@ -3408,7 +3982,7 @@ # # autograde ok on the question level # - } elseif {[dict exists $formAttributes auto_correct] && [dict get $formAttributes auto_correct]} { + } elseif {[:dict_value $formAttributes auto_correct 0]} { # # autograde ok on the form level # @@ -3581,9 +4155,7 @@ # list order is important, since it determines also the ordering # in the message. # - if {[dict exists $question_info show_max] - && [dict get $question_info show_max] ne "" - } { + if {[:dict_value $question_info show_max ""] ne ""} { foreach key {choice_options sub_questions} { if {[dict exists $question_info $key] && [dict get $question_info show_max] ne [dict get $question_info $key] @@ -3607,10 +4179,6 @@ return #xowf.shuffle_$m# } } - :method dict_value {dict key {default ""}} { - expr {[dict exists $dict $key] ? [dict get $dict $key] : $default} - } - :public method describe_form {{-asHTML:switch} form_obj} { # # Call for every form field of the form_obj the "describe"