From 1be9fe19fed0b4fe80d824a66d3d3729f8f2c507 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 24 Oct 2017 23:00:56 +0200 Subject: [PATCH 01/12] Update to Bootstrap 4-beta.2 --- naucse/templates/_base.html | 8 ++++---- naucse/templates/backpage.html | 6 +++--- naucse/templates/coverpage.html | 6 +++--- naucse/templates/lesson.html | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/naucse/templates/_base.html b/naucse/templates/_base.html index 0101cd2fd5..d6b5eef71a 100644 --- a/naucse/templates/_base.html +++ b/naucse/templates/_base.html @@ -11,7 +11,7 @@ {% endif %} - + {% if page is defined and page.latex %} @@ -76,9 +76,9 @@ - - - + + + {% if page is defined and page.latex %} diff --git a/naucse/templates/backpage.html b/naucse/templates/backpage.html index 8729d8ca41..830a668c6c 100644 --- a/naucse/templates/backpage.html +++ b/naucse/templates/backpage.html @@ -57,16 +57,16 @@

Zajímavé odkazy


{% if session.next is defined and session.next != None %} - Lekce: {{ session.next.title }} + Lekce: {{ session.next.title }} {% endif %}
diff --git a/naucse/templates/coverpage.html b/naucse/templates/coverpage.html index cbeda74d0b..66a249c088 100644 --- a/naucse/templates/coverpage.html +++ b/naucse/templates/coverpage.html @@ -68,16 +68,16 @@

Taháky

{% if session.prev is defined and session.prev != None %} - Lekce: {{ session.prev.title }} + Lekce: {{ session.prev.title }} {% endif %}
diff --git a/naucse/templates/lesson.html b/naucse/templates/lesson.html index e518a7c8cf..d5c19d8a31 100644 --- a/naucse/templates/lesson.html +++ b/naucse/templates/lesson.html @@ -45,21 +45,21 @@
{% if prv is defined and prv != None %} - {{ prv.title }} + {{ prv.title }} {% endif %}
{% if session is defined and session != None and course is defined %} - Lekce: {{ session.title }} + Lekce: {{ session.title }} {% endif %}
{% if nxt is defined and nxt != None %} - {{ nxt.title }} + {{ nxt.title }} {% elif session is defined and session != None and course is defined %} - Závěr lekce + Závěr lekce {% endif %}
From 3a8134b15e75d58bcfbbfa89f997ac2c987388ae Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 24 Oct 2017 22:52:19 +0200 Subject: [PATCH 02/12] Add date information to course description --- naucse/templates/course.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 730be737d2..e40c7dd897 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -13,7 +13,7 @@ > {{ course.title }}
- + {% if not course.canonical %}
Toto je kurz, který probíhá nebo proběhl naživo s instruktorem. @@ -24,6 +24,17 @@ {% endif %} + {% if course.start_date %} +
+
+
+ {{ (course.start_date, course.end_date) | format_date_range }} +
+ +
+
+ {% endif %} +

{{ course.title }}

{% if course.subtitle is defined and course.subtitle != None %} From 26db395748c70e00913ae4c3d7ecf06ce75a369d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 25 Oct 2017 00:01:35 +0200 Subject: [PATCH 03/12] Format dates in session lists --- naucse/templates.py | 7 ++++++- naucse/templates/_lessons_list.html | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/naucse/templates.py b/naucse/templates.py index ca08c6a761..fedaeb127f 100644 --- a/naucse/templates.py +++ b/naucse/templates.py @@ -170,6 +170,11 @@ def __str__(self): } +@template_filter() +def format_date(date, relative_to=None): + return '{d.day}. {d.month}. {d.year}'.format(d=date) + + @template_filter() def format_date_range(start_and_end): start, end = start_and_end @@ -182,5 +187,5 @@ def format_date_range(start_and_end): else: parts += ['{start.day}.'] parts += [' – '] - parts += ['{end.day}. {end.month}. {end.year}'] + parts += [format_date(end)] return ''.join(parts).format(start=start, end=end) diff --git a/naucse/templates/_lessons_list.html b/naucse/templates/_lessons_list.html index 700833df25..fc5a7867c3 100644 --- a/naucse/templates/_lessons_list.html +++ b/naucse/templates/_lessons_list.html @@ -8,7 +8,7 @@ {% endif %} {{ session.title }} {% if session.date %} - ({{ session.date }}) + ({{ session.date | format_date }}) {% endif %} {% endmacro %} From d0d95e7df77f3d1e5a5fd1feb7b51b8293e9e890 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 25 Oct 2017 00:02:14 +0200 Subject: [PATCH 04/12] Add info box to courses with dates --- naucse/templates/course.html | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/naucse/templates/course.html b/naucse/templates/course.html index e40c7dd897..82e71eec61 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -13,24 +13,31 @@ > {{ course.title }}
- - {% if not course.canonical %} -
- Toto je kurz, který probíhá nebo proběhl naživo s instruktorem. - {% if course.base_course %} - Přejít na ekvivalentní kurz pro samouky. - {% endif %} -
- {% endif %} {% if course.start_date %} -
+
+
+ {% if course.vars['coach-present'] %} + Kurz s instruktorem + {% else %} + Termín kurzu + {% endif %} +
{{ (course.start_date, course.end_date) | format_date_range }}
- +
+
{% endif %} From a8015efc9a1b8d63faa340014d20d94fa90232c1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 25 Oct 2017 01:49:36 +0200 Subject: [PATCH 05/12] Add calendar pages for courses --- naucse/models.py | 8 +-- naucse/routes.py | 22 ++++++++ naucse/static/css/calendar.css | 63 ++++++++++++++++++++++ naucse/templates.py | 7 ++- naucse/templates/course.html | 4 +- naucse/templates/course_calendar.html | 76 +++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 naucse/static/css/calendar.css create mode 100644 naucse/templates/course_calendar.html diff --git a/naucse/models.py b/naucse/models.py index 0bc34dcf05..6493ec04e8 100644 --- a/naucse/models.py +++ b/naucse/models.py @@ -281,9 +281,10 @@ def merge_dict(base, patch): class Session(Model): """An ordered collection of materials""" - def __init__(self, root, path, base_course, info): + def __init__(self, root, path, base_course, info, index): super().__init__(root, path) base_name = info.get('base') + self.index = index if base_name is None: self.info = info else: @@ -336,8 +337,9 @@ def get_coverpage_content(self, run, coverpage, app): def _get_sessions(course, plan): result = OrderedDict() - for sess_info in plan: - session = Session(course.root, course.path, course.base_course, sess_info) + for index, sess_info in enumerate(plan): + session = Session(course.root, course.path, course.base_course, + sess_info, index=index) result[session.slug] = session sessions = list(result.values()) diff --git a/naucse/routes.py b/naucse/routes.py index 9cfbf63c74..b7a175dadf 100644 --- a/naucse/routes.py +++ b/naucse/routes.py @@ -1,5 +1,6 @@ import os import datetime +import calendar from flask import Flask, render_template, url_for, send_from_directory from flask import abort, redirect @@ -265,3 +266,24 @@ def session_url(session): homework_section=homework_section, link_section=link_section, cheatsheet_section=cheatsheet_section) + +@app.route('//calendar/') +def course_calendar(course): + if not course.start_date: + abort(404) + months = [] + year = course.start_date.year + month = course.start_date.month + while (year, month) <= (course.end_date.year, course.end_date.month): + months.append((year, month)) + month += 1 + if month > 12: + month = 1 + year += 1 + sessions_by_date = {s.date: s for s in course.sessions.values()} + return render_template('course_calendar.html', + edit_path=course.edit_path, + course=course, + sessions_by_date=sessions_by_date, + months=months, + calendar=calendar.Calendar()) diff --git a/naucse/static/css/calendar.css b/naucse/static/css/calendar.css new file mode 100644 index 0000000000..2879726354 --- /dev/null +++ b/naucse/static/css/calendar.css @@ -0,0 +1,63 @@ +.calendar-card .card-header .year { + color: #868e96; +} + +table.calendar { + border-spacing: 0.25em; + border-collapse: separate; + display: block; + margin: 0.5em auto; +} + +table.calendar tr { + height: 4em; +} + +table.calendar td, +table.calendar th { + width: 14.28%; + min-width: 5em; + height: 100%; + background-color: rgba(0,0,0,.03); + vertical-align: center; + text-align: center; + box-sizing: border-box; + padding: 0; +} + +table.calendar td.foreign-month { + background-color: transparent; + color: rgba(0,0,0,.1); +} + +table.calendar td.no-event { + color: #343a40; +} + +table.calendar td.event { + background-color: #3fb0ac; + color: white; +} + +table.calendar td.event a { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + height: 100%; + padding: 0.5em; + color: white; +} + +table.calendar .session-number { + display: block; +} + +table.calendar .session-title { + display: block; + width: 4em; + text-align: center; + text-overflow: '…'; + white-space: nowrap; + overflow: hidden; +} diff --git a/naucse/templates.py b/naucse/templates.py index fedaeb127f..e3da3272dd 100644 --- a/naucse/templates.py +++ b/naucse/templates.py @@ -187,5 +187,10 @@ def format_date_range(start_and_end): else: parts += ['{start.day}.'] parts += [' – '] - parts += [format_date(end)] + parts += [format_date(end)] return ''.join(parts).format(start=start, end=end) + +@template_filter() +def monthname(number): + return ('Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', + 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec')[number-1] diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 82e71eec61..3f91bd0a6f 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -31,7 +31,9 @@
- {% endif %} + {% endif %} -

{{ course.title }}

+

{{ course.title }}

- {% if course.subtitle is defined and course.subtitle != None %} -

{{ course.subtitle }}

- {% endif%} + {% if course.subtitle is defined and course.subtitle != None %} +

{{ course.subtitle }}

+ {% endif%} - {{ course.long_description | markdown }} + {{ course.long_description | markdown }} + +
+
+ {% for session in plan.values() %} +
+

+ + {{ session_heading(session, loop.index, plan) }} + +

+ {% for mat in session.materials %} +
+ {% if mat.type == "page" %} + {{ mat.title }} + {% else %} + {{ mat.title }} + {% endif %} +
+ {% endfor %} +
+ {% endfor %} +
+
+ +
-{% endblock headline %} +{% endblock content %} From 1dcc520c076c47301abcd369a8333dc4c705458b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 29 Oct 2017 20:55:38 +0100 Subject: [PATCH 07/12] Add side card for the self-paced courses Helps with: https://github.com/pyvec/naucse.python.cz/issues/255 --- naucse/routes.py | 22 +++++++ naucse/static/css/nausce.css | 4 ++ naucse/templates/course.html | 123 +++++++++++++++++++++++++---------- 3 files changed, 113 insertions(+), 36 deletions(-) diff --git a/naucse/routes.py b/naucse/routes.py index b7a175dadf..6c19fbebad 100644 --- a/naucse/routes.py +++ b/naucse/routes.py @@ -117,6 +117,27 @@ def course(course): def lesson_url(lesson, *args, **kwargs): return url_for('course_page', course=course, lesson=lesson, *args, **kwargs) + recent_runs = [] + if not course.start_date: + # Build a list of "recent" runs based on this course. + # By recent we mean: haven't ended yet, or ended up to ~2 months ago + # (Note: even if naucse is hosted dynamically, + # it's still beneficial to show recently ended runs.) + today = datetime.date.today() + cutoff = today - datetime.timedelta(days=2*30) + this_year = today.year + for year, run_year in reversed(course.root.run_years.items()): + for run in run_year.runs.values(): + if run.base_course is course and run.end_date > cutoff: + recent_runs.append(run) + if year < this_year: + # Assume no run lasts for more than a year, + # e.g. if it's Jan 2018, some run that started in 2017 may + # be included, but don't even look through runs from 2016 + # or earlier. + break + recent_runs.sort(key=lambda r: r.start_date, reverse=True) + try: return render_template( 'course.html', @@ -124,6 +145,7 @@ def lesson_url(lesson, *args, **kwargs): plan=course.sessions, title=course.title, lesson_url=lesson_url, + recent_runs=recent_runs, **vars_functions(course.vars), edit_path=course.edit_path) except TemplateNotFound: diff --git a/naucse/static/css/nausce.css b/naucse/static/css/nausce.css index 29a3d47d69..c03f610f08 100644 --- a/naucse/static/css/nausce.css +++ b/naucse/static/css/nausce.css @@ -114,6 +114,10 @@ a:hover { margin-bottom: 1rem; } +.course-card .recent-runs li { + margin-top: 1em; +} + /*************************/ pre { diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 50bab5751b..09948c7943 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -28,45 +28,16 @@
- {% if course.start_date %} -
-
- {% if course.vars['coach-present'] %} - Kurz s instruktorem - {% else %} - Termín kurzu - {% endif %} -
-
-
- {{ (course.start_date, course.end_date) | format_date_range }} -
-
- -
- {% endif %} - -

{{ course.title }}

+
+
+

{{ course.title }}

- {% if course.subtitle is defined and course.subtitle != None %} -

{{ course.subtitle }}

- {% endif%} + {% if course.subtitle is defined and course.subtitle != None %} +

{{ course.subtitle }}

+ {% endif%} - {{ course.long_description | markdown }} + {{ course.long_description | markdown }} -
-
{% for session in plan.values() %}

@@ -85,8 +56,88 @@

{% endfor %}

{% endfor %} + +
+
+ {% if course.start_date %} +
+
+ Toto jsou podklady pro kurz s instruktorem +
+
+
+ {{ (course.start_date, course.end_date) | format_date_range }} +
+
+ +
+ {% else %} +
+
+ Toto jsou materiály pro samouky +
+
+

+ Doufáme, že naše materiály jsou srozumitelné a přínosné. + Pokud ne, ozvěte se prosím: +

+ +

+ Případné nejasnosti rádi vysvětlíme, + ale musíme o nich vědět! +

+
+ {% if recent_runs %} + + {% endif %} +
+ {% endif %}
+
From aa3785203eb75d20a8c7770e0facfc998b38b527 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 29 Oct 2017 21:08:32 +0100 Subject: [PATCH 08/12] Nicer styling for calendars --- naucse/static/css/calendar.css | 19 ++++++++++++++----- naucse/templates/course_calendar.html | 3 +-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/naucse/static/css/calendar.css b/naucse/static/css/calendar.css index 2879726354..2467050f41 100644 --- a/naucse/static/css/calendar.css +++ b/naucse/static/css/calendar.css @@ -1,12 +1,15 @@ -.calendar-card .card-header .year { - color: #868e96; +.calendar-heading { + margin-top: 2em; + margin-bottom: 1em; + text-align: center; } table.calendar { border-spacing: 0.25em; border-collapse: separate; - display: block; margin: 0.5em auto; + margin-left: auto; + margin-right: auto; } table.calendar tr { @@ -16,7 +19,6 @@ table.calendar tr { table.calendar td, table.calendar th { width: 14.28%; - min-width: 5em; height: 100%; background-color: rgba(0,0,0,.03); vertical-align: center; @@ -55,9 +57,16 @@ table.calendar .session-number { table.calendar .session-title { display: block; - width: 4em; text-align: center; text-overflow: '…'; white-space: nowrap; + width: 4em; overflow: hidden; } + +@media (min-width: 768px) { + table.calendar td, + table.calendar th { + min-width: 5em; + } +} diff --git a/naucse/templates/course_calendar.html b/naucse/templates/course_calendar.html index 0b629763dd..b7b3fee5fe 100644 --- a/naucse/templates/course_calendar.html +++ b/naucse/templates/course_calendar.html @@ -31,7 +31,7 @@

{{ course.subtitle }}

{% if course.start_date %} {% for year, month in months %} -

+

{{ month | monthname }} {{ year }}

@@ -63,7 +63,6 @@

{% endif %} {% if date.weekday() == 6 %}{% endif %} {% endfor %} -

{% endfor %} {% else %} From b7c3db14ec485a052c6d3f6e6769175886851fea Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 13 Nov 2017 14:52:38 +0100 Subject: [PATCH 09/12] Adjust positioning of the course info card - Move the card below text on "medium" screens -- it was too cramped there - When the card is below text, add a margin before it --- naucse/static/css/nausce.css | 9 +++++++++ naucse/templates/course.html | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/naucse/static/css/nausce.css b/naucse/static/css/nausce.css index c03f610f08..d89a3c50ff 100644 --- a/naucse/static/css/nausce.css +++ b/naucse/static/css/nausce.css @@ -114,6 +114,15 @@ a:hover { margin-bottom: 1rem; } +.course-card { + margin-top: 3em; +} +@media (min-width: 992px) { + .course-card { + margin-top: 0; + } +} + .course-card .recent-runs li { margin-top: 1em; } diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 09948c7943..93a6b75c5c 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -29,7 +29,7 @@
-
+

{{ course.title }}

{% if course.subtitle is defined and course.subtitle != None %} @@ -58,7 +58,7 @@

{% endfor %}

-
+
{% if course.start_date %}
From 90f08204be77c337938df60d989ddf923e452572 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 13 Nov 2017 15:05:49 +0100 Subject: [PATCH 10/12] routes: Move generating calendar months out into a new function This makes it - testable - easily refactored if needed - more readable for a reader of this route --- naucse/routes.py | 26 ++++++++++++++++++-------- test_naucse/test_routes_utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 test_naucse/test_routes_utils.py diff --git a/naucse/routes.py b/naucse/routes.py index 6c19fbebad..aa16b45201 100644 --- a/naucse/routes.py +++ b/naucse/routes.py @@ -289,23 +289,33 @@ def session_url(session): link_section=link_section, cheatsheet_section=cheatsheet_section) -@app.route('//calendar/') -def course_calendar(course): - if not course.start_date: - abort(404) + +def list_months(start_date, end_date): + """Return a span of months as a list of (year, month) tuples + + The months of start_date and end_date are both included. + """ months = [] - year = course.start_date.year - month = course.start_date.month - while (year, month) <= (course.end_date.year, course.end_date.month): + year = start_date.year + month = start_date.month + while (year, month) <= (end_date.year, end_date.month): months.append((year, month)) month += 1 if month > 12: month = 1 year += 1 + return months + + +@app.route('//calendar/') +def course_calendar(course): + if not course.start_date: + abort(404) sessions_by_date = {s.date: s for s in course.sessions.values()} return render_template('course_calendar.html', edit_path=course.edit_path, course=course, sessions_by_date=sessions_by_date, - months=months, + months=list_months(course.start_date, + course.end_date), calendar=calendar.Calendar()) diff --git a/test_naucse/test_routes_utils.py b/test_naucse/test_routes_utils.py new file mode 100644 index 0000000000..fee03cc2e9 --- /dev/null +++ b/test_naucse/test_routes_utils.py @@ -0,0 +1,25 @@ +import datetime + +import pytest + +import naucse.routes + + +@pytest.mark.parametrize( + ['start', 'end', 'expected'], + [ + (datetime.date(2016, 12, 3), datetime.date(2016, 12, 3), + [(2016, 12)]), + (datetime.date(2016, 12, 3), datetime.date(2016, 12, 23), + [(2016, 12)]), + (datetime.date(2016, 12, 3), datetime.date(2017, 1, 3), + [(2016, 12), (2017, 1)]), + (datetime.date(2016, 12, 3), datetime.date(2017, 5, 8), + [(2016, 12), (2017, 1), (2017, 2), (2017, 3), (2017, 4), (2017, 5)]), + (datetime.date(2016, 12, 3), datetime.date(2018, 1, 8), + [(2016, 12), (2017, 1), (2017, 2), (2017, 3), (2017, 4), (2017, 5), + (2017, 6), (2017, 7), (2017, 8), (2017, 9), (2017, 10), (2017, 11), + (2017, 12), (2018, 1)]), + ]) +def test_list_months(start, end, expected): + assert naucse.routes.list_months(start, end) == expected From d2fdef110788531a229d60b9043d8b5cee1e951b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 13 Nov 2017 15:16:38 +0100 Subject: [PATCH 11/12] routes: Move generating recent runs out into a new function --- naucse/routes.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/naucse/routes.py b/naucse/routes.py index aa16b45201..3b5d89b14f 100644 --- a/naucse/routes.py +++ b/naucse/routes.py @@ -112,17 +112,15 @@ def lesson_static(lesson, path): return send_from_directory(directory, filename) -@app.route('//') -def course(course): - def lesson_url(lesson, *args, **kwargs): - return url_for('course_page', course=course, lesson=lesson, *args, **kwargs) +def get_recent_runs(course): + """Build a list of "recent" runs based on a course. + By recent we mean: haven't ended yet, or ended up to ~2 months ago + (Note: even if naucse is hosted dynamically, + it's still beneficial to show recently ended runs.) + """ recent_runs = [] if not course.start_date: - # Build a list of "recent" runs based on this course. - # By recent we mean: haven't ended yet, or ended up to ~2 months ago - # (Note: even if naucse is hosted dynamically, - # it's still beneficial to show recently ended runs.) today = datetime.date.today() cutoff = today - datetime.timedelta(days=2*30) this_year = today.year @@ -136,7 +134,14 @@ def lesson_url(lesson, *args, **kwargs): # be included, but don't even look through runs from 2016 # or earlier. break - recent_runs.sort(key=lambda r: r.start_date, reverse=True) + recent_runs.sort(key=lambda r: r.start_date, reverse=True) + return recent_runs + + +@app.route('//') +def course(course): + def lesson_url(lesson, *args, **kwargs): + return url_for('course_page', course=course, lesson=lesson, *args, **kwargs) try: return render_template( @@ -145,7 +150,7 @@ def lesson_url(lesson, *args, **kwargs): plan=course.sessions, title=course.title, lesson_url=lesson_url, - recent_runs=recent_runs, + recent_runs=get_recent_runs(course), **vars_functions(course.vars), edit_path=course.edit_path) except TemplateNotFound: From 5147d58b6918fee6089f7caa526f34ced40d0b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 14 Nov 2017 12:38:06 +0100 Subject: [PATCH 12/12] Typo in infobox --- naucse/templates/course.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 93a6b75c5c..46dd602f9a 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -114,7 +114,7 @@

{% if recent_runs %}