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:
@@ -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)
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user