1
0

Add a working-date selector to the instructor home page

Everything on the home page — availability counts, live mode, the
calls editor, and list generation — now targets a selectable working
date instead of being fixed to today, with quick links to upcoming
scheduled class days. Printing tomorrow's list a day in advance is
just switching the date and generating; other days' data is
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 19:10:48 -07:00
parent c7e1058d19
commit 1a4e33a26e
3 changed files with 67 additions and 22 deletions

View File

@@ -92,6 +92,10 @@ def home():
db = get_db()
course = current_course()
today = datetime.date.today()
# The working date: everything on the page targets it. Defaults to
# today, but e.g. printing tomorrow's list a day early just means
# picking tomorrow here.
day = _parse_date(request.args.get("date"))
recent_days = db.execute(
select(
@@ -105,16 +109,19 @@ def home():
.limit(10)
).all()
today_calls = calls.calls_for_day(db, course.id, today)
context = _day_context(db, course, today)
day_calls = calls.calls_for_day(db, course.id, day)
context = _day_context(db, course, day)
context.update(
recent_days=recent_days,
today=today,
has_pending_today=any(
c.status == STATUS_PENDING for c in today_calls
upcoming_class_days=[
d.date for d in class_days(db, course.id, start=today)
][:4],
has_pending=any(
c.status == STATUS_PENDING for c in day_calls
),
resolved_today=sum(
1 for c in today_calls if c.status != STATUS_PENDING
resolved=sum(
1 for c in day_calls if c.status != STATUS_PENDING
),
)
return render_template("instructor_home.html", **context)

View File

@@ -4,12 +4,30 @@
<h1>{{ course.title or course.lti_context_id }}</h1>
<p>
{{ roster_count }} active students on the roster;
{{ present_count }} available today ({{ optout_count }} opted out).
{{ present_count }} available on {{ day.isoformat() }}
({{ optout_count }} opted out).
</p>
<form method="get" action="{{ url_for('instructor.home') }}">
<label>Working date:
<input type="date" name="date" value="{{ day.isoformat() }}"></label>
<button type="submit">Switch</button>
{% if day != today %}
<a href="{{ url_for('instructor.home') }}">back to today</a>
{% endif %}
{% if upcoming_class_days %}
<span class="muted">Class days:</span>
{% for d in upcoming_class_days %}
<a href="{{ url_for('instructor.home', date=d.isoformat()) }}"
{% if d == day %}style="font-weight: bold"{% endif %}>
{{ d.strftime("%a %b %-d") }}</a>
{% endfor %}
{% endif %}
</form>
<p>
<a href="{{ url_for('instructor.live') }}">Live cold call</a> ·
<a href="{{ url_for('instructor.day_edit', date_str=today.isoformat()) }}">Today's calls</a> ·
<a href="{{ url_for('instructor.live', date=day.isoformat()) }}">Live cold call</a> ·
<a href="{{ url_for('instructor.day_edit', date_str=day.isoformat()) }}">Calls for {{ day.isoformat() }}</a> ·
<a href="{{ url_for('instructor.report') }}">Report</a> ·
<a href="{{ url_for('grades.view') }}">Grades</a> ·
<a href="{{ url_for('instructor.schedule') }}">Schedule</a> ·
@@ -23,22 +41,22 @@
</form>
<h2>Printable list</h2>
{% if has_pending_today %}
{% if has_pending %}
<p>
<a href="{{ url_for('instructor.day_print', date_str=today.isoformat()) }}">
View today's list</a>
<a href="{{ url_for('instructor.day_print', date_str=day.isoformat()) }}">
View the list for {{ day.isoformat() }}</a>
</p>
{% endif %}
<form method="post"
action="{{ url_for('instructor.generate', date_str=today.isoformat()) }}"
{% if resolved_today %}
onsubmit="return confirm('{{ resolved_today }} call(s) today already have recorded outcomes. Those are kept, but the unused list lines are replaced by a fresh draw. Continue?');"
action="{{ url_for('instructor.generate', date_str=day.isoformat()) }}"
{% if resolved %}
onsubmit="return confirm('{{ resolved }} call(s) on {{ day.isoformat() }} already have recorded outcomes. Those are kept, but the unused list lines are replaced by a fresh draw. Continue?');"
{% endif %}>
<label>Number of calls (in cycle mode, the next batch of the
rotation): <input type="number" name="n" min="1" value="{{ present_count }}"></label>
<button type="submit">
{{ "Regenerate list (replaces the current one)" if has_pending_today
else "Generate list for " + today.isoformat() }}
{{ "Regenerate list (replaces the current one)" if has_pending
else "Generate list for " + day.isoformat() }}
</button>
</form>
@@ -46,14 +64,14 @@
<h2>Recent days</h2>
<table>
<tr><th>Date</th><th>Calls</th><th>Answered</th><th></th></tr>
{% for day, total, answered in recent_days %}
{% for d, total, answered in recent_days %}
<tr>
<td>{{ day.isoformat() }}</td>
<td>{{ d.isoformat() }}</td>
<td>{{ total }}</td>
<td>{{ answered or 0 }}</td>
<td>
<a href="{{ url_for('instructor.day_edit', date_str=day.isoformat()) }}">edit</a> ·
<a href="{{ url_for('instructor.day_print', date_str=day.isoformat()) }}">print</a>
<a href="{{ url_for('instructor.day_edit', date_str=d.isoformat()) }}">edit</a> ·
<a href="{{ url_for('instructor.day_print', date_str=d.isoformat()) }}">print</a>
</td>
</tr>
{% endfor %}

View File

@@ -69,7 +69,7 @@ def test_regenerate_replaces_pending_and_labels_change(instructor):
instructor.post(f"/instructor/day/{TODAY}/generate", data={"n": "12"})
page = instructor.get("/instructor/").get_data(as_text=True)
assert "View today's list" in page
assert f"View the list for {TODAY}" in page
assert "Regenerate list" in page
# Regenerating replaces the unused list outright.
@@ -95,6 +95,26 @@ def test_regenerate_replaces_pending_and_labels_change(instructor):
assert "already have recorded outcomes" in page
def test_home_working_date_override(instructor):
tomorrow = (
datetime.date.today() + datetime.timedelta(days=1)
).isoformat()
page = instructor.get(f"/instructor/?date={tomorrow}").get_data(as_text=True)
assert f"available on {tomorrow}" in page
assert f"Generate list for {tomorrow}" in page
assert "back to today" in page
# Generate tomorrow's list a day in advance; today stays untouched.
resp = instructor.post(
f"/instructor/day/{tomorrow}/generate",
data={"n": "5"},
follow_redirects=True,
)
assert resp.get_data(as_text=True).count("<tr>") == 6
page = instructor.get(f"/instructor/day/{TODAY}/print").get_data(as_text=True)
assert "No call list has been generated" in page
def test_add_call_after_the_fact(instructor):
page = instructor.get(f"/instructor/day/{TODAY}").get_data(as_text=True)
student_id = re.search(r'<option value="(\d+)">', page).group(1)