diff --git a/bases/rsptx/book_server_api/routers/books.py b/bases/rsptx/book_server_api/routers/books.py index 7530298dd..d43ee9fbb 100644 --- a/bases/rsptx/book_server_api/routers/books.py +++ b/bases/rsptx/book_server_api/routers/books.py @@ -402,6 +402,7 @@ async def serve_page( context = dict( request=request, course_name=course_name, + term_start_date=course_row.term_start_date.isoformat(), base_course=course_row.base_course, user_id=user.username if user else "", # _`root_path`: The server is mounted in a different location depending on how it's run (directly from gunicorn/uvicorn or under the ``/ns`` prefix using nginx). Tell the JS what prefix to use for Ajax requests. See also `setting root_path ` and the `FastAPI docs `_. This is then used in the ``eBookConfig`` of :doc:`runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html`. diff --git a/bases/rsptx/book_server_api/routers/course.py b/bases/rsptx/book_server_api/routers/course.py index 73d46fc63..2193d9554 100644 --- a/bases/rsptx/book_server_api/routers/course.py +++ b/bases/rsptx/book_server_api/routers/course.py @@ -151,6 +151,8 @@ def sort_key(assignment): "assignment_list": assignments, "stats": stats, "course": course, + "is_old_course": course.term_start_date + < (datetime.datetime.now() - datetime.timedelta(weeks=52)).date(), "user": user, "request": request, "institution": course.institution, diff --git a/bases/rsptx/interactives/package-lock.json b/bases/rsptx/interactives/package-lock.json index 3199d1280..345a71d11 100644 --- a/bases/rsptx/interactives/package-lock.json +++ b/bases/rsptx/interactives/package-lock.json @@ -18,7 +18,7 @@ "handsontable": "7.2.2", "jexcel": "^3.9.1", "jquery-ui": "1.10.4", - "micro-parsons": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.1.7/micro-parsons-0.1.7.tgz", + "micro-parsons": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.2.0/micro-parsons-0.2.0.tgz", "select2": "^4.1.0-rc.0", "sql.js": "1.5.0", "vega-embed": "3.14.0", @@ -5429,9 +5429,9 @@ } }, "node_modules/micro-parsons": { - "version": "0.1.7", - "resolved": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.1.7/micro-parsons-0.1.7.tgz", - "integrity": "sha512-oWElYGlD+HdOXm1/9Kci8rTLEdB8kdxLYqxbxni5zONGekUYyPXr1nli5OLrPeF1U1nVDvbQd1KYVIAKtTM9EA==", + "version": "0.2.0", + "resolved": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.2.0/micro-parsons-0.2.0.tgz", + "integrity": "sha512-GhCyQIs0paXD6k6/mNV9WYC2KqMnw1MwsZMSs3ufnN/izujrTDDdrrU/eSmcibkZgNEahw8Yffj8wNKnllyJHw==", "license": "MIT", "dependencies": { "highlight.js": "^11.7.0", @@ -12521,8 +12521,8 @@ "dev": true }, "micro-parsons": { - "version": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.1.7/micro-parsons-0.1.7.tgz", - "integrity": "sha512-oWElYGlD+HdOXm1/9Kci8rTLEdB8kdxLYqxbxni5zONGekUYyPXr1nli5OLrPeF1U1nVDvbQd1KYVIAKtTM9EA==", + "version": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.2.0/micro-parsons-0.2.0.tgz", + "integrity": "sha512-GhCyQIs0paXD6k6/mNV9WYC2KqMnw1MwsZMSs3ufnN/izujrTDDdrrU/eSmcibkZgNEahw8Yffj8wNKnllyJHw==", "requires": { "highlight.js": "^11.7.0", "sortablejs": "^1.14.0" diff --git a/bases/rsptx/interactives/package.json b/bases/rsptx/interactives/package.json index 4a7588082..dd3e530e9 100644 --- a/bases/rsptx/interactives/package.json +++ b/bases/rsptx/interactives/package.json @@ -43,7 +43,7 @@ "handsontable": "7.2.2", "jexcel": "^3.9.1", "jquery-ui": "1.10.4", - "micro-parsons": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.1.7/micro-parsons-0.1.7.tgz", + "micro-parsons": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.2.0/micro-parsons-0.2.0.tgz", "select2": "^4.1.0-rc.0", "sql.js": "1.5.0", "vega-embed": "3.14.0", diff --git a/bases/rsptx/interactives/runestone/clickableArea/js/clickable.js b/bases/rsptx/interactives/runestone/clickableArea/js/clickable.js index 5de896b13..a5635fbbf 100644 --- a/bases/rsptx/interactives/runestone/clickableArea/js/clickable.js +++ b/bases/rsptx/interactives/runestone/clickableArea/js/clickable.js @@ -152,17 +152,21 @@ export default class ClickableArea extends RunestoneBase { var ex = localStorage.getItem(this.localStorageKey()); if (ex !== null) { this.hasStoredAnswers = true; + let error = false; try { storageObj = JSON.parse(ex); - this.clickedIndexArray = storageObj.answer.split(";"); } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored ClickableArea data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storageObj.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); this.hasStoredAnswers = false; this.restoreAnswers({}); return; } + this.clickedIndexArray = storageObj.answer.split(";"); if (this.useRunestoneServices) { // log answer to server this.givenIndexArray = []; diff --git a/bases/rsptx/interactives/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html b/bases/rsptx/interactives/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html index 8b2c85806..b66e58559 100644 --- a/bases/rsptx/interactives/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html +++ b/bases/rsptx/interactives/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html @@ -290,6 +290,7 @@ eBookConfig.host = ''; eBookConfig.app = eBookConfig.host + '/runestone'; eBookConfig.course = '{{ course_name }}'; + eBookConfig.termStartDate = '{{ term_start_date }}'; eBookConfig.basecourse = '{{ base_course }}'; eBookConfig.isLoggedIn = {{ is_logged_in}}; eBookConfig.email = '{{ user_email }}'; @@ -309,6 +310,7 @@ eBookConfig.host = '{{course_url}}' || 'http://127.0.0.1:8000'; eBookConfig.app = eBookConfig.host+'/{{appname}}'; eBookConfig.course = '{{course_id}}'; + eBookConfig.termStartDate = '{{ term_start_date }}'; eBookConfig.basecourse = '{{ basecourse }}'; eBookConfig.isLoggedIn = false; eBookConfig.enableCompareMe = eBookConfig.useRunestoneServices; diff --git a/bases/rsptx/interactives/runestone/dragndrop/js/dragndrop.js b/bases/rsptx/interactives/runestone/dragndrop/js/dragndrop.js index 26dd62c24..8d6031080 100644 --- a/bases/rsptx/interactives/runestone/dragndrop/js/dragndrop.js +++ b/bases/rsptx/interactives/runestone/dragndrop/js/dragndrop.js @@ -700,6 +700,7 @@ export default class DragNDrop extends RunestoneBase { var ex = localStorage.getItem(this.localStorageKey()); if (ex !== null) { this.hasStoredDropzones = true; + let error = false; try { storedObj = JSON.parse(ex); this.minheight = storedObj.min_height; @@ -707,12 +708,16 @@ export default class DragNDrop extends RunestoneBase { this.dropwidth = storedObj.drop_width; } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored DragNDrop data for ${this.divid}: ${err}`); + error = true; + } + if (error || storedObj.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); this.hasStoredDropzones = false; this.finishSettingUp(); return; } + localStorage.removeItem(this.localStorageKey()); this.answerState = storedObj.answer; if (this.useRunestoneServices) { // store answer in database diff --git a/bases/rsptx/interactives/runestone/fitb/js/fitb.js b/bases/rsptx/interactives/runestone/fitb/js/fitb.js index 372c39c60..e0fd064e6 100644 --- a/bases/rsptx/interactives/runestone/fitb/js/fitb.js +++ b/bases/rsptx/interactives/runestone/fitb/js/fitb.js @@ -442,11 +442,15 @@ export default class FITB extends RunestoneBase { if (len > 0) { var ex = localStorage.getItem(this.localStorageKey()); if (ex !== null) { + let error = false; try { storedData = JSON.parse(ex); } catch (err) { // error while parsing; likely due to bad value stored in storage - console.assert(false, err.message); + console.log(`Error parsing stored FITB data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storedData.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); return; } diff --git a/bases/rsptx/interactives/runestone/hparsons/js/hparsons.js b/bases/rsptx/interactives/runestone/hparsons/js/hparsons.js index 9a751ce9c..da624747a 100644 --- a/bases/rsptx/interactives/runestone/hparsons/js/hparsons.js +++ b/bases/rsptx/interactives/runestone/hparsons/js/hparsons.js @@ -278,6 +278,11 @@ export default class HParsons extends RunestoneBase { return; } let localData = this.localData(); + // Guard against no timestamp as it was only added 3/24/2026 + if (localData.timestamp && localData.timestamp < eBookConfig.termStartDate) { + localStorage.removeItem(this.storageId); + return; + } if (localData.answerIndices && this.hparsonsInput.restoreAnswerByIndices) { this.hparsonsInput.restoreAnswerByIndices(localData.answerIndices.map(Number)); } else if (localData.answer) { @@ -299,7 +304,6 @@ export default class HParsons extends RunestoneBase { setLocalStorage(data) { let currentState = {}; if (data == undefined) { - if (this.isBlockGrading) { const answerIndices = this.hparsonsInput.getBlockIndices(); currentState = { answerIndices: answerIndices }; @@ -308,7 +312,7 @@ export default class HParsons extends RunestoneBase { const userAnswer = this.hparsonsInput.getParsonsTextArray(); currentState = { answer: userAnswer }; } - + currentState.timestamp = new Date(); } else { currentState = data; } diff --git a/bases/rsptx/interactives/runestone/lp/js/lp.js b/bases/rsptx/interactives/runestone/lp/js/lp.js index f1fb25220..e2380227f 100644 --- a/bases/rsptx/interactives/runestone/lp/js/lp.js +++ b/bases/rsptx/interactives/runestone/lp/js/lp.js @@ -253,11 +253,15 @@ class LP extends RunestoneBase { var key = this.localStorageKey(); var ex = localStorage.getItem(key); if (ex !== null) { + let error = false; try { storedData = JSON.parse(ex); } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored LP data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storedData.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(key); return; } diff --git a/bases/rsptx/interactives/runestone/matching/js/matching.js b/bases/rsptx/interactives/runestone/matching/js/matching.js index 3db2c80b4..4750e9cc6 100644 --- a/bases/rsptx/interactives/runestone/matching/js/matching.js +++ b/bases/rsptx/interactives/runestone/matching/js/matching.js @@ -170,6 +170,10 @@ export class MatchingProblem extends RunestoneBase { const data = localStorage.getItem(this.divid); if (data) { const parsedData = JSON.parse(data); + if (parsedData.timestamp && parsedData.timestamp < eBookConfig.termStartDate) { + localStorage.removeItem(this.divid); + return; + } this.connections = parsedData.connections.map(conn => ({ fromBox: this.allBoxes.find(box => box.dataset.id === conn.from), toBox: this.allBoxes.find(box => box.dataset.id === conn.to) @@ -184,6 +188,7 @@ export class MatchingProblem extends RunestoneBase { } } setLocalStorage() { + const timeStamp = new Date(); const data = { connections: this.connections.map(conn => ({ from: conn.fromBox.dataset.id, @@ -192,7 +197,8 @@ export class MatchingProblem extends RunestoneBase { score: this.scorePercent, correctCount: this.correctCount, incorrectCount: this.incorrectCount, - missingCount: this.missingCount + missingCount: this.missingCount, + timestamp: timeStamp }; localStorage.setItem(this.divid, JSON.stringify(data)); } diff --git a/bases/rsptx/interactives/runestone/mchoice/js/mchoice.js b/bases/rsptx/interactives/runestone/mchoice/js/mchoice.js index a0d8d7f23..9ca856fb4 100644 --- a/bases/rsptx/interactives/runestone/mchoice/js/mchoice.js +++ b/bases/rsptx/interactives/runestone/mchoice/js/mchoice.js @@ -346,13 +346,17 @@ export default class MultipleChoice extends RunestoneBase { var len = localStorage.length; if (len > 0) { var ex = localStorage.getItem(this.localStorageKey()); + let error = false; if (ex !== null) { try { storedData = JSON.parse(ex); answers = storedData.answer.split(","); } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored mchoice data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storedData.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); return; } diff --git a/bases/rsptx/interactives/runestone/parsons/js/parsons.js b/bases/rsptx/interactives/runestone/parsons/js/parsons.js index 64bce588c..18a76a55e 100644 --- a/bases/rsptx/interactives/runestone/parsons/js/parsons.js +++ b/bases/rsptx/interactives/runestone/parsons/js/parsons.js @@ -998,7 +998,12 @@ export default class Parsons extends RunestoneBase { if (this.graderactive) { return; } - this.loadData(this.localData()); + const localData = this.localData(); + if (localData.timestamp && localData.timestamp < eBookConfig.termStartDate) { + localStorage.removeItem(this.storageId); + localData= {}; + } + this.loadData(localData); } // RunestoneBase: Set the state of the problem in local storage setLocalStorage(data) { diff --git a/bases/rsptx/interactives/runestone/shortanswer/js/shortanswer.js b/bases/rsptx/interactives/runestone/shortanswer/js/shortanswer.js index 6b10ff82a..25e9bf09d 100644 --- a/bases/rsptx/interactives/runestone/shortanswer/js/shortanswer.js +++ b/bases/rsptx/interactives/runestone/shortanswer/js/shortanswer.js @@ -252,12 +252,16 @@ export default class ShortAnswer extends RunestoneBase { if (len > 0) { var ex = localStorage.getItem(this.localStorageKey()); if (ex !== null) { + let error = false; try { var storedData = JSON.parse(ex); answer = storedData.answer; } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored shortanswer data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storedData.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); return; } diff --git a/bases/rsptx/interactives/runestone/timed/js/timed.js b/bases/rsptx/interactives/runestone/timed/js/timed.js index 5b56ab9f2..5378988a8 100644 --- a/bases/rsptx/interactives/runestone/timed/js/timed.js +++ b/bases/rsptx/interactives/runestone/timed/js/timed.js @@ -1193,13 +1193,19 @@ export default class Timed extends RunestoneBase { this.taken = 1; var tmpArr; if (data === "") { + let error = false; + let storageObj; try { - tmpArr = JSON.parse( + storageObj = JSON.parse( localStorage.getItem(this.localStorageKey()) - ).answer; + ); + tmpArr = storageObj.answer; } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored Timed data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storageObj.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); this.taken = 0; return; diff --git a/bases/rsptx/interactives/runestone/webwork/js/webwork.js b/bases/rsptx/interactives/runestone/webwork/js/webwork.js index 276c3b651..71a4832de 100644 --- a/bases/rsptx/interactives/runestone/webwork/js/webwork.js +++ b/bases/rsptx/interactives/runestone/webwork/js/webwork.js @@ -53,6 +53,7 @@ class WebWork extends RunestoneBase { var ex = localStorage.getItem(this.localStorageKey()); if (ex !== null) { + let error = false; try { storedData = JSON.parse(ex); // Save the answers so that when the question is activated we can restore. @@ -63,7 +64,10 @@ class WebWork extends RunestoneBase { this.decorateStatus(); } catch (err) { // error while parsing; likely due to bad value stored in storage - console.log(err.message); + console.log(`Error parsing stored WebWork data for ${this.divid}: ${err.message}`); + error = true; + } + if (error || storedData.timestamp < eBookConfig.termStartDate) { localStorage.removeItem(this.localStorageKey()); return; } diff --git a/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py b/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py index a8371ce95..e96020710 100644 --- a/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py +++ b/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py @@ -284,6 +284,14 @@ def _scorable_mchoice_answers( & (db.mchoice_answers.sid == sid) & (db.mchoice_answers.div_id == question_name) ) + + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.mchoice_answers.timestamp >= course.term_start_date) + if deadline: query = query & (db.mchoice_answers.timestamp < deadline) if practice_start_time: @@ -351,6 +359,13 @@ def _scorable_useinfos( else: query = query & (db.useinfo.div_id == div_id) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.useinfo.timestamp >= course.term_start_date) + if event_filter: query = query & (db.useinfo.event == event_filter) if deadline: @@ -379,6 +394,12 @@ def _scorable_webwork_answers( & (db.webwork_answers.sid == sid) & (db.webwork_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.webwork_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.webwork_answers.timestamp < deadline) if practice_start_time: @@ -403,6 +424,12 @@ def _scorable_parsons_answers( & (db.parsons_answers.sid == sid) & (db.parsons_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.parsons_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.parsons_answers.timestamp < deadline) if practice_start_time: @@ -427,6 +454,12 @@ def _scorable_microparsons_answers( & (db.microparsons_answers.sid == sid) & (db.microparsons_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.microparsons_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.microparsons_answers.timestamp < deadline) if practice_start_time: @@ -451,6 +484,12 @@ def _scorable_fitb_answers( & (db.fitb_answers.sid == sid) & (db.fitb_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.fitb_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.fitb_answers.timestamp < deadline) if practice_start_time: @@ -475,6 +514,12 @@ def _scorable_clickablearea_answers( & (db.clickablearea_answers.sid == sid) & (db.clickablearea_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.clickablearea_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.clickablearea_answers.timestamp < deadline) if practice_start_time: @@ -499,6 +544,12 @@ def _scorable_dragndrop_answers( & (db.dragndrop_answers.sid == sid) & (db.dragndrop_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.dragndrop_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.dragndrop_answers.timestamp < deadline) if practice_start_time: @@ -547,6 +598,12 @@ def _scorable_splice_answers( & (db.splice_answers.sid == sid) & (db.splice_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.splice_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.splice_answers.timestamp < deadline) if practice_start_time: @@ -571,6 +628,12 @@ def _scorable_codelens_answers( & (db.codelens_answers.sid == sid) & (db.codelens_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.codelens_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.codelens_answers.timestamp < deadline) if practice_start_time: @@ -595,12 +658,18 @@ def _scorable_lp_answers( & (db.lp_answers.sid == sid) & (db.lp_answers.div_id == question_name) ) + course = ( + db(db.courses.course_name == course_name) + .select() + .first() + ) + query = query & (db.lp_answers.timestamp >= course.term_start_date) if deadline: query = query & (db.lp_answers.timestamp < deadline) if practice_start_time: - query = query & (db.codelens_answers.timestamp >= practice_start_time) + query = query & (db.lp_answers.timestamp >= practice_start_time) if now: - query = query & (db.codelens_answers.timestamp <= now) + query = query & (db.lp_answers.timestamp <= now) return db(query).select(orderby=db.lp_answers.timestamp) diff --git a/components/rsptx/db/crud/book.py b/components/rsptx/db/crud/book.py index a6d44cbf5..b9914df39 100644 --- a/components/rsptx/db/crud/book.py +++ b/components/rsptx/db/crud/book.py @@ -4,6 +4,7 @@ from ..models import ( Chapter, ChapterValidator, + Courses, SubChapter, SubChapterValidator, Question, @@ -137,11 +138,16 @@ async def fetch_page_activity_counts( page_divids = await session.execute(query) rslogger.debug(f"PDVD {page_divids}") div_counts = {q.name: 0 for q in page_divids.scalars()} - query = select(distinct(Useinfo.div_id)).where( - where_clause_common - & (Question.name == Useinfo.div_id) - & (Useinfo.course_id == course_name) - & (Useinfo.sid == username) + query = ( + select(distinct(Useinfo.div_id)) + .join(Courses, Courses.course_name == Useinfo.course_id) + .where( + where_clause_common + & (Question.name == Useinfo.div_id) + & (Useinfo.course_id == course_name) + & (Useinfo.sid == username) + & (Useinfo.timestamp > Courses.term_start_date) + ) ) async with async_session() as session: sid_counts = await session.execute(query) diff --git a/components/rsptx/db/crud/rslogging.py b/components/rsptx/db/crud/rslogging.py index bea3f8af6..5cf3c8675 100644 --- a/components/rsptx/db/crud/rslogging.py +++ b/components/rsptx/db/crud/rslogging.py @@ -6,6 +6,7 @@ from ..models import ( Useinfo, UseinfoValidation, + Courses, CoursesValidator, Code, CodeValidator, @@ -169,12 +170,14 @@ async def fetch_last_answer_table_entry( deadline_offset_naive = query_data.deadline.replace(tzinfo=None) query = ( select(tbl) + .join(Courses, Courses.course_name == tbl.course_name) .where( and_( tbl.div_id == query_data.div_id, tbl.course_name == query_data.course, tbl.sid == query_data.sid, tbl.timestamp <= deadline_offset_naive, + tbl.timestamp >= Courses.term_start_date, ) ) .order_by(tbl.timestamp.desc()) @@ -238,7 +241,13 @@ async def fetch_code( """ query = ( select(Code) - .where((Code.sid == sid) & (Code.acid == acid) & (Code.course_id == course_id)) + .join(Courses, Courses.id == Code.course_id) + .where( + (Code.sid == sid) + & (Code.acid == acid) + & (Code.course_id == course_id) + & (Code.timestamp >= Courses.term_start_date) + ) .order_by(Code.id.desc()) ) if limit > 0: diff --git a/components/rsptx/templates/book/course/current_course.html b/components/rsptx/templates/book/course/current_course.html index 0ef52ee2a..2ae6eaaeb 100644 --- a/components/rsptx/templates/book/course/current_course.html +++ b/components/rsptx/templates/book/course/current_course.html @@ -36,6 +36,12 @@

{{course.course_name}}

{% if course_description %}

{{course_description}}

{% endif %} +

Course start date: {{course.term_start_date}}

+ {% if is_old_course and is_instructor %} +
+ Warning: Course is over one year old. +
+ {% endif %}

School: {{institution}}

Student: {{user.first_name}} {{user.last_name}} (Not you?)

Instructors

@@ -46,6 +52,7 @@

Instructors

{% endfor %} +

{{course}}

Textbooks