Skip to content

Commit fa87048

Browse files
committed
Squashed commit of the following:
commit 393831d Author: Jason Altekruse <[email protected]> Date: Wed Jan 10 13:03:50 2024 -0600 Cleanup, would like to turn some of the logging changes in a separate PR commit 2c2baf1 Author: Jason Altekruse <[email protected]> Date: Wed Jan 10 11:45:30 2024 -0600 Squashed commit of the following: commit 429bd06 Author: Jason Altekruse <[email protected]> Date: Tue Jan 9 15:37:12 2024 -0600 Remove big files - being pulled in from temporary CDN during review, will decide on final location during discussion in PR review commit 30a72ed Author: Jason Altekruse <[email protected]> Date: Sun Jan 7 21:37:01 2024 -0600 point to GH pages temporarily until we get a real release of DoenetML up on NPM commit c7091d0 Author: Jason Altekruse <[email protected]> Date: Mon Dec 4 14:49:41 2023 -0600 Give a valid response if no student page state is stored yet commit d33ed6d Author: Jason Altekruse <[email protected]> Date: Mon Nov 27 23:17:30 2023 -0600 recalling state works! commit 3b35d6b Author: Jason Altekruse <[email protected]> Date: Mon Nov 27 22:19:10 2023 -0600 TODO - come back to this state and figure out why a 500 server error ends up with a useless payload of {"detail":{}} WIP - trying to get state loading from the server commit a920052 Merge: 2eabe19 7d76814 Author: Jason Altekruse <[email protected]> Date: Wed Nov 22 11:21:28 2023 -0600 Merge remote-tracking branch 'origin/main' into doenet-question-type Conflicts: bases/rsptx/web2py_server/applications/runestone/static/js/admin.js poetry.lock projects/interactives/poetry.lock projects/interactives/pyproject.toml projects/w2p_login_assign_grade/poetry.lock projects/w2p_login_assign_grade/pyproject.toml pyproject.toml commit 2eabe19 Author: Jason Altekruse <[email protected]> Date: Wed Nov 22 11:12:43 2023 -0600 Debugging inconsistent experience having grades calculated as a student - last time I tried hunting this down the dockerized version was behaving different than the dstarted version of the server commit 9ae91b1 Author: Jason Altekruse <[email protected]> Date: Wed Nov 22 11:11:54 2023 -0600 WIP - some pointers to where file uploads are currently done by students, should be able to be repurposed to allow image uploads for doenet content commit 4e159ff Author: Jason Altekruse <[email protected]> Date: Wed Nov 22 11:04:57 2023 -0600 Integrate the 2 panel doenet editor commit 3a91363 Author: Jason Altekruse <[email protected]> Date: Tue Nov 7 12:51:17 2023 -0600 Auto-grading for Doenet questions TODO - remove the copy-paste in this file Discuss possibly going as far as consolidating all of the answer tables into 1, I believe that would remove the need for several different mappings and "specialized" copy/pasted functions for each question type that I have needed to find commit c86b5bc Author: Jason Altekruse <[email protected]> Date: Mon Nov 6 08:56:09 2023 -0600 WIP - trying to get grades saving and showing up in the gradebook for doenet problems commit e4d9155 Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 22:07:44 2023 -0500 add useful info to logging of errors commit ef97f0d Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 22:06:57 2023 -0500 poetry locks commit 0d5771f Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 22:06:25 2023 -0500 WIP - adding doenet to the dropdown, and getting it to show up in the table after being added to an assignment commit c14b3f6 Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 22:04:08 2023 -0500 fixing other question types hopefully with codeChat downgrade commit f1c29dd Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 18:01:04 2023 -0500 making a request to backend! Next I need to figure out how I get access to the minimal necessary data for the request, mostly the course name from schemas.py # Schemas # ======= class LogItemIncoming(BaseModelNone): """ This class defines the schema for what we can expect to get from a logging event. Because we are using pydantic type verification happens automatically, if we want to add additional constraints we can do so. """ event: str act: str div_id: str course_name: str commit ba60d59 Author: Jason Altekruse <[email protected]> Date: Tue Oct 24 16:37:24 2023 -0500 I think this is the minimal set of changes to get Doenet rendering - making one change over in DoenetML right now to allow passing more config options (redoing change from before I started from the tip of DoenetML recently) commit 0c76290 Author: Jason Altekruse <[email protected]> Date: Thu Oct 19 18:43:07 2023 -0500 I am confused, but now it lets me update the JS with just building the NPM project and re-building the book? commit 789824a Author: Jason Altekruse <[email protected]> Date: Thu Oct 19 18:42:36 2023 -0500 Getting doenet into the standard initialization path for runestone components - TODO - refactor doenet-embed to produce an artifact that is importable here, may have a good format with a module and instead need to update the config on this side, this was the error I got // TODO fix this, in the meantime including from sphinx_static_files.html // ERROR in ./runestone/doenet/js/doenet-standalone.js 240673:19 //Module parse failed: Unterminated template (240673:19) //You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders commit 142aaf1 Author: Jason Altekruse <[email protected]> Date: Mon Oct 16 17:15:24 2023 -0500 lock files, I still can't get it pulling in the old version of codeChat commit d91f084 Author: Jason Altekruse <[email protected]> Date: Mon Oct 16 17:14:59 2023 -0500 WIP - got doenet showing in the assignment editor
1 parent 7d76814 commit fa87048

File tree

19 files changed

+565
-13
lines changed

19 files changed

+565
-13
lines changed

bases/rsptx/book_server_api/routers/assessment.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
# -------------------
2424
from bleach import clean
2525
from fastapi import APIRouter, Depends, HTTPException, Request, status
26+
from fastapi.responses import JSONResponse
27+
from fastapi.encoders import jsonable_encoder
2628
from pydantic import BaseModel
2729

2830
# Local application imports
@@ -67,6 +69,66 @@
6769
tags=["assessment"],
6870
)
6971

72+
@router.get("/getDoenetState")
73+
async def getdoenetstate(request: Request, div_id: str,
74+
course_name: str, event: str,
75+
# sid: Optional[str],
76+
user=Depends(auth_manager)
77+
):
78+
request_data = AssessmentRequest(course=course_name, div_id=div_id, event=event)
79+
# if the user is not logged in an HTTP 401 will be returned.
80+
# Otherwise if the user is an instructor then use the provided
81+
# sid (it could be any student in the class). If none is provided then
82+
# use the user objects username
83+
sid = user.username
84+
if await is_instructor(request):
85+
if request_data.sid:
86+
sid = request_data.sid
87+
else:
88+
if request_data.sid:
89+
# someone is attempting to spoof the api
90+
return make_json_response(
91+
status=status.HTTP_401_UNAUTHORIZED, detail="not an instructor"
92+
)
93+
request_data.sid = sid
94+
95+
96+
row = await fetch_last_answer_table_entry(request_data)
97+
# mypy complains that ``row.id`` doesn't exist (true, but the return type wasn't exact and this does exist).
98+
if not row or row.id is None: # type: ignore
99+
return JSONResponse(
100+
status_code=200, content=jsonable_encoder({"loadedState": False, "success": True})
101+
)
102+
ret = row.dict()
103+
rslogger.debug(f"row is {ret}")
104+
if "timestamp" in ret:
105+
ret["timestamp"] = (
106+
ret["timestamp"].replace(tzinfo=datetime.timezone.utc).isoformat()
107+
)
108+
rslogger.debug(f"timestamp is {ret['timestamp']}")
109+
110+
# Do server-side grading if needed, which restores the answer and feedback.
111+
if feedback := await is_server_feedback(request_data.div_id, request_data.course):
112+
rcd = runestone_component_dict[EVENT2TABLE[request_data.event]]
113+
# The grader should also be defined if there's feedback.
114+
assert rcd.grader
115+
# Use the grader to add server-side feedback to the returned dict.
116+
ret.update(await rcd.grader(row, feedback))
117+
118+
# get grade and instructor feedback if Any
119+
grades = await fetch_question_grade(sid, request_data.course, request_data.div_id)
120+
if grades:
121+
ret["comment"] = grades.comment
122+
ret["score"] = grades.score
123+
124+
real_ret = ret["answer"]["state"]
125+
real_ret["success"] = True
126+
real_ret["loadedState"] = True
127+
rslogger.debug(f"Returning {ret}")
128+
# return make_json_response(detail=ret)
129+
return JSONResponse(
130+
status_code=200, content=jsonable_encoder(real_ret)
131+
)
70132

71133
# getAssessResults
72134
# ----------------

bases/rsptx/book_server_api/routers/rslogging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ async def log_book_event(
149149
if entry.act in ["start", "pause", "resume"]:
150150
# We don't need these in the answer table but want the event to be timedExam.
151151
create_answer_table = False
152-
elif entry.event == "webwork" or entry.event == "hparsonsAnswer":
152+
elif entry.event == "webwork" or entry.event == "hparsonsAnswer" or entry.event == "doenet":
153153
entry.answer = json.loads(useinfo_dict["answer"])
154154

155155
if create_answer_table:

bases/rsptx/interactives/runestone/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .datafile import DataFile
1212
from .disqus import DisqusDirective
1313
from .dragndrop import DragNDrop
14+
from .doenet import DoenetDirective
1415
from .fitb import FillInTheBlank
1516
from .groupsub import GroupSubmission
1617
from .hparsons import HParsonsDirective
@@ -40,6 +41,7 @@
4041

4142

4243
# TODO: clean up - many of the folders are not needed as the files are imported by webpack
44+
# TODO - Jason second's this TODO, I've been confused by duplicates copies of static assets
4345
#
4446
# runestone_static_dirs()
4547
# -----------------------
@@ -251,6 +253,7 @@ def build(options):
251253
"datafile": DataFile,
252254
"disqus": DisqusDirective,
253255
"dragndrop": DragNDrop,
256+
"doenet": DoenetDirective,
254257
"groupsub": GroupSubmission,
255258
"hparsons": HParsonsDirective,
256259
"parsonsprob": ParsonsProblem,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .doenet import *
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
2+
# *********
3+
# |docname|
4+
# *********
5+
# Copyright (C) 2011 Bradley N. Miller
6+
#
7+
# This program is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
#
20+
__author__ = "jaltekruse"
21+
22+
from docutils import nodes
23+
from docutils.parsers.rst import directives
24+
from sqlalchemy import Table
25+
from runestone.server.componentdb import (
26+
addQuestionToDB,
27+
addHTMLToDB,
28+
maybeAddToAssignment,
29+
)
30+
from runestone.common.runestonedirective import (
31+
RunestoneIdDirective,
32+
RunestoneIdNode,
33+
)
34+
35+
36+
def setup(app):
37+
app.add_directive("doenet", DoenetDirective)
38+
app.add_node(DoenetNode, html=(visit_hp_html, depart_hp_html))
39+
40+
41+
TEMPLATE_START = """
42+
<div class="runestone">
43+
<div data-component="hparsons" id=%(divid)s data-question_label="%(question_label)s" class="alert alert-warning hparsons_section">
44+
<div class="hp_question">
45+
"""
46+
47+
TEMPLATE_END = """
48+
</div>
49+
<div class='hparsons'></div>
50+
<textarea
51+
%(language)s
52+
%(optional)s
53+
%(dburl)s
54+
%(reuse)s
55+
%(randomize)s
56+
%(blockanswer)s
57+
style="visibility: hidden;">
58+
%(initialsetting)s
59+
</textarea>
60+
</div>
61+
</div>
62+
"""
63+
64+
65+
class DoenetNode(nodes.General, nodes.Element, RunestoneIdNode):
66+
pass
67+
68+
69+
# self for these functions is an instance of the writer class. For example
70+
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
71+
# The node that is passed as a parameter is an instance of our node class.
72+
def visit_hp_html(self, node):
73+
74+
node["delimiter"] = "_start__{}_".format(node["runestone_options"]["divid"])
75+
76+
self.body.append(node["delimiter"])
77+
78+
res = TEMPLATE_START % node["runestone_options"]
79+
self.body.append(res)
80+
81+
82+
def depart_hp_html(self, node):
83+
res = TEMPLATE_END % node["runestone_options"]
84+
self.body.append(res)
85+
86+
addHTMLToDB(
87+
node["runestone_options"]["divid"],
88+
node["runestone_options"]["basecourse"],
89+
"".join(self.body[self.body.index(node["delimiter"]) + 1 :]),
90+
)
91+
92+
self.body.remove(node["delimiter"])
93+
94+
95+
class DoenetDirective(RunestoneIdDirective):
96+
"""
97+
<!-- .. doenet:: doenet-1
98+
-->
99+
1+3000=<answer>4</answer>
100+
"""
101+
102+
required_arguments = 1
103+
optional_arguments = 1
104+
has_content = True
105+
option_spec = RunestoneIdDirective.option_spec.copy()
106+
option_spec.update(
107+
{
108+
"dburl": directives.unchanged,
109+
"language": directives.unchanged,
110+
"reuse": directives.flag,
111+
"randomize": directives.flag,
112+
"blockanswer": directives.unchanged,
113+
}
114+
)
115+
116+
def run(self):
117+
super(DoenetDirective, self).run()
118+
addQuestionToDB(self)
119+
120+
env = self.state.document.settings.env
121+
122+
if "language" in self.options:
123+
self.options["language"] = "data-language='{}'".format(
124+
self.options["language"]
125+
)
126+
else:
127+
self.options["language"] = ""
128+
129+
if "reuse" in self.options:
130+
self.options["reuse"] = ' data-reuse="true"'
131+
else:
132+
self.options["reuse"] = ""
133+
134+
if "randomize" in self.options:
135+
self.options["randomize"] = ' data-randomize="true"'
136+
else:
137+
self.options["randomize"] = ""
138+
139+
if "blockanswer" in self.options:
140+
self.options["blockanswer"] = "data-blockanswer='{}'".format(
141+
self.options["blockanswer"]
142+
)
143+
else:
144+
self.options["blockanswer"] = ""
145+
146+
explain_text = None
147+
if self.content:
148+
if "~~~~" in self.content:
149+
idx = self.content.index("~~~~")
150+
explain_text = self.content[:idx]
151+
self.content = self.content[idx + 1 :]
152+
source = "\n".join(self.content)
153+
else:
154+
source = "\n"
155+
156+
self.explain_text = explain_text or ["Not an Exercise"]
157+
158+
self.options["initialsetting"] = source
159+
160+
# SQL Options
161+
if "dburl" in self.options:
162+
self.options["dburl"] = "data-dburl='{}'".format(self.options["dburl"])
163+
else:
164+
self.options["dburl"] = ""
165+
166+
course_name = env.config.html_context["course_id"]
167+
divid = self.options["divid"]
168+
169+
hpnode = DoenetNode()
170+
hpnode["runestone_options"] = self.options
171+
hpnode["source"], hpnode["line"] = self.state_machine.get_source_and_line(
172+
self.lineno
173+
)
174+
self.add_name(hpnode) # make this divid available as a target for :ref:
175+
176+
maybeAddToAssignment(self)
177+
if explain_text:
178+
self.updateContent()
179+
self.state.nested_parse(explain_text, self.content_offset, hpnode)
180+
181+
return [hpnode]

0 commit comments

Comments
 (0)