Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add course info cards & calendars for runs #259

Merged
merged 12 commits into from
Nov 14, 2017
8 changes: 5 additions & 3 deletions naucse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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())
Expand Down
22 changes: 22 additions & 0 deletions naucse/routes.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -265,3 +266,24 @@ def session_url(session):
homework_section=homework_section,
link_section=link_section,
cheatsheet_section=cheatsheet_section)

@app.route('/<course:course>/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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering, if there possibly isn't already a function, that does this?

>>> tuple((d.year, d.month) for d in dateutil.rrule.rrule(dateutil.rrule.MONTHLY, dtstart=datetime.date(year=2017, month=1, day=1), until=datetime.date(year=2018, month=2, day=1)))
((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), (2018, 2))

Now I don't know if this would be any better, faster or more readable (probably not). However when dealing with dates, I'd probably prefer to stick with the date type. (I know, those are in fact months...)

Anyway: please make it a function (regardless of the implementation). That would make it:

  • testable
  • easily refactored if needed
  • more readable for a reader of this route (if you pick a descriptive name, maybe months_range(start_date, end_date))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (year, month) tuples are a natural representation here. Datetime objects don't offer much functionality for using them as months.
I went with list_months; I didn't use “range” as the end date is included.

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())
63 changes: 63 additions & 0 deletions naucse/static/css/calendar.css
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 11 additions & 1 deletion naucse/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason not to use the standard library? I don't mind hardocding this here, since it's not very likely the month names will change any time soon. However using LC_TIME will make easier to switch the entire website to let's say Slovak, if we ever want to do that.

>>> import locale
>>> import calendar
>>> locale.setlocale(locale.LC_TIME, 'cs_CZ.utf-8')
'cs_CZ.utf-8'
>>> calendar.month_name[3].title()
'Březen'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a reasonable thing to say. But I don't trust locales.

In practice I found locales frequently solve either too much or too litle. See for example naucse's function for formatting date ranges – building that on top of a locale "format a date for me" function would probably turn out quite fragile, so anyway you end up with some things based on locale and some hard-coded, which means some explicit and some implicit, and you anyway need to take each language into account separately, and... it's just a mess.

Also, it's one more dependency: cs_CZ needs to be available on the target system. How do I ensure that? How do I test it? And, setlocale is not thread-safe. Using it would rule out the possibility of running the Czech and Slovak version on one (threaded) server.

The flip side is that the site can only be translated by programmers (or a team that has a programmer). And I am fine with that, at least for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. As I said, I was just curious.

return ('Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec',
'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec')[number-1]
8 changes: 4 additions & 4 deletions naucse/templates/_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% endif %}
</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">

{% if page is defined and page.latex %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1/katex.min.css" integrity="sha384-wITovz90syo1dJWVh32uuETPVEtGigN07tkttEqPv+uR2SE/mbQcG7ATL28aI9H0" crossorigin="anonymous">
Expand Down Expand Up @@ -76,9 +76,9 @@
</div>
</div>

<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>

{% if page is defined and page.latex %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1/katex.min.js" integrity="sha384-/y1Nn9+QQAipbNQWU65krzJralCnuOasHncUFXGkdwntGeSvQicrYkiUBwsgUqc1" crossorigin="anonymous"></script>
Expand Down
2 changes: 1 addition & 1 deletion naucse/templates/_lessons_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{% endif %}
{{ session.title }}
{% if session.date %}
({{ session.date }})
({{ session.date | format_date }})
{% endif %}
{% endmacro %}

Expand Down
6 changes: 3 additions & 3 deletions naucse/templates/backpage.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,16 @@ <h2>Zajímavé odkazy</h2>
<hr class="lesson-end">
<div class="row prev-next">
<div class="col text-left">
<a href="{{ session_url(course.slug, session.slug) }}">← <span class="hidden-xs-down">Lekce: {{ session.title }}</span></a>
<a href="{{ session_url(course.slug, session.slug) }}">← <span class="d-none d-sm-block">Lekce: {{ session.title }}</span></a>
</div>

<div class="col text-left">
<a href="{{ course_url(course) }}">↑ <span class="hidden-xs-down">{{ course.title }}</span></a>
<a href="{{ course_url(course) }}">↑ <span class="d-none d-sm-block">{{ course.title }}</span></a>
</div>

<div class="col text-right">
{% if session.next is defined and session.next != None %}
<a href="{{ session_url(course.slug, session.next.slug) }}"><span class="hidden-xs-down">Lekce: {{ session.next.title }}</span> →</a>
<a href="{{ session_url(course.slug, session.next.slug) }}"><span class="d-none d-sm-block">Lekce: {{ session.next.title }}</span> →</a>
{% endif %}
</div>
</div>
Expand Down
36 changes: 28 additions & 8 deletions naucse/templates/course.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,36 @@

> {{ course.title }}
<hr>

{% if not course.canonical %}
<div class="alert alert-info">
Toto je kurz, který probíhá nebo proběhl naživo s instruktorem.
{% if course.base_course %}
<a href="{{ course_url(course.base_course) }}">Přejít na ekvivalentní kurz pro samouky</a>.
</header>

{% if course.start_date %}
<div class="course-dates card w-lg-25 float-lg-right text-center">
<div class="card-header">
{% if course.vars['coach-present'] %}
Kurz s instruktorem
{% else %}
Termín kurzu
{% endif %}
</div>
{% endif %}
</header>
<div class="card-body">
<div class="calendar-dates">
{{ (course.start_date, course.end_date) | format_date_range }}
</div>
</div>
<div class="card-footer small text-left">
<ul class="list-unstyled" style="margin-bottom:0;">
<li>
<a href="{{ url_for('course_calendar', course=course) }}">→ Kalendář</a>
</li>
{% if course.base_course %}
<li>
<a href="{{ course_url(course.base_course) }}">→ Ekvivalentní kurz pro samouky</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}

<h1>{{ course.title }}</h1>

Expand Down
76 changes: 76 additions & 0 deletions naucse/templates/course_calendar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% extends '_base.html' %}

{% block extra_links %}
<link rel="stylesheet" href="{{url_for('static', filename='css/calendar.css')}}">
{% endblock extra_links%}

{% block content %}

<div class="page">
<section class="container">


<header class="lesson-header">
<a href="{{ url_for('index') }}">Nauč se Python </a>

{% if course.vars['coach-present'] %}
> <a href="{{ url_for('runs') }}">Kurzy</a>
{% else %}
> <a href="{{ url_for('courses') }}">Materiály</a>
{% endif%}
> <a href="{{ course_url(course) }}">{{ course.title }}</a>
> Kalendář
<hr>
</header>

<h1>{{ course.title }}</h1>

{% if course.subtitle is defined and course.subtitle != None %}
<h2>{{ course.subtitle }}</h2>
{% endif%}

{% if course.start_date %}
{% for year, month in months %}
<h3>
{{ month | monthname }} <span class="year">{{ year }}</span>
</h3>
<table class="calendar">
{% for date in calendar.itermonthdates(year, month) %}
{% if date.weekday() == 0 %}<tr>{% endif %}
{% if date.month == month %}
{% set session = sessions_by_date.get(date, None) %}
{% if session %}
<td class="event">
<a href="{{ url_for('session_coverpage', course=course, session=session.slug) }}"
title="#{{ session.index+1 }} – {{ session.title }}">
<span class="session-number">
{{ date.day }}. {{ date.month }}.
</span>
<span class="session-title">
{{ session.title }}
</span>
</a>
</td>
{% else %}
<td class="no-event">
{{ date.day }}
</td>
{% endif %}
{% else %}
<td class="foreign-month">
&nbsp;
</td>
{% endif %}
{% if date.weekday() == 6 %}</tr>{% endif %}
{% endfor %}
</tr>
</table>
{% endfor %}
{% else %}
Tento kurz nemá kalendář.
{% endif %}

</section>
</div>

{% endblock content %}
6 changes: 3 additions & 3 deletions naucse/templates/coverpage.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ <h2>Taháky</h2>
<div class="row prev-next">
<div class="col text-left">
{% if session.prev is defined and session.prev != None %}
<a href="{{ session_url(course.slug, session.prev.slug) }}">← <span class="hidden-xs-down">Lekce: {{ session.prev.title }}</span></a>
<a href="{{ session_url(course.slug, session.prev.slug) }}">← <span class="d-none d-sm-block">Lekce: {{ session.prev.title }}</span></a>
{% endif %}
</div>

<div class="col text-left">
<a href="{{ course_url(course) }}">↑ <span class="hidden-xs-down">{{ course.title }}</span></a>
<a href="{{ course_url(course) }}">↑ <span class="d-none d-sm-block">{{ course.title }}</span></a>
</div>

<div class="col text-right">
<a href="{{ session_url(course.slug, session.slug, 'back') }}"><span class="hidden-xs-down">Závěr lekce</span> →</a>
<a href="{{ session_url(course.slug, session.slug, 'back') }}"><span class="d-none d-sm-block">Závěr lekce</span> →</a>
</div>
</div>

Expand Down
8 changes: 4 additions & 4 deletions naucse/templates/lesson.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@
<div class="row prev-next">
<div class="col text-left">
{% if prv is defined and prv != None %}
<a href="{{ lesson_url(lesson=prv.page.lesson, page=prv.page.slug) }}">← <span class="hidden-xs-down">{{ prv.title }}</span></a>
<a href="{{ lesson_url(lesson=prv.page.lesson, page=prv.page.slug) }}">← <span class="d-none d-sm-block">{{ prv.title }}</span></a>
{% endif %}
</div>

<div class="col text-center">
{% if session is defined and session != None and course is defined %}
<a href="{{ session_url(course.slug, session.slug) }}">↑ <span class="hidden-xs-down">Lekce: {{ session.title }}</span></a>
<a href="{{ session_url(course.slug, session.slug) }}">↑ <span class="d-none d-sm-block">Lekce: {{ session.title }}</span></a>
{% endif %}
</div>

<div class="col text-right">
{% if nxt is defined and nxt != None %}
<a href="{{ lesson_url(lesson=nxt.page.lesson, page=nxt.page.slug) }}"><span class="hidden-xs-down">{{ nxt.title }}</span> →</a>
<a href="{{ lesson_url(lesson=nxt.page.lesson, page=nxt.page.slug) }}"><span class="d-none d-sm-block">{{ nxt.title }}</span> →</a>
{% elif session is defined and session != None and course is defined %}
<a href="{{ session_url(course.slug, session.slug, 'back') }}"><span class="hidden-xs-down">Závěr lekce</span> →</a>
<a href="{{ session_url(course.slug, session.slug, 'back') }}"><span class="d-none d-sm-block">Závěr lekce</span> →</a>
{% endif %}
</div>
</div>
Expand Down