Setup banner, assessment timestamps, and installation docs
Instructor home shows a setup banner while a course has no class schedule, since that is the one gap that degrades the student experience (free-date opt-outs, no withdrawal locking); it points at the prefilled schedule form and settings and disappears once any class day exists. Calls gain assessment_entered_at, stamped when an outcome is recorded or actually changed (bulk saves that change nothing don't restamp), null for pending calls and legacy imports, and included in the calls export — distinguishing assessed-in-class from assessed-later. The README setup section now gives both install routes: pure pip/venv, and Debian system packages with the apt line. Fixing this uncovered that numpy was missing from the project dependencies (the grading engine needs it; the system-site-packages venv had masked the omission). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,8 @@ import pytest
|
||||
|
||||
from conftest import outcome_actions
|
||||
|
||||
TODAY = datetime.date.today().isoformat()
|
||||
TODAY_DATE = datetime.date.today()
|
||||
TODAY = TODAY_DATE.isoformat()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -95,6 +96,79 @@ def test_regenerate_replaces_pending_and_labels_change(instructor):
|
||||
assert "already have recorded outcomes" in page
|
||||
|
||||
|
||||
def test_assessment_entered_at_stamping(instructor):
|
||||
from coldcall_lti.models import Call
|
||||
|
||||
# Live flow: pending call has no stamp; recording an outcome sets it.
|
||||
instructor.post("/instructor/live/next", data={"date": TODAY})
|
||||
page = instructor.get(f"/instructor/live?date={TODAY}").get_data(as_text=True)
|
||||
call_id = int(re.search(r"/instructor/call/(\d+)/outcome", page).group(1))
|
||||
|
||||
app = instructor.application
|
||||
|
||||
def entered_at(cid):
|
||||
with app.app_context():
|
||||
db = app.extensions["db_session_factory"]()
|
||||
value = db.get(Call, cid).assessment_entered_at
|
||||
db.close()
|
||||
return value
|
||||
|
||||
assert entered_at(call_id) is None
|
||||
instructor.post(
|
||||
f"/instructor/call/{call_id}/outcome",
|
||||
data={"action": outcome_actions(page)["GOOD"]},
|
||||
)
|
||||
first_stamp = entered_at(call_id)
|
||||
assert first_stamp is not None
|
||||
|
||||
# A day-editor save that changes nothing does not restamp; a real
|
||||
# change does.
|
||||
edit = instructor.get(f"/instructor/day/{TODAY}").get_data(as_text=True)
|
||||
good_id = re.search(
|
||||
r'value="(\d+)" selected>GOOD</option>', edit
|
||||
).group(1)
|
||||
instructor.post(
|
||||
f"/instructor/day/{TODAY}",
|
||||
data={f"status-{call_id}": "answered",
|
||||
f"assessment-{call_id}": good_id},
|
||||
)
|
||||
assert entered_at(call_id) == first_stamp
|
||||
|
||||
instructor.post(
|
||||
f"/instructor/day/{TODAY}",
|
||||
data={f"status-{call_id}": "missing"},
|
||||
)
|
||||
assert entered_at(call_id) != first_stamp
|
||||
|
||||
# The export carries the column.
|
||||
csv_text = instructor.get("/instructor/export/calls.csv").get_data(as_text=True)
|
||||
assert "assessment_entered_at" in csv_text.splitlines()[0]
|
||||
|
||||
|
||||
def test_setup_banner_until_schedule_exists(instructor):
|
||||
# The dev course seeds a schedule, so no banner by default.
|
||||
page = instructor.get("/instructor/").get_data(as_text=True)
|
||||
assert "No class schedule yet" not in page
|
||||
|
||||
# Strip the schedule; the banner appears.
|
||||
from coldcall_lti.models import ClassDay
|
||||
|
||||
app = instructor.application
|
||||
with app.app_context():
|
||||
db = app.extensions["db_session_factory"]()
|
||||
db.query(ClassDay).delete()
|
||||
db.commit()
|
||||
db.close()
|
||||
page = instructor.get("/instructor/").get_data(as_text=True)
|
||||
assert "No class schedule yet" in page
|
||||
|
||||
# Adding a single class day dismisses it.
|
||||
future = (TODAY_DATE + datetime.timedelta(days=30)).isoformat()
|
||||
instructor.post("/instructor/schedule/add", data={"date": future})
|
||||
page = instructor.get("/instructor/").get_data(as_text=True)
|
||||
assert "No class schedule yet" not in page
|
||||
|
||||
|
||||
def test_home_working_date_override(instructor):
|
||||
tomorrow = (
|
||||
datetime.date.today() + datetime.timedelta(days=1)
|
||||
|
||||
Reference in New Issue
Block a user