diff --git a/components/rsptx/build_tools/core.py b/components/rsptx/build_tools/core.py index 74fcf753c..09d3fa30e 100644 --- a/components/rsptx/build_tools/core.py +++ b/components/rsptx/build_tools/core.py @@ -1104,10 +1104,18 @@ def _process_single_question( idchild = dynamic.attrib["id"] qtype = dynamic.attrib["data-component"] if qtype == "doenet": - # rewrite the url in the dbtext to use the course name in the path + # Rewrite the url in the dbtext to use the course name (base course) in + # the path. This src is baked in at build time and copied verbatim + # whenever the question is later linked/copied into a different course + # (e.g. via assignment import or exercise search), so it keeps pointing + # at this base course forever. mode=browsing tells serve_page to render + # it anonymously, which skips the "active course doesn't match the URL" + # redirect to the change-course page -- safe here because grading is + # driven entirely by the outer page's own session via postMessage, and + # base courses are never login_required. dbtext = re.sub( r'(``. That path is baked in once, at book +build time, using the base course the question was authored in +(``build_tools/core.py``, ``_process_single_question``). Nothing rewrites it +when the question is later linked or copied into a course whose base course +differs -- via assignment import, exercise search, or the "copy exercise" +action -- so the iframe keeps requesting the *original* base course forever. + +``book_server_api``'s ``serve_page`` redirects any logged-in user whose +active course doesn't match the URL's course segment to the "change course" +page, unless the URL's course happens to be the user's own base course. For a +Doenet iframe pointing at a foreign base course, that redirect is what a +student sees instead of the activity. + +The fix (paired with the ``build_tools/core.py`` change in this same commit) +appends ``?mode=browsing`` to the baked-in src, which makes ``serve_page`` +treat the request as anonymous/read-only and skip that mismatch check +entirely. This is safe: base courses are never ``login_required``, and Doenet +grading is driven entirely by the *outer* page's own logged-in session via +postMessage (see ``spliceWrapper.ts``) -- the inner iframe's own auth state is +never consulted. This migration backfills every already-built ``doenet`` +question so existing courses are fixed without a full book rebuild. +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '5c11e34a2611' +down_revision: Union[str, None] = 'f3b8d5c2a710' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +# Matches the exact shape build_tools/core.py bakes in: an