Skip to content

Ptx-based template for student pages #1280

Merged
bnmnetp merged 14 commits into
RunestoneInteractive:mainfrom
ascholerChemeketa:ptx-template-student-assign-pages
Jul 7, 2026
Merged

Ptx-based template for student pages #1280
bnmnetp merged 14 commits into
RunestoneInteractive:mainfrom
ascholerChemeketa:ptx-template-student-assign-pages

Conversation

@ascholerChemeketa

Copy link
Copy Markdown
Contributor

Depends on PTX PR: PreTeXtBook/pretext#2978
It is merged but may not be in CLI yet. But, this PR is backwards safe - old books should continue to function just fine. Any book that lacks a _base.html file will fall back to the normal RS _base.html.

This is a good checkpoint on this project. I have not tackled peer pages, but the three main ones (course home, doAssignment, chooseAssignment) are set up.

Updated HTML is used for these always - there are some accessibility and structure updates that are good no matter what. Some PTX styles (section headings) affect pages regardless if full template is in use.

Right now it is opt in via course setting to use the PTX based template that is built for a course. I imagine we leave at as opt in until you and others get a chance to play. Then we make it opt out.


This pull request introduces several improvements and refactorings across the instructor, student, and book server APIs, focusing on enhancing template selection based on course settings, improving path handling, and unifying URL construction. The changes also bring more consistency to the handling of course and assignment attributes in context objects passed to templates, and clean up some legacy or duplicated code.

Template and Path Handling Improvements:

  • Student and assignment views now dynamically select the Jinja template directory based on the use_pretext_student_pages course attribute, allowing for PreTeXt-based student pages when enabled. The safe_join helper is used for secure path construction. ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R107-R111), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R174-R212), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R926-R980))
  • The custom safe_join implementation is removed from book_server_api/routers/books.py, and the shared helper from rsptx.response_helpers is used instead, reducing code duplication and improving maintainability. ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8R61), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8L526-L546))

Context and Attribute Consistency:

  • Context dictionaries passed to templates in student assignment views are refactored to consistently include course attributes, user roles, and various settings, improving template rendering and reducing special-case handling. ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R174-R212), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L738-R799), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R926-R980))
  • Added logic to strip numeric prefixes from chapter and subchapter names, preventing duplicate or confusing chapter labels in the UI. ([bases/rsptx/assignment_server_api/routers/student.pyL738-R799](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L738-R799))

URL Construction and Static Asset Handling:

  • All uses of the legacy get_course_url function are replaced with the new construct_course_url helper, ensuring consistent URL generation for static assets and external resources. ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44R87), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44L119-L120), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44L187-R188), [[4]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L691-R711))

Instructor and Assignment Copying Updates:

  • The assignment copying logic in the instructor API is updated to unpack additional chapter and subchapter data from the question data, preparing for more robust assignment duplication. ([bases/rsptx/admin_server_api/routers/instructor.pyL1154-R1157](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-94fac4ad0473ba1f2c291e699c8e8f45914f7394132cb664da4fcdda3f6f2d20L1154-R1157))
  • The course settings API now exposes the use_pretext_student_pages attribute, allowing the admin UI to reflect and control this feature. ([bases/rsptx/admin_server_api/routers/instructor.pyR426-R428](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-94fac4ad0473ba1f2c291e699c8e8f45914f7394132cb664da4fcdda3f6f2d20R426-R428))

General Refactoring and Cleanup:

  • Unused imports and legacy code (such as the old get_course_url and custom safe_join) are removed for clarity and maintainability. ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8L29), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L566-L579))

These changes set the stage for improved support of PreTeXt-based course content, more secure and consistent path handling, and a more maintainable codebase.


References:

  • Template and Path Handling: ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R107-R111), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R174-R212), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R926-R980), [[4]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8R61), [[5]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8L526-L546))
  • Context and Attribute Consistency: ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R174-R212), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L738-R799), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072R926-R980))
  • URL Construction: ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44R87), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44L119-L120), [[3]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dd60b6ed774f13b19ba90f1d48fa6b7197872eadd20abe4376603c86c782aa44L187-R188), [[4]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L691-R711))
  • Instructor/Assignment Copying: ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-94fac4ad0473ba1f2c291e699c8e8f45914f7394132cb664da4fcdda3f6f2d20L1154-R1157), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-94fac4ad0473ba1f2c291e699c8e8f45914f7394132cb664da4fcdda3f6f2d20R426-R428))
  • Refactoring/Cleanup: ([[1]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-dfad0b60c2f19f7a29315ea3c1a4440a2d4e18e3f75a6a2072032c99192b39e8L29), [[2]](https://github.com/RunestoneInteractive/rs/pull/1280/files#diff-43e4ec0d538ce2326f320b85c5feba3b8bb005d8b803f44146c02f45dab57072L566-L579))

Copilot AI review requested due to automatic review settings July 3, 2026 20:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in PreTeXt (PTX)-based templating path for key student pages (course home, choose assignment, do assignment) and refactors shared utilities so student/instructor/book servers can consistently and securely build paths and published-book URLs.

Changes:

  • Add PTX-based template selection (via use_pretext_student_pages) with book-specific template override support and updated HTML/CSS for student pages.
  • Introduce shared helpers (construct_course_url, shared safe_join) and remove duplicated implementations/usages.
  • Refactor assignment/course template contexts and UI structure (including accessibility-oriented tweaks like screen-reader-only text and table captioning).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
components/rsptx/templates/staticAssets/main.css Adds shared utility styles and adjusts heading margin overrides for non-PTX templates.
components/rsptx/templates/staticAssets/main-ptx-based.css Introduces PTX-template-specific CSS variables and layout/style resets.
components/rsptx/templates/staticAssets/index.css Removes assignment-related styles (moved into assignment-specific stylesheet).
components/rsptx/templates/staticAssets/course.css Updates course page layout styles and scopes non-PTX styling.
components/rsptx/templates/staticAssets/assignment/assignment.css New consolidated CSS for choose/do assignment pages with PTX vs non-PTX scoping.
components/rsptx/templates/staticAssets/assignment/assignment_block.css New PTX-scoped styling for assignment tables on course/chooser pages.
components/rsptx/templates/core.py Adds get_jinja_templates with a ChoiceLoader for book override + shared templates.
components/rsptx/templates/common/static_assets.html Makes static asset inclusion conditional for PTX-based templates to avoid duplication.
components/rsptx/templates/common/static_assets_min.html Removes PTX add-on CSS include from the “min” asset bundle.
components/rsptx/templates/book/course/current_course.html Restructures course home page markup and hooks in new CSS/assets.
components/rsptx/templates/author/editlibrary.html Moves with_errors macro usage locally into this template.
components/rsptx/templates/assignment/student/doAssignment.html Refactors do-assignment page layout, readings display, and question UI.
components/rsptx/templates/assignment/student/chooseAssignment.html Refactors chooser page markup and switches to assignment-specific CSS.
components/rsptx/templates/assignment/student/assignment_block.html Updates assignment table markup to improve semantics/accessibility and styling hooks.
components/rsptx/templates/admin/instructor/course_settings.html Exposes the use_pretext_student_pages toggle in instructor settings UI.
components/rsptx/templates/_base.html Removes global with_errors macro from base template.
components/rsptx/templates/init.py Exports get_jinja_templates from the templates package.
components/rsptx/response_helpers/core.py Adds construct_course_url and centralizes safe_join implementation.
components/rsptx/response_helpers/init.py Re-exports construct_course_url and safe_join.
components/rsptx/db/crud/question.py Expands fetch_assignment_questions query to include Chapter/SubChapter metadata.
bases/rsptx/book_server_api/routers/course.py Selects template loader based on course attribute and updates template context fields.
bases/rsptx/book_server_api/routers/books.py Removes local safe_join and uses shared helper.
bases/rsptx/assignment_server_api/routers/student.py Adds PTX template selection and refactors template context for chooser/doAssignment.
bases/rsptx/assignment_server_api/routers/instructor.py Switches to construct_course_url for consistent asset URL rewriting.
bases/rsptx/admin_server_api/routers/instructor.py Exposes use_pretext_student_pages in settings payload and updates assignment copying unpacking.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread components/rsptx/templates/core.py Outdated
Comment thread bases/rsptx/book_server_api/routers/course.py
Comment thread bases/rsptx/book_server_api/routers/course.py Outdated
Comment thread bases/rsptx/assignment_server_api/routers/student.py
Comment thread bases/rsptx/assignment_server_api/routers/student.py
Comment thread components/rsptx/templates/book/course/current_course.html
Comment thread components/rsptx/templates/book/course/current_course.html
Comment thread components/rsptx/templates/assignment/student/assignment_block.html Outdated
Comment thread components/rsptx/templates/assignment/student/doAssignment.html Outdated
Comment thread components/rsptx/templates/staticAssets/assignment/assignment.css Outdated
@ascholerChemeketa

Copy link
Copy Markdown
Contributor Author

I'll check these out and make updates.

@bnmnetp

bnmnetp commented Jul 6, 2026

Copy link
Copy Markdown
Member

The merge conflicts are easy to resolve, I had updated the links on the course home page for instructors so they were all pointing at the new servers. And I had removed the link to the practice tool.

@ascholerChemeketa ascholerChemeketa force-pushed the ptx-template-student-assign-pages branch from 91bf115 to 9c9de28 Compare July 6, 2026 22:29
Copilot AI review requested due to automatic review settings July 6, 2026 22:34
@ascholerChemeketa ascholerChemeketa force-pushed the ptx-template-student-assign-pages branch from 9c9de28 to 39045a9 Compare July 6, 2026 22:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 5 comments.

Comment on lines 183 to 187
"student_page": True,
"course_list": course_list,
"is_instructor": user_is_instructor,
"has_discussion_group": any([book.social_url for book in books]),
"is_instructor": "true" if user_is_instructor else "false",
"has_discussion_group": book.social_url,
"lti1p1": is_lti1p1_course,
Comment on lines 1 to 5
{% if is_instructor %}
<label style="margin-bottom:10px; display:block;">
<input type="checkbox" id="hide_hidden_assignments" onchange="toggleHiddenAssignments(this.checked)">
Student View: Hide Hidden Assignments
<span for="hide_hidden_assignments"><strong>Student View</strong> (Hide Hidden Assignments)</span>
</label>
Comment on lines +11 to 22
/* Everything in this construct should only be applied when not
using the pretext template.
:where forces the selector to have zero specificity, so it doesn't
make nested selectors more specific than they need to be
*/
:where(body:not(.ptx-runestone-template)) {
.course_page {
max-width: 90%;
margin-left: auto;
margin-right: auto;
}
}
Comment on lines +85 to +89
:where(body:not(.ptx-runestone-template)) {
.assignment-title {
text-align: center;
}

Comment on lines +6 to +10
/* This should only be applied when using the pretext template.
:where forces low specificity */
:where(body:is(.ptx-runestone-template)) {
.rs-assignment-table {
width: 100%;
@ascholerChemeketa ascholerChemeketa force-pushed the ptx-template-student-assign-pages branch from 39045a9 to 1d495cd Compare July 6, 2026 22:41
@ascholerChemeketa

Copy link
Copy Markdown
Contributor Author

Rebased to current main/HEAD with revisions to address all of CoPilot's issues.

@bnmnetp

bnmnetp commented Jul 6, 2026

Copy link
Copy Markdown
Member

Do you want to run black on components/rsptx to clean up the lint errors? I think there are 4 files.

@ascholerChemeketa ascholerChemeketa force-pushed the ptx-template-student-assign-pages branch from 1d495cd to b790e99 Compare July 7, 2026 14:45
Copilot AI review requested due to automatic review settings July 7, 2026 14:45
@ascholerChemeketa

Copy link
Copy Markdown
Contributor Author

Sorry, looks like Black was busted in my VSCode.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 8 comments.

Comment on lines +185 to +186
"is_instructor": "true" if user_is_instructor else "false",
"has_discussion_group": book.social_url,
Comment on lines 1 to 5
{% if is_instructor %}
<label style="margin-bottom:10px; display:block;">
<input type="checkbox" id="hide_hidden_assignments" onchange="toggleHiddenAssignments(this.checked)">
Student View: Hide Hidden Assignments
<span for="hide_hidden_assignments"><strong>Student View</strong> (Hide Hidden Assignments)</span>
</label>
Comment on lines +203 to +205
is_logged_in="true",
is_instructor="true" if user_is_instructor else "false",
student_page=True,
Comment on lines +983 to +986
is_logged_in="true",
is_instructor="true" if user_is_instructor else "false",
needs_manual_grading=needs_manual_grading,
student_page="true",
Comment on lines +81 to +85
<li class="{{ readings_full[reading]['status']}}"><strong> Chapter {{ readings_full[reading]['chapter_num'] }} {{ readings_full[reading]['chapter_name'] }}:</strong></li>
{% else: %}
<li class="{{ readings_full[reading]['status']}}"><a href="/ns/books/published/{{course.course_name}}/{{readings_full[reading]['chapter_label']}}.html">Chapter {{ readings_full[reading]['chapter_num'] }} {{ readings_full[reading]['chapter_name'] }}:</a></li>
{% endif %}
<ul class="assignment-readings-subchapters">
<h2 class="heading">Assignments</h2>
{% if lti1p1 %}
<h3>You must launch assignments through your LMS for this course</h3>
<p class="heading">You must launch assignments through your LMS for this course</p>
Comment on lines +16 to 22
:where(body:not(.ptx-runestone-template)) {
.course_page {
max-width: 90%;
margin-left: auto;
margin-right: auto;
}
}
Comment on lines +85 to +107
:where(body:not(.ptx-runestone-template)) {
.assignment-title {
text-align: center;
}

/*id for specificity in title*/
#assignment_name.assignment-title,
#questions,
.assignment-banner,
.assignment-page,
.assignment-meta,
.assignment-readings {
max-width: 90%;
margin-left: auto;
margin-right: auto;
}

#questions {
.ac_section > * {
max-width: 100%;
}
}

@ascholerChemeketa ascholerChemeketa force-pushed the ptx-template-student-assign-pages branch from b790e99 to 6f6f8e6 Compare July 7, 2026 15:42
@ascholerChemeketa

Copy link
Copy Markdown
Contributor Author

Fixed two real issues (one new, one missed). The is_instructor type is inherently messy but hopefully can go away with eBookConfig refactor

Copilot AI review requested due to automatic review settings July 7, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 11 comments.

Comment on lines 183 to 187
"student_page": True,
"course_list": course_list,
"is_instructor": user_is_instructor,
"has_discussion_group": any([book.social_url for book in books]),
"is_instructor": "true" if user_is_instructor else "false",
"has_discussion_group": book.social_url,
"lti1p1": is_lti1p1_course,
Comment on lines +358 to +362
select(Question, AssignmentQuestion, Chapter, SubChapter)
.join(Question, AssignmentQuestion.question_id == Question.id)
.join(
Chapter,
(Question.chapter == Chapter.chapter_label)
Comment on lines +21 to +28
<!-- ptx-based templates include these already -->
{% if using_ptx_base != "true" %}
{% for css_import in wp_imports["css"] %}
<link rel="stylesheet" type="text/css" href="/ns/books/published/{{course.course_name}}/_static/{{css_import}}" />
{% endfor %}
{% for js_import in wp_imports["js"] %}
<script src='/ns/books/published/{{course.course_name}}/_static/{{js_import}}'></script>
{% endfor %}
Comment on lines +181 to +184
</section>
{% endif %}
</div>

{% if not released and not assignment.is_timed %}
<div style="text-align:center">
<button class="btn btn-lg btn-warning buttonAskCompletion" id="gradeMeButton">Compute Score for Assignment</button>
<p><strong>Warning:</strong> Scores for problems that you self grade are unofficial.
Some problems will need to be manually graded, and your instructor may have requirements that cannot be autograded.
No deadlines are enforced when self grading, but your instructor may penalize you
for late work.</p>
</div>

{% endif %}
</div>
</div>
</section>
Comment on lines 2 to 5
<label style="margin-bottom:10px; display:block;">
<input type="checkbox" id="hide_hidden_assignments" onchange="toggleHiddenAssignments(this.checked)">
Student View: Hide Hidden Assignments
<span for="hide_hidden_assignments"><strong>Student View</strong> (Hide Hidden Assignments)</span>
</label>
Comment on lines +66 to +81
:where(body:not(.ptx-runestone-template)) {
.assignment-title {
text-align: center;
}

/*id for specificity in title*/
#assignment_name.assignment-title,
#questions,
.assignment-banner,
.assignment-page,
.assignment-meta,
.assignment-readings {
max-width: 90%;
margin-left: auto;
margin-right: auto;
}
Comment on lines +83 to +88
#questions {
.ac_section > * {
max-width: 100%;
}
}

Comment on lines +89 to +102
.questionblock {
background-color: #ddd;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 10px;
}

.metaquestion {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
}
Comment on lines +8 to +25
:where(body:is(.ptx-runestone-template)) {
.rs-assignment-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}

.rs-assignment-table thead {
border-bottom: 2px solid var(--rs-table-border, #888);
}

.rs-assignment-table th {
border-bottom: 2px solid var(--rs-table-border, #888);
padding-top: 0.5rem;
padding-bottom: 0.5rem;
text-align: start;
font-size: 100%;
}
Comment on lines +27 to +40
.rs-assignment-table td {
border-bottom: 1px solid var(--rs-table-border, #888);
padding-top: 0.5rem;
padding-bottom: 0.5rem;
font-size: 100%;
}

.rs-assignment-table .ti {
font-size: 125%;
}
.rs-assignment-table a {
border: 0;
}
}
@bnmnetp bnmnetp merged commit a6cf149 into RunestoneInteractive:main Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants