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..3b5d89b14f 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 @@ -111,6 +112,32 @@ def lesson_static(lesson, path): return send_from_directory(directory, filename) +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: + 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) + return recent_runs + + @app.route('//') def course(course): def lesson_url(lesson, *args, **kwargs): @@ -123,6 +150,7 @@ def lesson_url(lesson, *args, **kwargs): plan=course.sessions, title=course.title, lesson_url=lesson_url, + recent_runs=get_recent_runs(course), **vars_functions(course.vars), edit_path=course.edit_path) except TemplateNotFound: @@ -265,3 +293,34 @@ def session_url(session): homework_section=homework_section, link_section=link_section, cheatsheet_section=cheatsheet_section) + + +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 = 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=list_months(course.start_date, + course.end_date), + calendar=calendar.Calendar()) diff --git a/naucse/static/css/calendar.css b/naucse/static/css/calendar.css new file mode 100644 index 0000000000..2467050f41 --- /dev/null +++ b/naucse/static/css/calendar.css @@ -0,0 +1,72 @@ +.calendar-heading { + margin-top: 2em; + margin-bottom: 1em; + text-align: center; +} + +table.calendar { + border-spacing: 0.25em; + border-collapse: separate; + margin: 0.5em auto; + margin-left: auto; + margin-right: auto; +} + +table.calendar tr { + height: 4em; +} + +table.calendar td, +table.calendar th { + width: 14.28%; + 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; + 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/static/css/nausce.css b/naucse/static/css/nausce.css index 29a3d47d69..d89a3c50ff 100644 --- a/naucse/static/css/nausce.css +++ b/naucse/static/css/nausce.css @@ -114,6 +114,19 @@ 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; +} + /*************************/ pre { diff --git a/naucse/templates.py b/naucse/templates.py index ca08c6a761..e3da3272dd 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,10 @@ 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) + +@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/_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/_lessons_list.html b/naucse/templates/_lessons_list.html deleted file mode 100644 index 700833df25..0000000000 --- a/naucse/templates/_lessons_list.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "_base.html" %} - -{% block content %} - -{% macro session_heading(session, index, plan) %} - {% if plan|length > 1 %} - Lekce {{ index }} - - {% endif %} - {{ session.title }} - {% if session.date %} - ({{ session.date }}) - {% endif %} -{% endmacro %} - -
-
- - {% block headline %} - {% endblock headline %} - -
-
- {% 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 content %} 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


diff --git a/naucse/templates/course.html b/naucse/templates/course.html index 730be737d2..46dd602f9a 100644 --- a/naucse/templates/course.html +++ b/naucse/templates/course.html @@ -1,35 +1,144 @@ -{% extends '_lessons_list.html' %} - -{% block headline %} - -
- Nauč se Python - - {% if course.vars['coach-present'] %} - > Kurzy - {% else %} - > Materiály - {% endif%} - - > {{ 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 %} -
+{% extends "_base.html" %} + +{% macro session_heading(session, index, plan) %} + {% if plan|length > 1 %} + Lekce {{ index }} - + {% endif %} + {{ session.title }} + {% if session.date %} + ({{ session.date | format_date }}) + {% endif %} +{% endmacro %} + +{% block content %} + +
+
+ +
+ Nauč se Python + + {% if course.vars['coach-present'] %} + > Kurzy + {% else %} + > Materiály + {% endif%} -

{{ course.title }}

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

{{ course.subtitle }}

- {% endif%} +
+
+

{{ course.title }}

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

{{ course.subtitle }}

+ {% endif%} + + {{ 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 %} + +
+
+ {% 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 %} +
+
- {{ course.long_description | markdown }} +
+
-{% endblock headline %} +{% endblock content %} diff --git a/naucse/templates/course_calendar.html b/naucse/templates/course_calendar.html new file mode 100644 index 0000000000..b7b3fee5fe --- /dev/null +++ b/naucse/templates/course_calendar.html @@ -0,0 +1,75 @@ +{% extends '_base.html' %} + +{% block extra_links %} + +{% endblock extra_links%} + +{% block content %} + +
+
+ + +
+ Nauč se Python + + {% if course.vars['coach-present'] %} + > Kurzy + {% else %} + > Materiály + {% endif%} + > {{ course.title }} + > Kalendář +
+
+ +

{{ course.title }}

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

{{ course.subtitle }}

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

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

+ + {% for date in calendar.itermonthdates(year, month) %} + {% if date.weekday() == 0 %}{% endif %} + {% if date.month == month %} + {% set session = sessions_by_date.get(date, None) %} + {% if session %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% if date.weekday() == 6 %}{% endif %} + {% endfor %} +
+ + + {{ date.day }}. {{ date.month }}. + + + {{ session.title }} + + + + {{ date.day }} + +   +
+ {% endfor %} + {% else %} + Tento kurz nemá kalendář. + {% endif %} + +
+
+ +{% endblock content %} 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

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 %}
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