1515import tempfile
1616from io import open
1717import json
18+ import random
1819
1920# Third-party imports
2021# -------------------
2122from gluon import current
23+ from pytest import approx
2224from runestone .lp .lp_common_lib import (
2325 STUDENT_SOURCE_PATH ,
2426 code_here_comment ,
@@ -61,7 +63,7 @@ def is_server_feedback(div_id, course):
6163
6264# Provide feedback for a fill-in-the-blank problem. This should produce
6365# identical results to the code in ``evaluateAnswers`` in ``fitb.js``.
64- def fitb_feedback (answer_json , feedback ):
66+ def fitb_feedback (div_id , answer_json , feedback ):
6567 # Grade based on this feedback. The new format is JSON; the old is
6668 # comma-separated.
6769 try :
@@ -73,6 +75,11 @@ def fitb_feedback(answer_json, feedback):
7375 answer = answer_json .split ("," )
7476 displayFeed = []
7577 isCorrectArray = []
78+ db = current .db
79+ # For dynamic problems.
80+ seed = None
81+ locals_ = {}
82+ globals_ = {"approx" : approx }
7683 # The overall correctness of the entire problem.
7784 correct = True
7885 for blank , feedback_for_blank in zip (answer , feedback ):
@@ -85,18 +92,55 @@ def fitb_feedback(answer_json, feedback):
8592 is_first_item = True
8693 # Check everything but the last answer, which always matches.
8794 for fb in feedback_for_blank [:- 1 ]:
88- if "regex" in fb :
89- if re .search (
90- fb ["regex" ], blank , re .I if fb ["regexFlags" ] == "i" else 0
91- ):
95+ solution_code = fb .get ("solution_code" )
96+ regex = fb .get ("regex" )
97+ number = fb .get ("number" )
98+ if solution_code :
99+ # Run the dynamic code to compute solution prereqs.
100+ dynamic_code = fb .get ("dynamic_code" )
101+ if dynamic_code :
102+ # Get the seed.
103+ seed = (
104+ db (
105+ (db .fitb_answers .div_id == div_id )
106+ & (db .fitb_answers .sid == current .auth .user .username )
107+ & (
108+ db .fitb_answers .course_name
109+ == current .auth .user .course_name
110+ )
111+ )
112+ .select (
113+ db .fitb_answers .dynamic_seed ,
114+ orderby = ~ db .fitb_answers .id ,
115+ )
116+ .first ()
117+ .dynamic_seed
118+ )
119+ # Set it up.
120+ globals_ ["random" ] = random .Random (seed )
121+ exec (dynamic_code , locals_ , globals_ )
122+
123+ # Compare this solution.
124+ globals_ ["ans" ] = blank
125+ try :
126+ is_correct = eval (solution_code , locals_ , globals_ )
127+ except :
128+ is_correct = False
129+ if is_correct :
130+ isCorrectArray .append (is_first_item )
131+ if not is_first_item :
132+ correct = False
133+ displayFeed .append (fb ["feedback" ])
134+ break
135+ elif regex :
136+ if re .search (regex , blank , re .I if fb ["regexFlags" ] == "i" else 0 ):
92137 isCorrectArray .append (is_first_item )
93138 if not is_first_item :
94139 correct = False
95140 displayFeed .append (fb ["feedback" ])
96141 break
97142 else :
98- assert "number" in fb
99- min_ , max_ = fb ["number" ]
143+ min_ , max_ = number
100144 try :
101145 val = ast .literal_eval (blank )
102146 in_range = val >= min_ and val <= max_
@@ -118,7 +162,7 @@ def fitb_feedback(answer_json, feedback):
118162
119163 # Return grading results to the client for a non-test scenario.
120164 res = dict (correct = correct , displayFeed = displayFeed , isCorrectArray = isCorrectArray )
121- return "T" if correct else "F" , res
165+ return "T" if correct else "F" , seed , res
122166
123167
124168# lp feedback
0 commit comments