Gustaf Neumann
XoWiki Content Flow - an XoWiki based workflow system implementing state-based behavior of wiki pages and forms
2021-09-15
WU Vienna
BSD-Style
2
-
-
+
+
Index: openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml,v
diff -u -N -r1.2.2.65 -r1.2.2.66
--- openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 27 Dec 2021 14:07:29 -0000 1.2.2.65
+++ openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 3 Jan 2022 16:00:36 -0000 1.2.2.66
@@ -144,6 +144,8 @@
Dateiabgabefrage
Anordnungsfrage
+ Benotungsschema
+
Entwurf des Inclass-Exams (nicht freigegeben)
Inclass-Exam freigeschaltet
Inclass-Exam geschlossen
@@ -203,10 +205,10 @@
automatische Einsicht nicht m�glich
Randomisierung f�r Pr�fung geeignet
Randomisierung f�r Pr�fung nicht geeignet (keine Zufallsauswahl "immer" verwenden)
- %achievedPoints% (gerundet %achievedPointsRounded%) von m�glichen %totalPoints% Punkten, %percentage%%, Note: %grade%
- %achievedPoints% von m�glichen %totalPoints% Punkten, %percentage%% (gerundet %percentageRounded%%), Note: %grade%
- %achievedPoints% von m�glichen %totalPoints% Punkten, %percentage%%, Note: %grade%
-
+ %achievedPoints% (gerundet %achievedPointsRounded%) von m�glichen %totalPoints% Punkten, %percentage%%, Note: %grade%
+ %achievedPoints% von m�glichen %totalPoints% Punkten, %percentage%% (gerundet %percentageRounded%%), Note: %grade%
+ %achievedPoints% von m�glichen %totalPoints% Punkten, %percentage%%, Note: %grade%
+ %achievedPoints% von m�glichen %totalPoints% Punkten, %percentage%%
Name enth�lt zumindest ein ung�ltiges Zeichen
Detaileinstellungen
@@ -251,5 +253,16 @@
Wert muss kleiner oder gleich sein als
Wert muss gr��er oder gleich sein als
-
+
+ Rundungsschema
+ genau
+ nach Punkten
+ nach Prozenten
+ Rundungsgenauigkeit
+ Notengrenzen
+ Note
+ Benotungsschema
+ Das Benotungsschema definiertdie Notengrenzen und die Rundungsregeln, um von dem erreichten Ergebnis (in Prozenten) eine Note abzuleiten
+ Keine Notenvergabe
+
Index: openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml,v
diff -u -N -r1.2.2.72 -r1.2.2.73
--- openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 27 Dec 2021 14:07:29 -0000 1.2.2.72
+++ openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 3 Jan 2022 16:00:36 -0000 1.2.2.73
@@ -159,6 +159,8 @@
Ordering Interaction
Composite Interaction
+ Grading Scheme
+
Online Exam
Inclass Quiz
Inclass Exam
@@ -228,9 +230,10 @@
randomization for exam ok
randomization for exam NOT ok ("always" should not be used)
- %achievedPoints% (rounded %achievedPointsRounded%) of possible %totalPoints% points, %percentage%%, grade: %grade%
- %achievedPoints% of possible %totalPoints% points, %percentage%% (rounded %percentageRounded%%), grade: %grade%
- %achievedPoints% of possible %totalPoints% points, %percentage%%, grade: %grade%
+ %achievedPoints% (rounded %achievedPointsRounded%) of possible %totalPoints% points, %percentage%%, grade: %grade%
+ %achievedPoints% of possible %totalPoints% points, %percentage%% (rounded %percentageRounded%%), grade: %grade%
+ %achievedPoints% of possible %totalPoints% points, %percentage%%, grade: %grade%
+ %achievedPoints% of possible %totalPoints% points, %percentage%%
Name contains at least one invalid character
Detailed Configuration
@@ -279,4 +282,15 @@
Value must smaller or equal
Value must larger or equal
+ Rounding Scheme
+ precise
+ by points
+ by percentage
+ Rounding Precision
+ Grade Boundaries
+ Grade
+ Grading Scheme
+ The grading scheme defines grade boundaries and rounding rules for determining grades from achieved percentages
+ No grading
+
Index: openacs-4/packages/xowf/catalog/xowf.es_ES.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.es_ES.ISO-8859-1.xml,v
diff -u -N -r1.2.2.8 -r1.2.2.9
--- openacs-4/packages/xowf/catalog/xowf.es_ES.ISO-8859-1.xml 24 Aug 2021 13:12:06 -0000 1.2.2.8
+++ openacs-4/packages/xowf/catalog/xowf.es_ES.ISO-8859-1.xml 3 Jan 2022 16:00:36 -0000 1.2.2.9
@@ -215,10 +215,9 @@
revisi�n autom�tica del examen imposible
aleatorizaci�n para el examen correcta
aleatorizaci�n para el examen incorrecta ("siempre" no deber�a usarse)
-
- %achievedPoints% (redondeado%achievedPointsRounded%) de %totalPoints% puntos posibles, %percentage%%, nota: %grade%
- %achievedPoints% de %totalPoints% puntos posibles, %percentage%% (redondeado %percentageRounded%%), nota: %grade%
- %achievedPoints% de %totalPoints% puntos posibles, %percentage%%, nota: %grade%
+ %achievedPoints% (redondeado %achievedPointsRounded%) de %totalPoints% puntos posibles, %percentage%%, nota: %grade%
+ %achievedPoints% de %totalPoints% puntos posibles, %percentage%% (redondeado %percentageRounded%%), nota: %grade%
+ %achievedPoints% de %totalPoints% puntos posibles, %percentage%%, nota: %grade%
Nombre contiene al menos un car�cter inv�lido
Configuraci�n detallada
Index: openacs-4/packages/xowf/catalog/xowf.it_IT.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.it_IT.ISO-8859-1.xml,v
diff -u -N -r1.2.2.7 -r1.2.2.8
--- openacs-4/packages/xowf/catalog/xowf.it_IT.ISO-8859-1.xml 30 Aug 2021 13:41:02 -0000 1.2.2.7
+++ openacs-4/packages/xowf/catalog/xowf.it_IT.ISO-8859-1.xml 3 Jan 2022 16:00:36 -0000 1.2.2.8
@@ -143,9 +143,9 @@
Apri correzione consegna studente
Apri la correzione consegna degli studenti
di
- %achievedPoints% (arrotondato %achievedPointsRounded%) di %totalPoints% punti possibili, %percentage%%, voto: %grade%
- %achievedPoints% di %totalPoints% punti totali, %percentage%%, voto: %grade%
- %achievedPoints% di %totalPoints% punti possibili, %percentage%% (arrotondato %percentageRounded%%), voto: %grade%
+ %achievedPoints% (arrotondato %achievedPointsRounded%) di %totalPoints% punti possibili, %percentage%%, voto: %grade%
+ %achievedPoints% di %totalPoints% punti totali, %percentage%%, voto: %grade%
+ %achievedPoints% di %totalPoints% punti possibili, %percentage%% (arrotondato %percentageRounded%%), voto: %grade%
Partecipante
Partecipanti
i partecipanti hanno risposto a questa domanda
Index: openacs-4/packages/xowf/lib/edit-grading-scheme.wf
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/edit-grading-scheme.wf,v
diff -u -N
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/xowf/lib/edit-grading-scheme.wf 3 Jan 2022 16:00:36 -0000 1.1.2.1
@@ -0,0 +1,168 @@
+# -*- Tcl -*-
+########################################################################
+# Workflow for editing grading scheme instances
+# =============================================
+#
+# This workflow manages definition of grading schemes. It implements a
+# trivial state management and creates instances of the GradingScheme
+# class when instantiated. To create new grading schemes, e.g. add the
+# following line to the menu entries.
+#
+# {entry -name New.Grading.Scheme -form en:edit-grading-scheme.wf}
+#
+
+set :policy ::xowf::test_item::test-item-policy-edit
+set :autoname 1
+
+Action initialize -proc activate {obj} {
+ set name [$obj name]
+ if {[$obj is_new_entry $name]} {
+ #set container [[$obj wf_context] wf_container]
+ #set item_type [$container item_type $obj]
+ $obj title "New Grading Scheme ($name)"
+ } else {
+ $obj title $name
+ }
+}
+
+Action save \
+ -next_state created \
+ -label #xowiki.Form-submit_button# \
+ -proc activate {obj} {
+ ::xowf::test_item::grading::flush_grading_schemes \
+ -package_id [$obj package_id] \
+ -parent_id [$obj parent_id]
+ }
+
+Action view -label #xowiki.view# -proc activate {obj} {
+ set url [export_vars -base [$obj pretty_link] {
+ {m view} {p.return_url "[::xo::cc url]"}
+ }]
+ ad_returnredirect $url
+ ad_script_abort
+}
+
+State parameter {
+ {extra_css {/resources/xowf/test-item.css}}
+}
+State initial \
+ -actions {save} \
+ -form_loader load_form -form foo
+State created \
+ -actions {save view} \
+ -form_loader load_form -form foo
+
+
+#
+# Form loader for grading scheme
+#
+:proc load_form {ctx args} {
+ #ns_log notice "============ grading scheme load_form <$args>"
+ dict set roundingDict _type radio
+ dict set roundingDict label #xowf.Rounding_scheme#
+ dict set roundingDict value None
+ dict set roundingDict horizontal true
+ dict set roundingDict options {
+ {\#xowf.Rounding_scheme-GradingRoundNone# None}
+ {\#xowf.Rounding_scheme-GradingRoundPoints# Points}
+ {\#xowf.Rounding_scheme-GradingRoundPercentage# Percentage}
+ }
+
+ set precisionDict {
+ _type number
+ label #xowf.Rounding_precision#
+ min 0
+ max 4
+ value 2
+ js_validate true
+ }
+
+ set gradeDict {
+ _type grade_boundary
+ js_validate true
+ inline true
+ CSSclass "form-control inline"
+ value 80
+ min 0
+ max 100
+ step .1
+ }
+ set form_obj [::xowiki::Form new \
+ -destroy_on_cleanup \
+ -set name en:grading_form \
+ -form {{} text/html
+ } \
+ -form_constraints [subst {
+ {[::xowiki::formfield::dict_to_spec -name rounding $roundingDict]}
+ {[::xowiki::formfield::dict_to_spec -name precision $precisionDict]}
+ {[::xowiki::formfield::dict_to_spec -name grade1 $gradeDict]}
+ {[::xowiki::formfield::dict_to_spec -name grade2 $gradeDict]}
+ {[::xowiki::formfield::dict_to_spec -name grade3 $gradeDict]}
+ {[::xowiki::formfield::dict_to_spec -name grade4 $gradeDict]}
+ _description:omit _page_order:omit _creator:omit _title:omit _nls_language:hidden
+ }] \
+ -text {} \
+ -anon_instances t \
+ ]
+ #ns_log notice "============ grading scheme load_form <$args> return $form_obj\n[$form_obj form_constraints]"
+ return $form_obj
+}
+
+:object-specific {
+ set ctx [:wf_context]
+ set container [$ctx wf_container]
+ if {$ctx ne $container} {
+ $ctx forward load_form $container %proc $ctx
+ }
+
+ :proc render_icon {} {
+ return {text "Grading Scheme" is_richtext false}
+ }
+
+ #
+ # Build instance attributes of grading scheme from properties
+ # "grade1..4", "precision" and "rounding".
+ #
+ set percentage_boundaries [lmap p {grade4 grade3 grade2 grade1} {
+ if {[:property $p] eq ""} {continue}
+ :property $p
+ }]
+ set precision [:property precision]
+ set rounding [:property rounding]
+
+ if {[llength $percentage_boundaries] == 4 && $precision ne "" && $rounding ne ""} {
+ set roundingClass ::xowf::test_item::grading::GradingRound$rounding
+ if {[nsf::is class $roundingClass]} {
+ set gradingSchemeName [dict get [::${:package_id} split_name ${:name}] suffix]
+ $roundingClass create ::xowf::test_item::grading::$gradingSchemeName \
+ -percentage_boundaries $percentage_boundaries \
+ -precision $precision
+ ::xowf::test_item::grading::$gradingSchemeName destroy_on_cleanup
+ #ns_log notice "### loaded grading scheme '$gradingSchemeName'"
+ } else {
+ ns_log warning "invalid grading scheme ${:name}: unknown rounding '$rounding';" \
+ "defined: [lmap c [lsort [::xowf::test_item::grading::Grading info subclasses] {namespace tail $c}]]"
+ }
+ } else {
+ ns_log warning "invalid grading scheme ${:name}: missing values in ${:instance_attributes}"
+ }
+ unset -nocomplain percentage_boundaries precision rounding roundingClass
+
+ # ::xowf::test_item::grading::GradingRoundNone
+ # ::xowf::test_item::grading::GradingRoundNone
+ #ns_log notice "edit-grading-scheme state ${:state} name ${:name} IA ${:instance_attributes}"
+}
+
+#
+# Local variables:
+# mode: tcl
+# tcl-indent-level: 2
+# indent-tabs-mode: nil
+# End:
Index: openacs-4/packages/xowf/lib/inclass-exam-answer.wf
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/inclass-exam-answer.wf,v
diff -u -N -r1.1.2.52 -r1.1.2.53
--- openacs-4/packages/xowf/lib/inclass-exam-answer.wf 3 Dec 2021 12:17:46 -0000 1.1.2.52
+++ openacs-4/packages/xowf/lib/inclass-exam-answer.wf 3 Jan 2022 16:00:36 -0000 1.1.2.53
@@ -307,6 +307,8 @@
#
proc done_form_loader {ctx form_name} {
set obj [$ctx object]
+ #ns_log notice "==================================== done_form_loader called"
+
#
#
#
@@ -328,6 +330,7 @@
-anon_instances t \
]
}
+ #ns_log notice "==================================== done_form_loader DONE"
return $result
}
@@ -348,13 +351,13 @@
set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$obj parent_id]]
if {$for_question && [$obj state] in {initial working}} {
if {$form_info eq ""} {
- set form_info [${:QM} nth_question_form \
- -with_numbers \
- -with_title=false \
- -with_minutes=$with_minutes \
- -position $position \
- -item_nr $item_nr \
- $parent_obj]
+ set form_info [${:QM} nth_question_form \
+ -with_numbers \
+ -with_title=false \
+ -with_minutes=$with_minutes \
+ -position $position \
+ -item_nr $item_nr \
+ $parent_obj]
}
set title_info [lindex [dict get $form_info title_infos] 0]
set titleString [dict get $title_info full_title]
@@ -693,6 +696,7 @@
window.open("about:blank", "_self").close();
}]
}
+ #ns_log notice "==== object-specific inclass-exam-answer [self] isAnswerInstance $isAnswerInstance DONE"
}
Index: openacs-4/packages/xowf/lib/inclass-exam.wf
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/lib/Attic/inclass-exam.wf,v
diff -u -N -r1.1.2.86 -r1.1.2.87
--- openacs-4/packages/xowf/lib/inclass-exam.wf 2 Jan 2022 19:32:52 -0000 1.1.2.86
+++ openacs-4/packages/xowf/lib/inclass-exam.wf 3 Jan 2022 16:00:36 -0000 1.1.2.87
@@ -353,6 +353,7 @@
:object-specific {
set t0 [clock clicks -milliseconds]
+ #ns_log notice "==== object-specific inclass-exam [self] state ${:state}"
set ctx [:wf_context]
set container [$ctx wf_container]
if {$ctx ne $container} {
@@ -361,7 +362,6 @@
set :QM [$container set QM]
${container}::Property return_url -default "" -allow_query_parameter true
- #ns_log notice "==== object-specific inclass-exam [self] state ${:state}"
if {${:state} eq "done"} {
set done_actions republish
Index: openacs-4/packages/xowf/resources/prototypes/edit-grading-scheme.wf.page
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/resources/prototypes/Attic/edit-grading-scheme.wf.page,v
diff -u -N
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/xowf/resources/prototypes/edit-grading-scheme.wf.page 3 Jan 2022 16:00:36 -0000 1.1.2.1
@@ -0,0 +1,12 @@
+# -*- tcl-*-
+# The variable package_id and parent_id are provided via the caller context
+xowf::Package create_new_workflow_page \
+ -package_id $package_id \
+ -parent_id $parent_id \
+ -name en:edit-grading-scheme.wf \
+ -title "Grading Scheme" \
+ -instance_attributes {
+ workflow_definition {::xowf::include /packages/xowf/lib/edit-grading-scheme.wf}
+ form_constraints {}
+ return_url {}
+ }
Index: openacs-4/packages/xowf/resources/prototypes/select_question.form.page
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/resources/prototypes/Attic/select_question.form.page,v
diff -u -N -r1.1.2.6 -r1.1.2.7
--- openacs-4/packages/xowf/resources/prototypes/select_question.form.page 21 Nov 2021 20:26:06 -0000 1.1.2.6
+++ openacs-4/packages/xowf/resources/prototypes/select_question.form.page 3 Jan 2022 16:00:36 -0000 1.1.2.7
@@ -8,7 +8,7 @@
@question@
#xowf.Detail_configuration#
- @shuffle_items@ @max_items@ @allow_paste@ @allow_spellcheck@ @show_minutes@ @show_points@ @show_ip@ @countdown_audio_alarm@
+ @shuffle_items@ @max_items@ @allow_paste@ @allow_spellcheck@ @show_minutes@ @show_points@ @show_ip@ @countdown_audio_alarm@
@synchronized@ @time_window@ @time_budget@
@@ -19,12 +19,13 @@
@proctoring_record@
@signature@
+ @grading@
} text/html} \
-form_constraints {
@cr_fields:hidden
{_title:text,label=#xowf.online-exam-name#,default=#xowf.online-exam-default_name#}
- {question:form_page,multiple=true,keep_order=true,parent_id=.,form=en:edit-interaction.wf|en:TestItemText.form|en:TestItemShortText.form|en:TestItemMC.form|en:TestItemSC.form|en:TestItemUpload.form|en:TestItemReorder.form,required,help_text=#xowf.select_question_help_text#,label=#xowiki.questions#}
+ {question:form_page,multiple=true,keep_order=true,parent_id=.,form=en:edit-interaction.wf,required,help_text=#xowf.select_question_help_text#,label=#xowiki.questions#}
{countdown_audio_alarm:boolean,horizontal=true,default=t,label=#xowf.Countdown_audio_alarm#,help_text=#xowf.Countdown_audio_alarm_help_text#}
{shuffle_items:boolean,horizontal=true,label=#xowf.randomized_items#,help_text=#xowf.randomized_items_help_text#}
{max_items:number,min=1,label=#xowf.Max_items#,help_text=#xowf.Max_items_help_text#}
@@ -40,6 +41,7 @@
{proctoring:boolean,horizontal=true,default=f,label=#xowf.Proctoring#,help_text=#xowf.Proctoring_help_text#}
{proctoring_options:checkbox,horizontal=true,options={Desktop d} {Camera c} {Audio a} {Statement s},default=d c a s,label=#xowf.Proctoring_options#,help_text=#xowf.Proctoring_options_help_text#,swa?:disabled=1}
{proctoring_record:boolean,horizontal=true,default=t,label=#xowf.Proctoring_record#,help_text=#xowf.Proctoring_record_help_text#}
+ {grading:grading_scheme,required,default=none,label=#xowf.Grading_scheme#,help_text=#xowf.Grading_scheme_help_text#}
_description:omit _page_order:omit
}
Index: openacs-4/packages/xowf/tcl/grading-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/Attic/grading-procs.tcl,v
diff -u -N
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/xowf/tcl/grading-procs.tcl 3 Jan 2022 16:00:36 -0000 1.1.2.1
@@ -0,0 +1,370 @@
+::xo::library doc {
+ Test Item grading procs - support for different kind of grading types and schemes
+
+ @author Gustaf Neumann
+}
+
+#
+# Potential TODOs:
+# - support different grading labels (currently numeric 1..5)
+# - support finer granularity
+#
+
+namespace eval ::xowf::test_item::grading {
+ nx::Class create Grading {
+ :property {precision ""}
+ :property {title ""}
+ #
+ # The following two properties are specified by the sub-classes
+ # and ensure that no grading is defined accidentally from the
+ # base class.
+ #
+ :property {percentage_boundaries:required}
+ :property {csv:required}
+
+ :method init {} {
+ #
+ # Provide a default, self-descriptive title
+ #
+ if {${:title} eq ""} {
+ set roundingClass [namespace tail [:info class]]
+ if {$roundingClass ne "GradingRoundNone" && [string match *Round* $roundingClass]} {
+ set round_string "#xowf.Rounding_scheme#: #xowf.Rounding_scheme-$roundingClass#,"
+ } else {
+ set round_string ""
+ }
+ if {$roundingClass ne "GradingRoundNone" && ${:precision} ne ""} {
+ set precision "#xowf.Rounding_precision#: ${:precision},"
+ } else {
+ set precision ""
+ }
+ set :title "[namespace tail [self]]: $round_string $precision #xowf.Grade_boundaries#: ${:percentage_boundaries}"
+ ns_log notice "[self] initialized with title ${:title}"
+ }
+ next
+ }
+
+ :method calc_grade {-percentage -points -achievable_points} {
+ #
+ # Return a numeric grade for an exam submission based on
+ # percentage and the property "percentage_mapping". On
+ # invalid data, return 0.
+ #
+ # When "-percentage" is provided, use this for calculation
+ # Otherwise calculate percentage based on "-points" (which might
+ # be custom rounded) and "-achievable_points".
+ #
+
+ if {![info exists percentage] && $achievable_points > 0} {
+ set percentage \
+ [format %.2f [expr {($points*100/$achievable_points) + 0.00001}]]
+ }
+ if {[info exists percentage]} {
+ set grade 1
+ set nrGrades [expr {[llength ${:percentage_boundaries}]+1}]
+ if {$nrGrades ne 5} {
+ ns_log warning "grading [self]: unexpected number of grades: $nrGrades"
+ }
+ set gradePos 0
+ foreach boundary ${:percentage_boundaries} {
+ if {$percentage < $boundary} {
+ set grade [expr {$nrGrades - $gradePos}]
+ break
+ }
+ incr gradePos
+ }
+ } else {
+ set grade 0
+ }
+ return $grade
+ }
+
+ :method complete_dict {achieved_points} {
+
+ # Important dict members of "achieved_points":
+ # - achievedPoints: points that the student has achieved in her exam
+ # - achievablePoints: points that the student could have achieved so far
+ # - totalPoints: points that the student can achieve when finishing the exam
+ #
+ # achieved_points: {achievedPoints 4.0 achievablePoints 4 totalPoints 4}
+ # percentage_mapping: {50.0 60.0 70.0 80.0}
+ #
+ # While "achievedPoints" and "achievablePoints" are calculated by
+ # iterating over the submitted values, "totalPoints" contains
+ # the sum of points of all questions of the exam, no matter if
+ # these were answered or not.
+ #
+ if {![dict exists $achieved_points achievablePoints] && [dict exists $achieved_points totalPoints]} {
+ ns_log warning "test_item::grading legacy call, use 'achievablePoints' instead of 'totalPoints'"
+ dict set achieved_points achievablePoints [dict get $achieved_points totalPoints]
+ }
+ #
+ # When the "achievedPoints" member is set to empty, and "details" are
+ # provided, the caller can request a new calculation based on
+ # the "details" member.
+ #
+ if {[dict get $achieved_points achievedPoints] eq ""
+ && [dict exists $achieved_points details]
+ } {
+ set achievablePoints 0
+ set achievedPoints 0
+ #ns_log notice "RECALC in complete_dict "
+ foreach detail [dict get $achieved_points details] {
+ #ns_log notice "RECALC in complete_dict '$detail'"
+ set achievedPoints [expr {$achievedPoints + [dict get $detail achieved]}]
+ set achievablePoints [expr {$achievablePoints + [dict get $detail achievable]}]
+ }
+ dict set achieved_points achievedPoints $achievedPoints
+ dict set achieved_points achievablePoints $achievablePoints
+ }
+
+ foreach key {
+ achievedPoints
+ achievablePoints
+ totalPoints
+ } {
+ if {![dict exists $achieved_points $key]} {
+ ns_log warning "test_item::grading dict without $key: $achieved_points"
+ ::xo::show_stack
+ dict set achieved_points $key 0
+ }
+ }
+ #
+ # Format all values with two comma precision. The values
+ # achievedPointsRounded and "percentageRounded" are rounded to
+ # the custom precision.
+ #
+ dict with achieved_points {
+ dict set achieved_points achievedPointsRounded [format %.${:precision}f $achievedPoints]
+ set achievedPoints [format %.2f $achievedPoints]
+ set percentage [format %.2f [expr {$totalPoints > 0 ? ($achievedPoints*100.0/$totalPoints) : 0}]]
+ dict set achieved_points percentage $percentage
+ dict set achieved_points percentageRounded [format %.${:precision}f $percentage]
+ }
+ #ns_log notice "R=$achieved_points"
+ return $achieved_points
+ }
+
+ :public method print {-achieved_points:required} {
+ #
+ # Return a dict containing the members "panel" and "csv"
+ # depending on the type of rounding options
+ #
+ set achieved_points [:complete_dict $achieved_points]
+ set grade [:grade -achieved_points $achieved_points]
+ dict with achieved_points {
+ return [list panel [_ xowf.panel_[namespace tail [:info class]]] csv [subst ${:csv}]]
+ }
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: xowf::test_item::grading::GradingRoundPoints
+ #----------------------------------------------------------------------
+ nx::Class create GradingRoundPoints -superclass Grading {
+ :property {csv {$achievedPoints\t$achievedPointsRounded\t$percentage%\t$grade}}
+
+ :public method grade {-achieved_points:required} {
+ #
+ # Return a numeric grade for an exam submission based on rounded
+ # points. On invalid data, return 0.
+ #
+ set achieved_points [:complete_dict $achieved_points]
+ dict with achieved_points {
+ return [:calc_grade -points $achievedPointsRounded -achievable_points $totalPoints]
+ }
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: xowf::test_item::grading::GradingRoundPercentage
+ #----------------------------------------------------------------------
+ nx::Class create GradingRoundPercentage -superclass Grading {
+ :property {csv {$achievedPoints\t$percentage%\t$percentageRounded%\t$grade}}
+
+ :public method grade {-achieved_points:required} {
+ #
+ # Return a numeric grade for an exam submission based on rounded
+ # percentage. On invalid data, return 0.
+ #
+ set achieved_points [:complete_dict $achieved_points]
+ if {[dict exists $achieved_points achievedPoints]} {
+ dict with achieved_points {
+ return [:calc_grade -percentage $percentageRounded]
+ }
+ }
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: xowf::test_item::grading::GradingRoundNone
+ #----------------------------------------------------------------------
+ nx::Class create GradingRoundNone -superclass Grading {
+ :property {csv {$achievedPoints\t$percentage%\t$grade}}
+
+ :public method grade {-achieved_points:required} {
+ #
+ # Return a numeric grade for an exam submission based with no
+ # special rounding (2 digits). On invalid data, return 0.
+ #
+ if {[dict exists $achieved_points achievedPoints]} {
+ set achieved_points [:complete_dict $achieved_points]
+ dict with achieved_points {
+ return [:calc_grade -percentage $percentage]
+ }
+ }
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Class: xowf::test_item::grading::GradingNone
+ #----------------------------------------------------------------------
+ nx::Class create GradingNone -superclass Grading {
+ #
+ # Grading scheme, which omits grading at all.
+ #
+ :property {csv {$achievedPoints\t$percentage%}}
+
+ :public method grade {-achieved_points:required} {
+ #
+ # No grading scheme defined, return grading 0.
+ #
+ return 0
+ }
+ }
+
+ #----------------------------------------------------------------------
+ # Create instances of the Grading Schemes
+ #----------------------------------------------------------------------
+ GradingRoundPoints create ::xowf::test_item::grading::round-points \
+ -precision 2 \
+ -percentage_boundaries {50 60 70 80}
+
+ GradingRoundPercentage create ::xowf::test_item::grading::round-percentage \
+ -precision 2 \
+ -percentage_boundaries {50 60 70 80}
+
+ GradingRoundNone create ::xowf::test_item::grading::round-none \
+ -percentage_boundaries {50 60 70 80}
+
+ GradingNone create ::xowf::test_item::grading::none -percentage_boundaries {} \
+ -title #xowf.Grading_scheme-None#
+
+
+ #----------------------------------------------------------------------
+ # Class: xowf::test_item::grading::gradingGradingRoundNone
+ #----------------------------------------------------------------------
+
+ ad_proc -private ::xowf::test_item::grading::grading_scheme_wf_item_id {
+ -package_id:required
+ -parent_id:required
+ } {
+
+ Return and cache the item_id of the edit-grading-scheme.wf. Maybe,
+ we should generalize this function for other cases as well,
+ therefore, we make this for the time being private.
+
+ } {
+ #
+ # The mapping of the "edit-grading-scheme.wf" to its item_id is
+ # very stable, unless someone defines another workflow
+ # "edit-grading-scheme.wf". So we use here global cache, knowing
+ # that this might not be universally correct.
+ #
+ set form_item_id [acs::misc_cache eval xowf-edit-grading-scheme.wf {
+ #ns_log notice "??? load edit-grading-scheme-wf"
+ ::$package_id instantiate_forms \
+ -parent_id $parent_id \
+ -default_lang en \
+ -forms edit-grading-scheme.wf
+ }]
+
+ return $form_item_id
+ }
+
+ ad_proc -private ::xowf::test_item::grading::flush_grading_schemes {
+ -package_id:required
+ -parent_id:required
+ } {
+
+ Helper to hide the implementation details of the flushed cache.
+ For now, we flush all grading schemes, but probably it would be
+ sufficient to flush just a subset. The tricky part is that the
+ grading objects are loaded potentially from the foll search
+ hierarchy, starting with the local folder, reaching to the global
+ objects. So, if anything is changed there, we would not notice
+ immediately. Therefore, the passed-in package_id and parent_id are
+ not used currently.
+
+ This function is called, whenever a grading scheme is edited.
+ } {
+ ns_log notice "??? acs::misc_cache :flush_pattern xowf-grading-schemes*"
+ acs::misc_cache flush_pattern -partition_key 0 xowf-grading-schemes*
+ }
+
+ ad_proc ::xowf::test_item::grading::load_grading_schemes {
+ -package_id:required
+ -parent_id:required
+ } {
+
+ Load the actual grading scheme objects defined for the package_id
+ and parent_id. It might be the case that this function is called
+ multiple times by a single request (when e.g. multiple exams are
+ on a single page). So we are caching the result to avoid repeated
+ computations of the same result.
+
+ } {
+ set t0 [clock clicks -microseconds]
+ #
+ # Load the actual grading scheme objects
+ #
+ set grading_info [acs::misc_cache eval xowf-grading-schemes($package_id,$parent_id) {
+ #
+ # First get the item_id of the edit-grading-scheme.wf
+ #
+ set form_item_id [grading_scheme_wf_item_id \
+ -parent_id $parent_id \
+ -package_id $package_id]
+ #
+ # Get its instances. When creating the instances, the grading
+ # objects are as well created.
+ #
+ ::xowiki::FormPage get_form_entries \
+ -base_item_ids $form_item_id \
+ -form_fields {} \
+ -publish_status ready|production \
+ -parent_id $parent_id \
+ -package_id $package_id \
+ -initialize true
+
+ set grading_info ""
+ foreach gso [::xowf::test_item::grading::Grading info instances -closure] {
+ dict set grading_info $gso [$gso serialize]
+ }
+ set grading_info
+ }]
+
+ #
+ # Recreate the grading scheme objects that do not exist in the
+ # current thread.
+ #
+ foreach gso [dict keys $grading_info] {
+ if {![nsf::is object $gso]} {
+ eval [dict get $grading_info $gso]
+ $gso destroy_on_cleanup
+ }
+ }
+ set t1 [clock clicks -microseconds]
+ ns_log notice "??? load_grading_schemes part2 [expr {($t1-$t0)/1000.0}]ms "
+ }
+
+}
+
+::xo::library source_dependent
+#
+# Local variables:
+# mode: tcl
+# tcl-indent-level: 2
+# eval: (setq tcl-type-alist (remove* "method" tcl-type-alist :test 'equal :key 'car))
+# indent-tabs-mode: nil
+# End:
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.190 -r1.7.2.191
--- openacs-4/packages/xowf/tcl/test-item-procs.tcl 27 Dec 2021 14:31:32 -0000 1.7.2.190
+++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 3 Jan 2022 16:00:36 -0000 1.7.2.191
@@ -197,10 +197,6 @@
#
test_item set richtextWidget {richtext,editor=ckeditor4,ck_package=basic,displayMode=inline,extraPlugins=}
- test_item instproc makeSpec {-name:required dict} {
- return [list [list $name [:dict_to_fc $dict]]]
- }
-
test_item instproc feed_back_definition {} {
#
# Return the definition of the feed_back widgets depending on the
@@ -323,6 +319,7 @@
}
if {${:grading} ne "none" && [llength ${:grading}] >1} {
+ set grading_dict {_name grading _type select}
dict set grading_dict default [lindex ${:grading} 0]
dict set grading_dict options {}
foreach o ${:grading} {
@@ -331,33 +328,37 @@
dict set grading_dict form_item_wrapper_CSSclass form-inline
dict set grading_dict label #xowf.Grading-Scheme#
dict set grading_dict required true
- set gradingSpec [list [list grading [:dict_to_fc -type select $grading_dict]]]
+ set gradingSpec [list [:dict_to_spec -aspair $grading_dict]]
} else {
set gradingSpec ""
}
if {$can_shuffle} {
+ set shuffle_dict {_name shuffle _type radio}
dict set shuffle_dict horizontal true
dict set shuffle_dict form_item_wrapper_CSSclass form-inline
dict set shuffle_dict default none
dict set shuffle_dict label #xowf.Shuffle#
dict set shuffle_dict options \
"{#xowf.shuffle_none# none} {#xowf.shuffle_peruser# peruser} {#xowf.shuffle_always# always}"
set shuffleSpec [subst {
- [list [list shuffle [:dict_to_fc -type radio $shuffle_dict]]]
+ [list [:dict_to_spec -aspair $shuffle_dict]]
{show_max {number,form_item_wrapper_CSSclass=form-inline,min=1,label=#xowf.show_max#}}
}]
} else {
set shuffleSpec ""
}
#
- # Default towcol spec
+ # Default twocol spec
#
- dict set twocolDict label #xowf.Twocol_layout#
- dict set twocolDict default f
- dict set twocolDict form_item_wrapper_CSSclass form-inline
- dict set twocolDict _type boolean_checkbox
+ set twocolDict {
+ _name twocol
+ _type boolean_checkbox
+ label #xowf.Twocol_layout#
+ default f
+ form_item_wrapper_CSSclass form-inline
+ }
if {${:question_type} in {section case}} {
#
@@ -399,7 +400,7 @@
$shuffleSpec
$gradingSpec
$typeSpecificComponentSpec
- [expr {$twocolDict ne "" ? [:makeSpec -name twocol $twocolDict] : ""}]
+ [list [:dict_to_spec -aspair $twocolDict]]
{interaction {$interaction_class,$options,feedback_level=${:feedback_level},auto_correct=${:auto_correct},label=}}
[:feed_back_definition]
}]
@@ -641,11 +642,15 @@
set intro_text [:get_named_sub_component_value text]
append intro_text [:text_attachments]
+ set fc_dict {
+ _name answer
+ _type textarea
+ disabled_as_div 1
+ label #xowf.answer#
+ autosave true
+ }
dict set fc_dict rows [:get_named_sub_component_value lines]
dict set fc_dict cols [:get_named_sub_component_value columns]
- dict set fc_dict disabled_as_div 1
- dict set fc_dict label #xowf.answer#
- dict set fc_dict autosave true
if {${:auto_correct}} {
dict set fc_dict correct_when [:comp_correct_when_from_value [:get_named_sub_component_value correct_when]]
@@ -654,7 +659,7 @@
set form [:form_markup -interaction text -intro_text $intro_text -body @answer@]
lappend fc \
@categories:off @cr_fields:hidden \
- "answer:[:dict_to_fc -type textarea $fc_dict]"
+ [:dict_to_spec $fc_dict]
#ns_log notice "text_interaction $form\n$fc"
${:object} set_property -new 1 form $form
@@ -731,10 +736,9 @@
lines [dict get $value $fieldName.lines]]
}
+ set fc_dict { _name answer _type text_fields disabled_as_div 1 label ""}
dict set fc_dict shuffle_kind [${:parent_field} get_named_sub_component_value shuffle]
dict set fc_dict show_max [${:parent_field} get_named_sub_component_value show_max]
- dict set fc_dict disabled_as_div 1
- dict set fc_dict label ""
dict set fc_dict options $options
dict set fc_dict answer $answer
dict set fc_dict descriptions $solution
@@ -745,10 +749,10 @@
set fc {}
lappend fc \
- answer:[:dict_to_fc -type text_fields $fc_dict] \
+ [:dict_to_spec $fc_dict] \
@categories:off @cr_fields:hidden
- ns_log notice "short_text_interaction $form\n$fc"
+ #ns_log notice "short_text_interaction $form\n$fc"
${:object} set_property -new 1 form $form
${:object} set_property -new 1 form_constraints $fc
set anon_instances true ;# TODO make me configurable
@@ -855,8 +859,7 @@
incr count
}
- #dict set fc_dict shuffle_kind [${:parent_field} get_named_sub_component_value shuffle]
- #dict set fc_dict show_max [${:parent_field} get_named_sub_component_value show_max]
+ set fc_dict {_name answer _type reorder_box}
dict set fc_dict disabled_as_div 1
dict set fc_dict label ""
dict set fc_dict options $options
@@ -866,7 +869,7 @@
set form [:form_markup -interaction reorder -intro_text $intro_text -body @answer@]
set fc {}
lappend fc \
- answer:[:dict_to_fc -type reorder_box $fc_dict] \
+ [:dict_to_spec $fc_dict] \
@categories:off @cr_fields:hidden
#ns_log notice "reorder_interaction $form\n$fc"
@@ -933,6 +936,8 @@
lappend solution [dict get $value $fieldName.solution]
}
+ dict set fc_dict _name answer
+ dict set fc_dict _type [expr {${:multiple} ? "checkbox" : "radio"}]
dict set fc_dict richtext 1
dict set fc_dict answer $correct
dict set fc_dict options $options
@@ -942,11 +947,10 @@
dict set fc_dict show_max [${:parent_field} get_named_sub_component_value show_max]
set interaction [expr {${:multiple} ? "mc" : "sc"}]
- set widget [expr {${:multiple} ? "checkbox" : "radio"}]
set form [:form_markup -interaction $interaction -intro_text $intro_text -body @answer@]
set fc {}
lappend fc \
- answer:[:dict_to_fc -type $widget $fc_dict] \
+ [:dict_to_spec $fc_dict] \
@categories:off @cr_fields:hidden
#ns_log notice "mc_interaction2 $form\n$fc"
@@ -1015,6 +1019,7 @@
set max_nr_submission_files [${:parent_field} get_named_sub_component_value max_nr_submission_files]
#dict set file_dict choose_file_label "Datei hochladen"
+ set file_dict {_name answer _type file}
if {$max_nr_submission_files > 1} {
dict set file_dict repeat 1..$max_nr_submission_files
dict set file_dict repeat_add_label #xowiki.form-repeatable-add-another-file#
@@ -1027,7 +1032,7 @@
set form [:form_markup -interaction upload -intro_text $intro_text -body @answer@]
lappend fc \
@categories:off @cr_fields:hidden \
- "answer:[:dict_to_fc -type file $file_dict]"
+ [:dict_to_spec $file_dict]
${:object} set_property -new 1 form $form
${:object} set_property -new 1 form_constraints $fc
@@ -1050,7 +1055,6 @@
Class create test_section -superclass {TestItemField} -parameter {
{multiple true}
{form en:edit-interaction.wf}
- {QM ::xowf::test_item::question_manager}
}
test_section set item_type Composite
test_section set closed_question_type false
@@ -1104,7 +1108,7 @@
# set substvalues [$formObj property substvalues]
# if {$substvalues ne ""} {
# ns_log notice ".... [$formObj name] has substvalues $substvalues"
- # set d [${:QM} percent_substitute_in_form \
+ # set d [:QM percent_substitute_in_form \
# -obj ${:object} \
# -form_obj $formObj \
# -position $position \
@@ -1135,7 +1139,7 @@
}
set result "-with_$kind"
}]
- set question_infos [${:QM} question_info \
+ set question_infos [:QM question_info \
-question_number_label "#xowf.subquestion#" \
{*}$title_options \
-numbers $numbers \
@@ -1148,7 +1152,7 @@
# Build a single clean form based on the question infors,
# containing all selected items.
#
- set aggregatedForm [${:QM} aggregated_form \
+ set aggregatedForm [:QM aggregated_form \
-with_grading_box hidden \
$question_infos]
set aggregatedFC [dict get $question_infos form_constraints]
@@ -1171,8 +1175,8 @@
# Automatically compute the minutes and points of the composite
# field and update the form field.
#
- set total_minutes [${:QM} total_minutes $question_infos]
- set total_points [${:QM} total_points $question_infos]
+ set total_minutes [:QM total_minutes $question_infos]
+ set total_points [:QM total_points $question_infos]
[${:parent_field} get_named_sub_component minutes] value $total_minutes
[${:parent_field} get_named_sub_component points] value $total_points
@@ -1241,19 +1245,21 @@
list [$folder_obj title] ../[$folder_obj name]
}]
+ set pool_dict {_name folder _type select}
dict set pool_dict required true
dict set pool_dict options $folder_options
dict set pool_dict default ../[::$current_folder_id name]
dict set pool_dict label #xowf.pool_question_folder#
+ set item_dict {_name item_types _type checkbox}
dict set item_dict options $item_type_options
dict set item_dict default $item_types
dict set item_dict label #xowf.pool_question_item_types#
:create_components [subst {
- {folder {[:dict_to_fc -type select $pool_dict]}}
- {item_types {[:dict_to_fc -type checkbox $item_dict]}}
- {pattern {text,default=*,label=#xowf.pool_question_pattern#}}
+ [list [:dict_to_spec -aspair $pool_dict]]
+ [list [:dict_to_spec -aspair $item_dict]]
+ {pattern {text,default=*,label=#xowf.pool_question_pattern#}}
}]
set :__initialized 1
@@ -1262,14 +1268,16 @@
pool_question instproc convert_to_internal {} {
next
set allowed_item_types [:get_named_sub_component_value item_types]
+
+ set fc_dict {_name answer _type pool_question_placeholder}
dict set fc_dict folder [:get_named_sub_component_value folder]
dict set fc_dict pattern [:get_named_sub_component_value pattern]
dict set fc_dict item_types $allowed_item_types
set form ""
lappend fc \
@categories:off @cr_fields:hidden \
- "answer:[:dict_to_fc -type pool_question_placeholder $fc_dict]"
+ [:dict_to_spec $fc_dict]
${:object} set_property -new 1 form $form
${:object} set_property -new 1 form_constraints $fc
@@ -1317,6 +1325,10 @@
#
# Abstract class for common functionality
#
+
+ :public alias dict_value ::xowiki::formfield::dict_value
+ :alias fc_to_dict ::xowiki::formfield::fc_to_dict
+
:method assert_assessment_container {o:object} {
set ok [expr {[$o is_wf_instance] == 0 && [$o is_wf] == 1}]
if {!$ok} {
@@ -1350,9 +1362,6 @@
}
}
- :method dict_value {dict key {default ""}} {
- expr {[dict exists $dict $key] ? [dict get $dict $key] : $default}
- }
#----------------------------------------------------------------------
# Class: AssessmentInterface
@@ -1400,8 +1409,30 @@
}]
}
+ #----------------------------------------------------------------------
+ # Class: AssessmentInterface
+ # Method: list_equal
+ #----------------------------------------------------------------------
+ :method list_equal { l1 l2 } {
+ #
+ # Compare two lists for equality. This function has to be used,
+ # when lists contain the same elements in the same order, but
+ # these are not literally equal due to, e.g., line breaks
+ # between list elements, etc.
+ #
+ if {[llength $l1] != [llength $l2]} {
+ return 0
+ }
+ foreach e1 $l1 e2 $l2 {
+ if {$e1 ne $e2} {
+ return 0
+ }
+ }
+ return 1
+ }
}
}
+
namespace eval ::xowf::test_item {
nx::Class create Renaming_form_loader -superclass AssessmentInterface {
@@ -1557,59 +1588,18 @@
#
set form_id [$ctx default_load_form_id $form_name]
set obj [$ctx object]
- set form_obj [::xo::db::CrClass get_instance_from_db -item_id $form_id]
+ set form_obj [::xowiki::FormPage get_instance_from_db -item_id $form_id]
return [:rename_attributes $form_obj]
}
-
}
- Renaming_form_loader create renaming_form_loader
+ AssessmentInterface forward FL \
+ [Renaming_form_loader create renaming_form_loader]
}
namespace eval ::xowf::test_item {
- ad_proc -private spec_to_dict {spec} {
- #
- # Convert a single spec to a Tcl dict.
- #
- set elements [split $spec ,]
- dict set result type [lindex $elements 0]
- foreach s [lrange $elements 1 end] {
- switch -glob -- $s {
- *=* {
- set p [string first = $s]
- set attribute [string range $s 0 $p-1]
- set value [::xowiki::formfield::FormField fc_decode [string range $s $p+1 end]]
- dict set result $attribute $value
- }
- default {
- ns_log notice "... spec_to_dict ignores <$s>"
- }
- }
- }
- return $result
- }
-
- ad_proc -private fc_to_dict {form_constraints} {
- #
- # Convert from form_constraint syntax to a dict. This is just a
- # partial implementation, since form constraints are interpreted
- # from left to right, changing types, etc., which is not
- # supported here.
- #
- set result ""
- foreach fc $form_constraints {
- #ns_log notice "... fc_to_dict works on <$fc>"
- if {[regexp {^([^:]+):(.*)$} $fc _ field_name definition]} {
- if {[string match @* $field_name]} continue
- dict set result $field_name [spec_to_dict $definition]
- dict set result $field_name definition $definition
- }
- }
- return $result
- }
-
ad_proc -private tdom_render {script} {
#
# Render a snippet of tdom-html commands (as e.g. form-fields) into
@@ -1624,10 +1614,7 @@
}
ns_log warning "tdom_render: $script returns empty"
}
-}
-namespace eval ::xowf::test_item {
-
nx::Class create Answer_manager -superclass AssessmentInterface {
#
@@ -1654,6 +1641,7 @@
# - get_IPs
# - revisions_up_to
# - last_time_in_state
+ # - last_time_switched_to_state
# - state_periods
#
@@ -1688,16 +1676,15 @@
$parentObj set_property -new 1 wfName $wfName
set wfTitle [$parentObj property _title]
- set questionObjs [::xowf::test_item::question_manager question_objs $parentObj]
+ set questionObjs [:QM question_objs $parentObj]
set wfQuestionNames {}
set wfQuestionTitles {}
set attributeNames {}
foreach form_obj $questionObjs {
+ lappend attributeNames \
+ [:FL form_name_based_attribute_stem [$form_obj name]]
- lappend attributeNames [xowf::test_item::renaming_form_loader \
- form_name_based_attribute_stem [$form_obj name]]
-
lappend wfQuestionNames ../[$form_obj name]
lappend wfQuestionTitles [$form_obj title]
}
@@ -1841,12 +1828,12 @@
# Get the question dict, which is a mapping between question
# names and form_obj_ids.
#
- set question_dict [renaming_form_loader name_to_question_obj_dict \
+ set question_dict [:FL name_to_question_obj_dict \
[dict get $combined_form_info question_objs]]
# ns_log notice "export_answers: question_dict: $question_dict"
set form_constraints [lsort -unique [dict get $combined_form_info form_constraints]]
- set fc_dict [fc_to_dict $form_constraints]
+ set fc_dict [:fc_to_dict $form_constraints]
#ns_log notice "... form_constraints ([llength $form_constraints]) $form_constraints"
#ns_log notice ".... dict $fc_dict"
#
@@ -1963,7 +1950,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: delete_all_answer_data
#----------------------------------------------------------------------
:public method delete_all_answer_data {obj:object} {
#
@@ -1986,7 +1973,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: delete_scheduled_atjobs
#----------------------------------------------------------------------
:public method delete_scheduled_atjobs {obj:object} {
#
@@ -2011,7 +1998,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: get_answer_wf
#----------------------------------------------------------------------
:public method get_answer_wf {obj:object} {
#
@@ -2025,7 +2012,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: get_wf_instances
#----------------------------------------------------------------------
:public method get_wf_instances {
{-initialize false}
@@ -2058,7 +2045,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: get_answers
#----------------------------------------------------------------------
:public method get_answers {{-state ""} {-extra_attributes {}} wf:object} {
#
@@ -2077,8 +2064,9 @@
if {$state ne "" && [$i state] ne $state} {
continue
}
- set answerAttributes [xowf::test_item::renaming_form_loader answer_attributes \
- [$i instance_attributes]]
+
+ set answerAttributes \
+ [:FL answer_attributes [$i instance_attributes]]
foreach extra $extra_attributes {
lappend answerAttributes $extra [$i property $extra]
}
@@ -2090,7 +2078,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: get_duration
#----------------------------------------------------------------------
:public method get_duration {{-exam_published_time ""} revision_sets} {
#
@@ -2115,14 +2103,15 @@
dict set r examPublished [clock format $examPublishedClock -format "%H:%M:%S"]
set epTimeDiff [expr {$toClock - $examPublishedClock}]
dict set r examPublishedDuration "[expr {$epTimeDiff/60}]m [expr {$epTimeDiff%60}]s"
+ ns_log notice "EP examPublishedDuration [dict get $r examPublishedDuration] EP [dict get $r examPublished] $exam_published_time"
dict set r examPublishedSeconds $epTimeDiff
}
return $r
}
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: get_IPs
#----------------------------------------------------------------------
:public method get_IPs {revision_sets} {
#
@@ -2141,7 +2130,7 @@
#----------------------------------------------------------------------
# Class: Answer_manager
- # Method: last_time_in_state
+ # Method: revisions_up_to
#----------------------------------------------------------------------
:public method revisions_up_to {revision_sets revision_id} {
#
@@ -2162,7 +2151,7 @@
# Class: Answer_manager
# Method: last_time_in_state
#----------------------------------------------------------------------
- :public method last_time_in_state {revision_sets -state:required -with_until:switch } {
+ :public method last_time_in_state {revision_sets -state:required} {
#
# Loops through revision sets and retrieves the latest date
# where state is equal the specified value.
@@ -2184,6 +2173,38 @@
#----------------------------------------------------------------------
# Class: Answer_manager
+ # Method: last_time_switched_to_state
+ #----------------------------------------------------------------------
+ :public method last_time_switched_to_state {revision_sets -state:required {-before ""}} {
+ #
+ # Loops through revision sets and retrieves the latest date
+ # where state is equal the specified value.
+ #
+ # @param revision_sets a list of ns_sets containing revision
+ # data. List is assumed to be sorted in descending
+ # creation_date order (as retrieved by get_revision_sets)
+ #
+ # @return a date
+ #
+ set result ""
+ set last_state ""
+ foreach ps $revision_sets {
+ if {$before ne ""} {
+ set currentClock [clock scan [::xo::db::tcl_date [ns_set get $ps last_modified] tz]]
+ if {$currentClock > $before} {
+ break
+ }
+ }
+ if {$last_state ne $state && $state eq [ns_set get $ps state]} {
+ set result [ns_set get $ps last_modified]
+ }
+ set last_state [ns_set get $ps state]
+ }
+ return $result
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Answer_manager
# Method: pretty_period
#----------------------------------------------------------------------
:method pretty_period {{-dayfmt %q} {-timefmt %H:%M} from to} {
@@ -2252,10 +2273,15 @@
-answer_attributes:required
} {
#
+ # Calculate the achieved_points dict for an exam submission. This
+ # function iterates of every question and sums up the achievable
+ # and achieved points of the questions. The per-question results
+ # are placed in the dict entry "details".
+ #
# This method has to be called after the instance was rendered,
# since it uses the produced form_fields.
#
- # @return dict containing "achievedPoints", "details" and "achievablePoints"
+ # @return dict containing "achievedPoints", "achievablePoints" and "details"
#
set all_form_fields [::xowiki::formfield::FormField info instances -closure]
set totalPoints 0
@@ -2323,15 +2349,19 @@
// Submit button of grading dialog was pressed.
//
var id = ev.currentTarget.dataset.id;
- var gradingBox = document.getElementById(id);
+ var gradingBox = document.getElementById(id);
var pointsInput = document.querySelector('#grading-points');
var helpBlock = document.querySelector('#grading-points-help-block');
- var comment = document.querySelector('#grading-comment').value;
+ var comment = document.querySelector('#grading-comment').value;
var points = pointsInput.value;
var pointsFormGroup = pointsInput.parentElement.parentElement;
+ var percentage = "";
if (points != "") {
- if(parseFloat(points) > parseFloat(pointsInput.max) || parseFloat(points) < parseFloat(pointsInput.min)){
+ //
+ // Number valdation
+ //
+ if (parseFloat(points) > parseFloat(pointsInput.max) || parseFloat(points) < parseFloat(pointsInput.min)){
if (parseFloat(points) > parseFloat(pointsInput.max)) {
helpBlock.textContent = '[_ xowf.Value_max] ' + pointsInput.max;
} else {
@@ -2345,12 +2375,18 @@
pointsFormGroup.classList.remove('has-error');
helpBlock.classList.add('hidden');
}
+ var achievable = gradingBox.dataset.achievable;
+ if (achievable != "") {
+ percentage = "(" + (points*100.0/achievable).toFixed(2) + "%)";
+ }
+
} else {
pointsFormGroup.removeClass('has-error');
helpBlock.classList.add('hidden');
}
document.querySelector('#' + id + ' .points').textContent = points;
+ document.querySelector('#' + id + ' .percentage').textContent = percentage;
document.querySelector('#' + id + ' .comment').textContent = comment;
gradingBox.dataset.achieved = points;
gradingBox.dataset.comment = comment;
@@ -2548,7 +2584,9 @@
if {$revision_id eq ""} {
set revision_sets [:revisions_up_to $revision_sets $live_revision_id]
}
- set last_published [:last_time_in_state $parent_revsion_sets -state published]
+ set toClock [clock scan [::xo::db::tcl_date [ns_set get [lindex $revision_sets end] last_modified] tz]]
+ set last_published [:last_time_switched_to_state $parent_revsion_sets -state published -before $toClock]
+ ns_log notice "LAST PUBL $last_published"
set duration [:get_duration -exam_published_time $last_published $revision_sets]
set state [$answerObj state]
@@ -2709,9 +2747,8 @@
{-examWf:object}
{-submissions:object}
} {
- set combined_form_info [::xowf::test_item::question_manager combined_question_form $examWf]
- set nameToQuestionObj [xowf::test_item::renaming_form_loader \
- name_to_question_obj_dict \
+ set combined_form_info [:QM combined_question_form $examWf]
+ set nameToQuestionObj [:FL name_to_question_obj_dict \
[dict get $combined_form_info question_objs]]
#
# Sort items by username
@@ -3111,7 +3148,8 @@
}
set submission_state [$submission state]
- set noManualGrading [expr {$submission_state ne "done" || $exam_state eq "published"}]
+ #set noManualGrading [expr {$submission_state ne "done" || $exam_state eq "published"}]
+ set noManualGrading [expr {$exam_state eq "published"}]
set grading_boxes [${root} selectNodes {//div[contains(@class,'grading-box')]}]
foreach grading_box $grading_boxes {
@@ -3122,7 +3160,7 @@
: ""}]
ns_log notice "... QN '$qn' item_type '$item_type'" \
"submission state $submission_state" \
- "exam state $exam_state"
+ "exam state $exam_state noManualGrading $noManualGrading"
if {$noManualGrading} {
:dom class add $grading_box {a[contains(@class,'manual-grade')]} hidden
}
@@ -3158,6 +3196,10 @@
}
} else {
:dom node replace $grading_box {span[@class='points']} {::html::t $achieved}
+ if {$achievable ne ""} {
+ set percentage [format %.2f [expr {$achieved*100.0/$achievable}]]
+ :dom node replace $grading_box {span[@class='percentage']} {::html::t ($percentage%)}
+ }
}
#
# When "comment" is empty, do not show the label.
@@ -3232,10 +3274,9 @@
return ""
}
}
+ set answerAttributes \
+ [:FL answer_attributes [$submission instance_attributes]]
- set answerAttributes [xowf::test_item::renaming_form_loader \
- answer_attributes [$submission instance_attributes]]
-
#
# "render_full_submission_form" calls "summary_form" to obtain the
# user's answers to all questions.
@@ -3365,28 +3406,45 @@
# Method: grading_scheme
#----------------------------------------------------------------------
:method grading_scheme {
+ {-examWf:object,required}
{-grading:alnum,0..n ""}
{-total_points}
} {
#
- # The management of the grading scheme has to be extended. For the
- # time being, we have a single grading scheme with the option to
- # round to full points or not. When an exam has less than 40
- # points, we do not round per default, since this rounding could
- # provide more than 1 percent of the result. This should be made
- # configurable (also in www-print-answer-table, which is not used
- # right now).
+ # Return the grading scheme object based on the provided short
+ # name. In case the grading scheme belongs to the predefined
+ # grading schemes, the object can be directly loaded. When the
+ # name refers to a user-defined grading object, this might have
+ # to be loaded.
#
+ # We could consider some hints about the usefulness of the
+ # chosen grading scheme, E.g., when an exam has 40 points or
+ # less, rounding has the potential effect that a high percentage
+ # of the grade is just due to rounding. So, in such cases a
+ # non-rounding scheme should be preferred.
+ #
# @return fully qualified grading scheme object
#
if {$grading eq ""} {
- set grading [expr {$total_points < 40 ? "wi1_noround" : "wi1p"}]
+ #set grading [expr {$total_points < 40 ? "round-none" : "round-points"}]
+ set grading "none"
+ ns_log notice "--- legacy grading scheme -> none"
}
set grading_scheme ::xowf::test_item::grading::$grading
- if {[info commands $grading_scheme] eq ""} {
- set grading_scheme ::xowf::test_item::grading::wi1
+ if {![nsf::is object $grading_scheme]} {
+ #
+ # Maybe we have to load this grading scheme...
+ #
+ ::xowf::test_item::grading::load_grading_schemes \
+ -package_id [$examWf package_id] \
+ -parent_id [$examWf parent_id]
+ ns_log notice "--- grading schemes loaded"
}
+ if {![nsf::is object $grading_scheme]} {
+ set grading_scheme ::xowf::test_item::grading::round-points
+ ns_log notice "--- fallback to default grading scheme object"
+ }
#ns_log notice "USE grading_scheme $grading_scheme"
return $grading_scheme
}
@@ -3413,9 +3471,9 @@
#
# @return dict containing "do_stream" and "HTML"
#
- set combined_form_info [::xowf::test_item::question_manager combined_question_form $examWf]
+ set combined_form_info [:QM combined_question_form $examWf]
set autograde [dict get $combined_form_info autograde]
- set totalPoints [::xowf::test_item::question_manager total_points \
+ set totalPoints [:QM total_points \
-max_items [$examWf property max_items ""] \
$combined_form_info]
@@ -3433,9 +3491,10 @@
"valid [dict get $combined_form_info question_objs]"
set form_objs ""
}
+ ns_log notice "--- grading '$grading'"
+ set grading_scheme [:grading_scheme -examWf $examWf -grading $grading -total_points $totalPoints]
+ #ns_log notice "--- grading_scheme $grading_scheme from grading '$grading'"
- set grading_scheme [:grading_scheme -grading $grading -total_points $totalPoints]
-
set :grade_dict {}
set :grade_csv ""
@@ -3506,7 +3565,7 @@
}
if {$export} {
- set recutil [xowf::test_item::answer_manager recutil_create \
+ set recutil [:AM recutil_create \
-clear \
-exam_id [$wf parent_id] \
-fn [expr {$filter_id eq "" ? "all.rec" : "$filter_id.rec"}]
@@ -3564,8 +3623,8 @@
-with_exam_heading [expr {!$as_student}] \
-with_signature $withSignature]
- set html [dict get $d HTML]
- dict set results [$submission set creation_user] [dict get $d results]
+ set html [:dict_value $d HTML]
+ dict set results [$submission set creation_user] [:dict_value $d results]
if {$do_stream && $html ne ""} {
ns_write [lang::util::localize $html]
@@ -3735,10 +3794,11 @@
#
unset -nocomplain $key
} else {
- #ns_log notice "### key exists [info exists $key]"
+ #ns_log notice "### answer_form_field_objs key exists [info exists $key]"
if {![info exists $key]} {
#ns_log notice "form_info: $form_info"
set fc [lsort -unique [dict get $form_info disabled_form_constraints]]
+ #ns_log notice "### FC $fc"
set pc_params [::xo::cc perconnection_parameter_get_all]
if {$generic} {
set fc [:replace_in_fc -fc $fc shuffle_kind none]
@@ -3841,7 +3901,7 @@
{-view_all_method print-answers}
{-with_answers:boolean true}
{-state done}
- {-grading_scheme ::xowf::test_item::grading::wi1}
+ {-grading_scheme ::xowf::test_item::grading::none}
wf:object
} {
#
@@ -3850,7 +3910,7 @@
#
#set form_info [:combined_question_form -with_numbers $wf]
- set form_info [::xowf::test_item::question_manager combined_question_form $wf]
+ set form_info [:QM combined_question_form $wf]
set answer_form_field_objs [:answer_form_field_objs -wf $wf $form_info]
set autograde [dict get $form_info autograde]
@@ -4223,15 +4283,14 @@
# has to be provided with valid HTML markup.
#
- set answers [xowf::test_item::answer_manager get_answers $wf]
+ set answers [:AM get_answers $wf]
set nrParticipants [llength $answers]
if {$current_question ne ""} {
- set answered [xowf::test_item::renaming_form_loader answers_for_form \
+ set answered [:FL answers_for_form \
[$current_question name] \
$answers]
} else {
- set answered [xowf::test_item::answer_manager get_answers \
- -state $target_state $wf]
+ set answered [:AM get_answers -state $target_state $wf]
}
set nrAnswered [llength $answered]
@@ -4510,7 +4569,8 @@
}
}
- Answer_manager create answer_manager
+ AssessmentInterface forward AM \
+ [Answer_manager create answer_manager]
}
@@ -4675,7 +4735,7 @@
# might be required (e.g. instantiate the other package, etc.).
#
if {![nsf::is object ::$folder_id]} {
- ::xo::db::CrClass get_instance_from_db -item_id $folder_id
+ ::xowiki::FormPage get_instance_from_db -item_id $folder_id
}
if {[::$folder_id is_link_page]} {
set targetObj [::$folder_id get_target_from_link_page]
@@ -4764,14 +4824,13 @@
# the plain "answer", which can be provided via the "field_name"
# attribute.
#
- set query_dict [fc_to_dict [$pool_question_obj property form_constraints]]
+ set query_dict [:fc_to_dict [$pool_question_obj property form_constraints]]
if {$field_name eq ""} {
#
# No field name was provided, so get the field name from the
# question obj.
#
- set field_name [::xowf::test_item::renaming_form_loader \
- form_name_based_attribute_stem [$pool_question_obj name]]
+ set field_name [:FL form_name_based_attribute_stem [$pool_question_obj name]]
if {![dict exists $query_dict $field_name]} {
#
# Fall back to field_name "answer". This will be necessary,
@@ -4860,7 +4919,7 @@
error "could not find a replacement item for [$pool_question_obj name]: only duplicate items found"
}
- set form_obj [::xo::db::CrClass get_instance_from_db \
+ set form_obj [::xowiki::FormPage get_instance_from_db \
-item_id [dict get $candidate_dict $new_name item_id]]
#$form_obj initialize
@@ -4919,7 +4978,7 @@
}
#ns_log notice "YYYY OLD NAMES [join $original_question_names { }]"
#ns_log notice "YYYY UPD NAMES [join $exam_question_names { }]"
- if {$original_question_names ne $exam_question_names} {
+ if {![:list_equal $original_question_names $exam_question_names]} {
ns_log notice "YYYY store question names in user's answer workflow"
$answer_obj set_property -new 1 question $exam_question_names
}
@@ -5045,6 +5104,17 @@
# Class: Question_manager
# Method: qualified_question_names
#----------------------------------------------------------------------
+ :method max_items {max:integer,0..1 list} {
+ if {$max ne "" && $max < [llength $list]} {
+ return [lrange $list 0 $max-1]
+ }
+ return $list
+ }
+
+ #----------------------------------------------------------------------
+ # Class: Question_manager
+ # Method: qualified_question_names
+ #----------------------------------------------------------------------
:method qualified_question_names {question_objs} {
#
# Return the question names with parent folder in form of an
@@ -5055,7 +5125,7 @@
lmap question_obj $question_objs {
set parent_id [$question_obj parent_id]
if {![nsf::is object ::$parent_id]} {
- ::xo::db::CrClass get_instance_from_db -item_id $parent_id
+ ::xowiki::FormPage get_instance_from_db -item_id $parent_id
}
set ref [::$parent_id name]/[$question_obj name]
}
@@ -5093,7 +5163,6 @@
-default_lang [$obj lang] \
-forms $questionNames]
- #ns_log notice "load_question_objs called with $obj $names -> $questionForms"
if {[llength $questionForms] < [llength $names]} {
if {[llength $names] == 1} {
ns_log warning "load_question_objs: question '$names' could not be loaded"
@@ -5136,9 +5205,15 @@
# Return the shuffled index position, in case shuffling is turned on.
#
if {$shuffle_id > -1} {
- set form_objs [:question_objs $obj]
- set shuffled [::xowiki::randomized_indices -seed $shuffle_id [llength $form_objs]]
+ #
+ # Take always all questions as the basis for randomization,
+ # also when "max_items" is set.
+ #
+ set shuffled [::xowiki::randomized_indices \
+ -seed $shuffle_id \
+ [:question_count -all $obj]]
set position [lindex $shuffled $position]
+ #ns_log notice "shuffled_index question_count [:question_count $obj] -> <$shuffled> -> position $position"
}
return $position
}
@@ -5165,14 +5240,9 @@
}
#
- # Make sure, we return just up to max_items form_objs.
+ # Return at most max items, when specified.
#
- set max_items [$obj property max_items ""]
- if {$max_items ne ""} {
- set form_objs [lrange $form_objs 0 $max_items-1]
- }
-
- return $form_objs
+ return [:max_items [$obj property max_items ""] $form_objs]
}
#----------------------------------------------------------------------
@@ -5191,10 +5261,10 @@
}
if {[info exists :wfi] && [${:wfi} property question] ne ""} {
set names [${:wfi} property question]
- #ns_log notice "question_names returns obj-specific $names"
+ #ns_log notice "question_names returns obj-specific [join $names]"
} else {
set names [$obj property question]
- #ns_log notice "question_names returns wf-names ($obj property)"
+ #ns_log notice "question_names returns wf-names ($obj property): [join $names]"
}
return $names
}
@@ -5203,17 +5273,19 @@
# Class: Question_manager
# Method: question_count
#----------------------------------------------------------------------
- :public method question_count {obj:object} {
+ :public method question_count {{-all:switch false} obj:object} {
#
# Return the number questions in an exam. It is either the
# number of defined questions, or it might be restricted by the
# property max_items (if defined for "obj").
#
set nr_questions [llength [:question_names $obj]]
- set max_items [$obj property max_items ""]
- if {$max_items ne ""} {
- if {$max_items < $nr_questions} {
- set nr_questions $max_items
+ if {!$all} {
+ set max_items [$obj property max_items ""]
+ if {$max_items ne ""} {
+ if {$max_items < $nr_questions} {
+ set nr_questions $max_items
+ }
}
}
return $nr_questions
@@ -5248,7 +5320,6 @@
# position).
#
:assert_assessment $obj
- #set questions [dict get [$obj instance_attributes] question]
set questions [:question_names $obj]
set result [:load_question_objs $obj [lindex $questions $position]]
return $result
@@ -5480,7 +5551,7 @@
# place.
#
- set dict [lindex [fc_to_dict [dict get $formAttributes form_constraints]] 1]
+ set dict [lindex [:fc_to_dict [dict get $formAttributes form_constraints]] 1]
foreach a [dict get $dict answer] {
set op ""
regexp {^(\S+)\s} $a . op
@@ -5551,9 +5622,7 @@
"[dict get $title_info full_title]
\n"
}
if {$with_grading_box ne ""} {
- set question_name [xowf::test_item::renaming_form_loader \
- form_name_based_attribute_stem \
- [$question_obj name]]
+ set question_name [:FL form_name_based_attribute_stem [$question_obj name]]
set visible [expr {$with_grading_box eq "hidden" ? "hidden" : ""}]
if {$with_grading_box eq "hidden"} {
set question_name answer_$question_name
@@ -5563,6 +5632,7 @@
| data-question_name='$question_name' data-title='[$question_obj title]'
| data-question_id='[$question_obj item_id]'>
| #xowf.Points#:
+ |
| #xowf.feedback#:
|
|
@@ -5615,8 +5685,9 @@
set position -1
set positions [lmap form_obj $form_objs {incr position}]
}
+
foreach form_obj $form_objs number $numbers position $positions {
- set form_obj [::xowf::test_item::renaming_form_loader rename_attributes $form_obj]
+ set form_obj [:FL rename_attributes $form_obj]
set form_title [$form_obj title]
set minutes [:question_property $form_obj minutes]
set points [:question_property $form_obj points]
@@ -5807,10 +5878,7 @@
#ns_log notice "XXX combined_question_form fos=$form_objs all_form_objs=$all_form_objs <$positions>"
if {$user_specific} {
- set max_items [$obj property max_items ""]
- if {$max_items ne ""} {
- set form_objs [lrange $form_objs 0 $max_items-1]
- }
+ set form_objs [:max_items [$obj property max_items ""] $form_objs]
}
if {$with_numbers} {
set numbers ""
@@ -6090,9 +6158,9 @@
# Get the form-field objects with all alternatives (use flag
# "-generic")
#
- set form_field_objs [xowf::test_item::answer_manager answer_form_field_objs \
+ set form_field_objs [:AM answer_form_field_objs \
-generic \
- -wf [xowf::test_item::answer_manager get_answer_wf $obj] \
+ -wf [:AM get_answer_wf $obj] \
$combined_form_info]
#
# Get the persisted statistics from the workflow
@@ -6174,8 +6242,8 @@
set autograde [dict get $combined_form_info autograde]
set revision_sets [$obj get_revision_sets]
- set published_periods [xowf::test_item::answer_manager state_periods $revision_sets -state published]
- set review_periods [xowf::test_item::answer_manager state_periods $revision_sets -state submission_review]
+ set published_periods [:AM state_periods $revision_sets -state published]
+ set review_periods [:AM state_periods $revision_sets -state submission_review]
set total_minutes [:total_minutes -max_items $max_items $combined_form_info]
set total_points [:total_points -max_items $max_items $combined_form_info]
set max_items_msg ""
@@ -6234,7 +6302,7 @@
if {[dict exists $title_info $property]} {
set value [dict get $title_info $property]
if {$value eq ""} {
- ns_log notice "missing $property in '$title_info'"
+ ns_log notice "missing property '$property' in '$title_info'"
set value 0
}
set total [expr {$total + $value}]
@@ -6252,11 +6320,7 @@
# When max_items is nonempty, return the title infos of all
# items. Otherwise, just the specified number of items.
#
- set title_infos [dict get $form_info title_infos]
- if {$max_items ne ""} {
- set title_infos [lrange $title_infos 0 $max_items-1]
- }
- return $title_infos
+ return [:max_items $max_items [dict get $form_info title_infos]]
}
#----------------------------------------------------------------------
@@ -6276,7 +6340,7 @@
#----------------------------------------------------------------------
:public method total_points {{-max_items:integer,0..1 ""} form_info} {
#
- # Compute the maximal achievable points of an exam based on the
+ # Compute the maximum achievable points of an exam based on the
# form_info dict.
#
return [:total -property points [:title_infos -max_items $max_items $form_info]]
@@ -6344,7 +6408,7 @@
# @return time string as returned from the database
#
if {[$manager property synchronized 0]} {
- set parent_obj [::xo::db::CrClass get_instance_from_db -item_id [$answer_obj parent_id]]
+ set parent_obj [::xowiki::FormPage get_instance_from_db -item_id [$answer_obj parent_id]]
set base_time [$parent_obj last_modified]
} else {
set base_time [$answer_obj creation_date]
@@ -6434,9 +6498,9 @@
}
}
}
-
- Question_manager create question_manager
-
+ set qm [Question_manager create question_manager]
+ AssessmentInterface forward QM $qm
+ ::xowiki::formfield::TestItemField instforward QM $qm
}
namespace eval ::xowiki::formfield {
@@ -6509,7 +6573,7 @@
}
::xowiki::formfield::pool_question_placeholder {
set type PoolQuestion
- set item_dict [::xowf::test_item::question_manager get_pool_questions \
+ set item_dict [:QM get_pool_questions \
-field_name $field_name ${:object} ""]
set counts ""
@@ -6559,185 +6623,6 @@
}
}
-namespace eval ::xowf::test_item::grading {
- nx::Class create Grading {
- :property {percentage_boundaries {50.0 60.0 70.0 80.0}}
-
- :method calc_grade {-percentage -points:required -achieved_points:required} {
- #
- # Return a numeric grade based on achieved_points dict and
- # percentage_mapping. On invalid data, return 0.
- #
- # Important dict members of "achieved_points":
- # - achievedPoints: points that the student has achieved in her exam
- # - achievablePoints: points that the student could have achieved so far
- # - totalPoints: points that the student can achieve when finishing the exam
- #
- # achieved_points: {achievedPoints 4.0 achievablePoints 4 totalPoints 4}
- # percentage_mapping: {50.0 60.0 70.0 80.0}
- #
- #if {![dict exists $achieved_points achievablePoints] && [dict exists $achieved_points totalPoints]} {
- # ns_log warning "test_item::grading legacy call, use 'achievablePoints' instead of 'totalPoints'"
- # dict set achieved_points achievablePoints [dict get $achieved_points totalPoints]
- #}
-
- if {![info exists percentage]} {
- #ns_log notice "=== calc_grade compute percentage from totalPoints"
-
- if {[dict exists $achieved_points totalPoints] && [dict get $achieved_points totalPoints] > 0} {
- set percentage \
- [format %.2f [expr {
- ($points*100/
- [dict get $achieved_points totalPoints]) + 0.00001
- }]]
- }
- } else {
- ns_log notice "USE PROVIDED percentage '$percentage'"
- }
- if {[info exists percentage]} {
- set grade 1
- set gradePos 0
- foreach boundary ${:percentage_boundaries} {
- if {$percentage < $boundary} {
- set grade [expr {5-$gradePos}]
- break
- }
- incr gradePos
- }
- } else {
- set grade 0
- }
- return $grade
- }
-
- :public method print {-achieved_points:required} {
- #
- # Return the achievedPoints when available (or empty).
- #
- if {[dict exists $achieved_points achievedPoints]} {
- return [dict get $achieved_points achievedPoints]
- }
- }
-
- :method complete_dict {achieved_points} {
- #
- # This is a transitional method, just for defensive programming
- # to make sure, nobody else uses the legacy field... should
- # disappear soon.
- #
- if {![dict exists $achieved_points achievablePoints] && [dict exists $achieved_points totalPoints]} {
- ns_log warning "test_item::grading legacy call, use 'achievablePoints' instead of 'totalPoints'"
- dict set achieved_points achievablePoints [dict get $achieved_points totalPoints]
- }
- #
- # When "achievedPoints" is set to empty, and "details" are
- # provided, we perform a new calculation based on "details".
- #
- if {[dict get $achieved_points achievedPoints] eq ""
- && [dict exists $achieved_points details]
- } {
- set achievablePoints 0
- set achievedPoints 0
- #ns_log notice "RECALC in complete_dict "
- foreach detail [dict get $achieved_points details] {
- #ns_log notice "RECALC in complete_dict '$detail'"
- set achievedPoints [expr {$achievedPoints + [dict get $detail achieved]}]
- set achievablePoints [expr {$achievablePoints + [dict get $detail achievable]}]
- }
- dict set achieved_points achievedPoints $achievedPoints
- dict set achieved_points achievablePoints $achievablePoints
- }
-
- foreach key {
- achievedPoints
- achievablePoints
- totalPoints
- } {
- if {![dict exists $achieved_points $key]} {
- ns_log warning "test_item::grading dict without $key: $achieved_points"
- ::xo::show_stack
- dict set achieved_points $key 0
- }
- }
- dict with achieved_points {
- dict set achieved_points achievedPointsRounded [format %.0f $achievedPoints]
- set achievablePoints [format %.2f $achievablePoints]
- set achievedPoints [format %.2f $achievedPoints]
- set percentage [format %.2f [expr {$totalPoints > 0 ? ($achievedPoints*100.0/$totalPoints) : 0}]]
- dict set achieved_points percentage $percentage
- dict set achieved_points percentageRounded [format %.0f $percentage]
- }
- #ns_log notice "R=$achieved_points"
- return $achieved_points
- }
-
- }
-
- Grading create ::xowf::test_item::grading::wi1 -percentage_boundaries {50.0 60.0 70.0 80.0} {
-
- :public object method print {-achieved_points:required} {
- set achieved_points [:complete_dict $achieved_points]
- set grade [:grade -achieved_points $achieved_points]
- dict with achieved_points {
- set panelHTML [_ xowf.panel_achieved_points_wi1]
- return [list panel $panelHTML csv [subst {$achievedPoints\t$achievedPointsRounded\t$percentage%\t$grade}]]
- }
- }
- :public object method grade {-achieved_points:required} {
- set achieved_points [:complete_dict $achieved_points]
- if {[dict exists $achieved_points achievedPoints]} {
- dict with achieved_points {
- return [:calc_grade -points $achievedPointsRounded -achieved_points $achieved_points]
- }
- }
- }
- }
-
- Grading create ::xowf::test_item::grading::wi1p -percentage_boundaries {50.0 60.0 70.0 80.0} {
-
- :public object method print {-achieved_points:required} {
- set achieved_points [:complete_dict $achieved_points]
- set grade [:grade -achieved_points $achieved_points]
- dict with achieved_points {
- set panelHTML [_ xowf.panel_achieved_points_wi1p]
- return [list panel $panelHTML csv [subst {$achievedPoints\t$percentage%\t$percentageRounded%\t$grade}]]
- }
- }
- :public object method grade {-achieved_points:required} {
- set achieved_points [:complete_dict $achieved_points]
- if {[dict exists $achieved_points achievedPoints]} {
- dict with achieved_points {
- return [:calc_grade -points $achievedPoints -percentage $percentageRounded -achieved_points $achieved_points]
- }
- }
- }
- }
-
-
- Grading create ::xowf::test_item::grading::wi1_noround -percentage_boundaries {50.0 60.0 70.0 80.0} {
-
- :public object method print {-achieved_points:required} {
- if {[dict exists $achieved_points achievedPoints]} {
- set achieved_points [:complete_dict $achieved_points]
- set grade [:grade -achieved_points $achieved_points]
- dict with achieved_points {
- set panelHTML [_ xowf.panel_achieved_points_wi1_noround]
- return [list panel $panelHTML csv [subst {$achievedPoints\t$percentage%\t$grade}]]
- }
- }
- }
- :public object method grade {-achieved_points:required} {
- if {[dict exists $achieved_points achievedPoints]} {
- set achieved_points [:complete_dict $achieved_points]
- dict with achieved_points {
- return [:calc_grade -points $achievedPoints -achieved_points $achieved_points]
- }
- }
- }
- }
-
-}
-
namespace eval ::xowiki {
::xowiki::MenuBar instproc config=test-items {
{-bind_vars {}}
@@ -6765,9 +6650,12 @@
{entry -name New.Item.CompositeInteraction -form en:edit-interaction.wf -query p.item_type=Composite}
{entry -name New.Item.PoolQuestionInteraction -form en:edit-interaction.wf -query p.item_type=PoolQuestion}
+ {entry -name New.Grading.Scheme -form en:edit-grading-scheme.wf}
+
{entry -name New.App.OnlineExam -form en:online-exam.wf -disabled true}
{entry -name New.App.InclassQuiz -form en:inclass-quiz.wf -disabled true}
{entry -name New.App.InclassExam -form en:inclass-exam.wf -query p.realexam=1}
+
}
}
Index: openacs-4/packages/xowf/tcl/xowf-form-field-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/xowf-form-field-procs.tcl,v
diff -u -N -r1.8.2.4 -r1.8.2.5
--- openacs-4/packages/xowf/tcl/xowf-form-field-procs.tcl 21 Dec 2021 08:12:34 -0000 1.8.2.4
+++ openacs-4/packages/xowf/tcl/xowf-form-field-procs.tcl 3 Jan 2022 16:00:36 -0000 1.8.2.5
@@ -428,6 +428,75 @@
set :__initialized 1
}
+ ###########################################################
+ #
+ # ::xowiki::formfield::grading_scheme
+ #
+ ###########################################################
+
+ Class create grading_scheme -superclass select -parameter {
+ }
+
+ grading_scheme instproc initialize {} {
+ if {${:__state} ne "after_specs"} return
+
+ set t1 [clock clicks -milliseconds]
+ ::xowf::test_item::grading::load_grading_schemes \
+ -package_id [${:object} package_id] \
+ -parent_id [${:object} parent_id]
+
+ set :options [lsort [lmap gso [::xowf::test_item::grading::Grading info instances -closure] {
+ set grading [namespace tail $gso]
+ list [$gso cget -title] $grading
+ }]]
+ ns_log notice "#### available grading_scheme_objs (took [expr {[clock clicks -milliseconds]-$t1}]ms)\n[join [lsort ${:options}] \n]"
+ next
+
+ set :__initialized 1
+ }
+
+ ###########################################################
+ #
+ # ::xowiki::formfield::grade_boundary
+ #
+ ###########################################################
+ Class create grade_boundary -superclass number -parameter {
+ }
+ grade_boundary instproc render_input {} {
+ #
+ # The definition of this validator assumes 4 grade boundaries with
+ # exactly these naming conventions. The corresponding form is
+ # defined in edit-grading-scheme.wf.
+ #
+ next
+ template::add_event_listener -event input -id ${:id} -script {
+ const inputField = event.target;
+ const form = inputField.parentNode.parentNode;
+ //console.log('check descending values');
+ const grade1 = form.elements["grade1"];
+ const grade2 = form.elements["grade2"];
+ const grade3 = form.elements["grade3"];
+ const grade4 = form.elements["grade4"];
+ if (grade1.value < grade2.value) {
+ console.log('error grade 1');
+ grade2.setCustomValidity('percentage for grade 1 must by larger than grade 2');
+ } else {
+ grade2.setCustomValidity('');
+ }
+ if (grade2.value < grade3.value) {
+ console.log('error grade 2');
+ grade3.setCustomValidity('percentage for grade 2 must by larger than grade 3');
+ } else {
+ grade3.setCustomValidity('');
+ }
+ if (grade3.value < grade4.value) {
+ console.log('error grade 3');
+ grade4.setCustomValidity('percentage for grade 3 must by larger than grade 4');
+ } else {
+ grade4.setCustomValidity('');
+ }
+ }
+ }
}
::xo::library source_dependent
Index: openacs-4/packages/xowf/www/resources/test-item.css
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/www/resources/Attic/test-item.css,v
diff -u -N -r1.1.2.39 -r1.1.2.40
--- openacs-4/packages/xowf/www/resources/test-item.css 3 Dec 2021 12:17:47 -0000 1.1.2.39
+++ openacs-4/packages/xowf/www/resources/test-item.css 3 Jan 2022 16:00:36 -0000 1.1.2.40
@@ -347,7 +347,8 @@
div.grading-box a.manual-grade {
color: #dd1e1e;
}
-div.grading-box span.comment,
+div.grading-box span.comment,
+div.grading-box span.percentage,
div.grading-box span.points {
color: #888888;
}
@@ -358,3 +359,26 @@
div.grading-box span.text-warn {
color: #ed920ac9;
}
+
+/*
+ * Editing grading schemes
+ */
+form.Form-edit-grading-scheme input.form-control.inline {
+ display: inline;
+ width: inherit;
+}
+form.Form-edit-grading-scheme div.form-group div input[type="number"].form-control {
+ text-align: right;
+ width: 5em;
+ direction: rtl;
+}
+form.Form-edit-grading-scheme div input[type="number"].form-control.inline {
+ text-align: right;
+ width: 6em;
+ direction: rtl;
+}
+
+/*
+form.Form-edit-grading-scheme input::-webkit-outer-spin-button,
+form.Form-edit-grading-scheme input::-webkit-inner-spin-button { margin-left: 2px; }
+*/