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()
|
db = get_db()
|
||||||
course = current_course()
|
course = current_course()
|
||||||
today = datetime.date.today()
|
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(
|
recent_days = db.execute(
|
||||||
select(
|
select(
|
||||||
@@ -105,16 +109,19 @@ def home():
|
|||||||
.limit(10)
|
.limit(10)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
today_calls = calls.calls_for_day(db, course.id, today)
|
day_calls = calls.calls_for_day(db, course.id, day)
|
||||||
context = _day_context(db, course, today)
|
context = _day_context(db, course, day)
|
||||||
context.update(
|
context.update(
|
||||||
recent_days=recent_days,
|
recent_days=recent_days,
|
||||||
today=today,
|
today=today,
|
||||||
has_pending_today=any(
|
upcoming_class_days=[
|
||||||
c.status == STATUS_PENDING for c in today_calls
|
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(
|
resolved=sum(
|
||||||
1 for c in today_calls if c.status != STATUS_PENDING
|
1 for c in day_calls if c.status != STATUS_PENDING
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return render_template("instructor_home.html", **context)
|
return render_template("instructor_home.html", **context)
|
||||||
|
|||||||
@@ -4,12 +4,30 @@
|
|||||||
<h1>{{ course.title or course.lti_context_id }}</h1>
|
<h1>{{ course.title or course.lti_context_id }}</h1>
|
||||||
<p>
|
<p>
|
||||||
{{ roster_count }} active students on the roster;
|
{{ 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>
|
</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>
|
<p>
|
||||||
<a href="{{ url_for('instructor.live') }}">Live cold call</a> ·
|
<a href="{{ url_for('instructor.live', date=day.isoformat()) }}">Live cold call</a> ·
|
||||||
<a href="{{ url_for('instructor.day_edit', date_str=today.isoformat()) }}">Today's calls</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('instructor.report') }}">Report</a> ·
|
||||||
<a href="{{ url_for('grades.view') }}">Grades</a> ·
|
<a href="{{ url_for('grades.view') }}">Grades</a> ·
|
||||||
<a href="{{ url_for('instructor.schedule') }}">Schedule</a> ·
|
<a href="{{ url_for('instructor.schedule') }}">Schedule</a> ·
|
||||||
@@ -23,22 +41,22 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>Printable list</h2>
|
<h2>Printable list</h2>
|
||||||
{% if has_pending_today %}
|
{% if has_pending %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ url_for('instructor.day_print', date_str=today.isoformat()) }}">
|
<a href="{{ url_for('instructor.day_print', date_str=day.isoformat()) }}">
|
||||||
View today's list</a>
|
View the list for {{ day.isoformat() }}</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post"
|
<form method="post"
|
||||||
action="{{ url_for('instructor.generate', date_str=today.isoformat()) }}"
|
action="{{ url_for('instructor.generate', date_str=day.isoformat()) }}"
|
||||||
{% if resolved_today %}
|
{% if resolved %}
|
||||||
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?');"
|
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 %}>
|
{% endif %}>
|
||||||
<label>Number of calls (in cycle mode, the next batch of the
|
<label>Number of calls (in cycle mode, the next batch of the
|
||||||
rotation): <input type="number" name="n" min="1" value="{{ present_count }}"></label>
|
rotation): <input type="number" name="n" min="1" value="{{ present_count }}"></label>
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
{{ "Regenerate list (replaces the current one)" if has_pending_today
|
{{ "Regenerate list (replaces the current one)" if has_pending
|
||||||
else "Generate list for " + today.isoformat() }}
|
else "Generate list for " + day.isoformat() }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -46,14 +64,14 @@
|
|||||||
<h2>Recent days</h2>
|
<h2>Recent days</h2>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>Date</th><th>Calls</th><th>Answered</th><th></th></tr>
|
<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>
|
<tr>
|
||||||
<td>{{ day.isoformat() }}</td>
|
<td>{{ d.isoformat() }}</td>
|
||||||
<td>{{ total }}</td>
|
<td>{{ total }}</td>
|
||||||
<td>{{ answered or 0 }}</td>
|
<td>{{ answered or 0 }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('instructor.day_edit', date_str=day.isoformat()) }}">edit</a> ·
|
<a href="{{ url_for('instructor.day_edit', date_str=d.isoformat()) }}">edit</a> ·
|
||||||
<a href="{{ url_for('instructor.day_print', date_str=day.isoformat()) }}">print</a>
|
<a href="{{ url_for('instructor.day_print', date_str=d.isoformat()) }}">print</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def test_regenerate_replaces_pending_and_labels_change(instructor):
|
|||||||
|
|
||||||
instructor.post(f"/instructor/day/{TODAY}/generate", data={"n": "12"})
|
instructor.post(f"/instructor/day/{TODAY}/generate", data={"n": "12"})
|
||||||
page = instructor.get("/instructor/").get_data(as_text=True)
|
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
|
assert "Regenerate list" in page
|
||||||
|
|
||||||
# Regenerating replaces the unused list outright.
|
# 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
|
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):
|
def test_add_call_after_the_fact(instructor):
|
||||||
page = instructor.get(f"/instructor/day/{TODAY}").get_data(as_text=True)
|
page = instructor.get(f"/instructor/day/{TODAY}").get_data(as_text=True)
|
||||||
student_id = re.search(r'<option value="(\d+)">', page).group(1)
|
student_id = re.search(r'<option value="(\d+)">', page).group(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user