Skip to content

Commit cc9fd78

Browse files
committed
🔄 Templates: Migrate from Mako to Flask's native Jinja2 engine
Replace the unmaintained `flask-mako` package (last updated 2015, incompatible with Flask 3.x) with Jinja2, Flask's built-in template engine. This eliminates two dependencies and removes the last blocker for running on modern Flask. - Convert all 37 `.mak` templates to `.html` Jinja2 equivalents - `master.html`: `<%def>` blocks → `{% block %}`, `${self.body()}` → `{% block body %}` - `blogs.html`: Inline Python moved to view, `loop.index` adjusted for Jinja2's 1-based indexing, `${var|h}` → `{{ var|e }}` - `syllabus.html`: block overrides, unused macro removed - Update `site.py`, `blueprints.py`, `participants.py` to use Flask's native `render_template` - Add `name=` to duplicate blueprint registrations (Flask 3.x) - Update `freeze.py` to use `(endpoint, values)` tuples for namespaced blueprint endpoints - Remove `pyproject.toml` dependencies on `Mako` and `flask-mako` - Delete all 37 old `.mak` template files Assisted-by: Claude Opus 4.6 (1M context) Signed-off-by: Justin Wheeler <git@jwheel.org>
1 parent 769e182 commit cc9fd78

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+433
-379
lines changed

‎freeze.py‎

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,69 +18,60 @@
1818

1919
@freezer.register_generator
2020
def simple_page():
21-
"""Generate URLs for all top-level .mak templates."""
21+
"""Generate URLs for all top-level .html templates."""
2222
templates_dir = os.path.join(base_dir, 'templates')
2323
for fname in os.listdir(templates_dir):
24-
if fname.endswith('.mak') and fname not in ('master.mak', 'ohno.mak',
25-
'participant.mak',
26-
'blogs.mak'):
27-
yield {'page': fname.replace('.mak', '')}
24+
if fname.endswith('.html') and fname not in ('master.html', 'ohno.html',
25+
'participant.html',
26+
'blogs.html'):
27+
yield {'page': fname.replace('.html', '')}
2828

2929

30-
def _hw_pages():
31-
"""Generate page values for homework templates."""
30+
@freezer.register_generator
31+
def blueprint_pages():
32+
"""Generate URLs for all blueprint-served pages.
33+
34+
Yields (endpoint, values) tuples for namespaced blueprint endpoints.
35+
Flask 3.x requires unique names for duplicate blueprint registrations,
36+
so endpoints are namespaced as '<name>.<view_function>'.
37+
"""
3238
hw_dir = os.path.join(base_dir, 'templates', 'hw')
3339
if os.path.isdir(hw_dir):
34-
yield {'page': 'index'}
35-
for fname in os.listdir(hw_dir):
36-
if fname.endswith('.mak') and fname != 'index.mak':
37-
yield {'page': fname.replace('.mak', '')}
40+
pages = ['index']
41+
pages.extend(f.replace('.html', '') for f in os.listdir(hw_dir)
42+
if f.endswith('.html') and f != 'index.html')
43+
for page in pages:
44+
yield 'hw.display_homework', {'page': page}
45+
yield 'assignments.display_homework', {'page': page}
3846

39-
40-
def _lecture_pages():
41-
"""Generate page values for lecture templates."""
4247
lectures_dir = os.path.join(base_dir, 'templates', 'lectures')
4348
if os.path.isdir(lectures_dir):
44-
yield {'page': 'index'}
45-
for fname in os.listdir(lectures_dir):
46-
if fname.endswith('.mak') and fname != 'index.mak':
47-
yield {'page': fname.replace('.mak', '')}
48-
49+
pages = ['index']
50+
pages.extend(f.replace('.html', '') for f in os.listdir(lectures_dir)
51+
if f.endswith('.html') and f != 'index.html')
52+
for page in pages:
53+
yield 'lectures.display_lecture', {'page': page}
4954

50-
def _quiz_pages():
51-
"""Generate quiz_num values for quiz templates."""
5255
quiz_dir = os.path.join(base_dir, 'templates', 'quiz')
5356
if os.path.isdir(quiz_dir):
5457
for fname in os.listdir(quiz_dir):
55-
if fname.endswith('.mak'):
56-
yield {'quiz_num': fname.replace('.mak', '')}
58+
if fname.endswith('.html'):
59+
quiz_num = fname.replace('.html', '')
60+
yield 'quizzes.show_quiz', {'quiz_num': quiz_num}
61+
yield 'quiz.show_quiz', {'quiz_num': quiz_num}
5762

58-
59-
def _participant_pages():
60-
"""Generate URLs for all participant profile pages."""
6163
people_dir = os.path.join('scripts', 'people')
6264
for dirpath, dirnames, files in os.walk(people_dir):
6365
for fname in files:
6466
if fname.endswith('.yaml'):
6567
parts = dirpath.split(os.sep)
6668
if len(parts) >= 4:
67-
year = parts[-2]
68-
term = parts[-1]
69-
username = fname.replace('.yaml', '')
70-
yield {'year': year, 'term': term, 'username': username}
71-
72-
73-
# Register generators for each named blueprint registration.
74-
# Flask 3.x requires unique names for duplicate blueprint registrations,
75-
# so endpoints are namespaced as '<name>.<view_function>'.
76-
freezer.register_generator(lambda: _hw_pages(), 'assignments.display_homework')
77-
freezer.register_generator(lambda: _hw_pages(), 'hw.display_homework')
78-
freezer.register_generator(lambda: _lecture_pages(), 'lectures.display_lecture')
79-
freezer.register_generator(lambda: _quiz_pages(), 'quizzes.show_quiz')
80-
freezer.register_generator(lambda: _quiz_pages(), 'quiz.show_quiz')
81-
freezer.register_generator(lambda: _participant_pages(), 'participants.participant_page')
82-
freezer.register_generator(lambda: _participant_pages(), 'blogs.participant_page')
83-
freezer.register_generator(lambda: _participant_pages(), 'checkblogs.participant_page')
69+
values = {
70+
'year': parts[-2],
71+
'term': parts[-1],
72+
'username': fname.replace('.yaml', ''),
73+
}
74+
yield 'participant_page', values
8475

8576

8677
if __name__ == '__main__':

‎hflossk/blueprints.py‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import os
22

3-
from flask import Blueprint
4-
from flask_mako import render_template
3+
from flask import Blueprint, render_template
54

65
homework = Blueprint('homework', __name__, template_folder='templates')
76
lectures = Blueprint('lectures', __name__, template_folder='templates')
@@ -16,11 +15,11 @@ def display_homework(page):
1615
'static', 'hw'))
1716
hws.extend(os.listdir(os.path.join(os.path.split(__file__)[0],
1817
'templates', 'hw')))
19-
hws = [hw for hw in sorted(hws) if not hw == "index.mak"]
18+
hws = [hw for hw in sorted(hws) if not hw == "index.html"]
2019
else:
2120
hws = None
2221

23-
return render_template('hw/{}.mak'.format(page), name='mako', hws=hws)
22+
return render_template('hw/{}.html'.format(page), hws=hws)
2423

2524

2625
@lectures.route('/', defaults={'page': 'index'})
@@ -30,14 +29,14 @@ def display_lecture(page):
3029
lecture_notes = os.listdir(os.path.join(os.path.split(__file__)[0],
3130
'templates', 'lectures'))
3231
lecture_notes = [note for note in sorted(lecture_notes)
33-
if not note == "index.mak"]
32+
if not note == "index.html"]
3433
else:
3534
lecture_notes = None
3635

37-
return render_template('lectures/{}.mak'.format(page), name='mako',
36+
return render_template('lectures/{}.html'.format(page),
3837
lectures=lecture_notes)
3938

4039

4140
@quizzes.route('/<quiz_num>')
4241
def show_quiz(quiz_num):
43-
return render_template('quiz/{}.mak'.format(quiz_num), name='mako')
42+
return render_template('quiz/{}.html'.format(quiz_num))

‎hflossk/participants.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
import hflossk
55
import yaml
6-
from flask import Blueprint
7-
from flask_mako import render_template
6+
from flask import Blueprint, render_template
87

98

109
participants_bp = Blueprint('participants_bp',
@@ -85,6 +84,9 @@ def participants(root_dir):
8584
)
8685
contents['isActive'] = (currentYear in year_term_data
8786
and currentTerm in year_term_data)
87+
# Ensure hw dict exists for template iteration
88+
if 'hw' not in contents:
89+
contents['hw'] = {}
8890

8991
student_data.append(contents)
9092

@@ -97,10 +99,8 @@ def participants(root_dir):
9799
len(assignments))
98100

99101
return render_template(
100-
'blogs.mak', name='mako',
102+
'blogs.html',
101103
student_data=student_data,
102104
gravatar=hflossk.site.gravatar,
103105
target_number=target_number
104106
)
105-
106-
#

‎hflossk/site.py‎

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
from datetime import datetime
1212

1313
import yaml
14-
from flask import Flask, jsonify
15-
from flask_mako import MakoTemplates, render_template
14+
from flask import Flask, jsonify, render_template
1615
from werkzeug.exceptions import NotFound
1716

1817
from hflossk.blueprints import homework, lectures, quizzes
@@ -21,7 +20,6 @@
2120

2221
app = Flask(__name__)
2322
app.template_folder = "templates"
24-
mako = MakoTemplates(app)
2523
base_dir = os.path.split(__file__)[0]
2624

2725

@@ -32,7 +30,6 @@ def inject_yaml():
3230
site_config = yaml.safe_load(site_yaml)
3331
return site_config
3432

35-
app.config['MAKO_TRANSLATE_EXCEPTIONS'] = False
3633
config = inject_yaml()
3734
COURSE_START = datetime.combine(config['course']['start'], datetime.min.time())
3835
COURSE_END = datetime.combine(config['course']['end'], datetime.max.time())
@@ -59,13 +56,13 @@ def gravatar(email):
5956
@app.route('/<page>')
6057
def simple_page(page):
6158
"""
62-
Render a simple page. Looks for a .mak template file
59+
Render a simple page. Looks for a .html template file
6360
with the name of the page parameter that was passed in.
6461
By default, this just shows the homepage.
6562
6663
"""
6764

68-
return render_template('{}.mak'.format(page), name='mako')
65+
return render_template('{}.html'.format(page))
6966

7067

7168
@app.route('/static/manifest.webapp')
@@ -86,7 +83,7 @@ def syllabus():
8683

8784
with open(os.path.join(base_dir, 'schedule.yaml')) as schedule_yaml:
8885
schedule = yaml.safe_load(schedule_yaml)
89-
return render_template('syllabus.mak', schedule=schedule, name='mako')
86+
return render_template('syllabus.html', schedule=schedule)
9087

9188

9289
@app.route('/blog/<username>')
@@ -125,7 +122,7 @@ def participant_page(year, term, username):
125122
year, term, username + '.yaml'))
126123
with open(person_yaml) as participant_file:
127124
return render_template(
128-
'participant.mak', name='make',
125+
'participant.html',
129126
participant_data=yaml.safe_load(participant_file),
130127
gravatar=gravatar
131128
)
@@ -142,14 +139,14 @@ def resources():
142139
res['Videos'] = os.listdir(os.path.join(
143140
base_dir, 'static', 'videos'))
144141

145-
return render_template('resources.mak', name='mako', resources=res)
142+
return render_template('resources.html', resources=res)
146143

147144

148-
app.register_blueprint(homework, url_prefix='/assignments')
149-
app.register_blueprint(homework, url_prefix='/hw')
145+
app.register_blueprint(homework, url_prefix='/assignments', name='assignments')
146+
app.register_blueprint(homework, url_prefix='/hw', name='hw')
150147
app.register_blueprint(lectures, url_prefix='/lectures')
151-
app.register_blueprint(quizzes, url_prefix='/quizzes')
152-
app.register_blueprint(quizzes, url_prefix='/quiz')
153-
app.register_blueprint(participants_bp, url_prefix='/participants')
154-
app.register_blueprint(participants_bp, url_prefix='/blogs')
155-
app.register_blueprint(participants_bp, url_prefix='/checkblogs')
148+
app.register_blueprint(quizzes, url_prefix='/quizzes', name='quizzes')
149+
app.register_blueprint(quizzes, url_prefix='/quiz', name='quiz')
150+
app.register_blueprint(participants_bp, url_prefix='/participants', name='participants')
151+
app.register_blueprint(participants_bp, url_prefix='/blogs', name='blogs')
152+
app.register_blueprint(participants_bp, url_prefix='/checkblogs', name='checkblogs')
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
<%inherit file="master.mak"/>
1+
{% extends "master.html" %}
2+
3+
{% block body %}
24

35
<div class="jumbotron">
46
<h1>Hello, world!</h1>
@@ -25,3 +27,4 @@ <h2>Mako</h2>
2527
<p><a class="btn" target="_blank" href="http://www.makotemplates.org/">View details &raquo;</a></p>
2628
</div><!--/span-->
2729
</div><!--/row-->
30+
{% endblock %}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{% extends "master.html" %}
2+
3+
{% block body %}
4+
<head>
5+
<script src="/static/js/pace.min.js"></script>
6+
<script src="/static/js/jquery.js"></script>
7+
<link href="/static/css/pace.css" rel="stylesheet" />
8+
<script>
9+
10+
/**
11+
* getPostCount
12+
*
13+
*
14+
*/
15+
function getPostCount(username) {
16+
var target = {{ target_number | int }};
17+
$.ajax({
18+
url: "/blog/" + username,
19+
method: "GET",
20+
dataType: "json",
21+
success: function(data) {
22+
var count = data['number'];
23+
$('span#' + username).text(count);
24+
if (count >= target) {
25+
$('span#' + username).addClass('label-success');
26+
} else if (count >= target * 0.8) {
27+
$('span#' + username).addClass('label-warning');
28+
} else {
29+
$('span#' + username).addClass('label-danger');
30+
}
31+
}
32+
});
33+
}
34+
35+
$(document).ready(function() {
36+
$.each($("div.student"), function(index, elem) {
37+
var username = $(elem).data('student');
38+
getPostCount(username);
39+
});
40+
});
41+
42+
</script>
43+
</head>
44+
45+
<div class="jumbotron">
46+
<h1>Participants</h1>
47+
<p>Should have {{ target_number | int }} blog post(s)</p>
48+
<!--<p><a href="#" class="btn btn-primary btn-lg">Generate Report&raquo;</a></p>-->
49+
</div>
50+
51+
<div class="row">
52+
{% for student in student_data %}
53+
<div class="col-sm-4">
54+
<div class="student shadowcard padded" data-student="{{ student['irc'] | e }}">
55+
<div>
56+
<img class="uglymug pull-left" src="{{ gravatar(student.get('avatar', student['rit_dce'] + '@rit.edu')) }}" alt="{{ student['irc'] | e }}'s Avatar!" />
57+
<h4 class="item"><a href="{{ student['participant_page'] | e }}">{{ student['irc'] | e }}</a></h4>
58+
<div class="item blog clearfix">
59+
<a target="_blank" href="{{ student['blog'] | e }}">Blog</a>
60+
<span class="label" id="{{ student['irc'] | e }}"></span>
61+
</div>
62+
</div>
63+
<ul class="cardlist list-unstyled">
64+
{% for forge_link in student['forges'] %}
65+
<li><a target="_blank" href="{{ forge_link | e }}">{{ forge_link | e }}</a></li>
66+
{% endfor %}
67+
68+
{% if student['isActive'] %}
69+
{% set keys = ['quiz1', 'litreview1', 'bugfix', 'teamprop1', 'meetup1', 'commarchreport', 'commarchpreso', 'meetup2', 'curriculum', 'smoketest', 'vidreview1', 'vidreview2', 'teamprop2', 'litreview2', 'meetup3', 'quiz2', 'finalpreso'] %}
70+
{% for key in keys %}
71+
{% if key in student['hw'] %}
72+
<li><a target="_blank" href="{{ student['hw'][key] | e }}">{{ key }}</a></li>
73+
{% else %}
74+
<li class="redtext">{{ key }}?</li>
75+
{% endif %}
76+
{% endfor %}
77+
{% else %}
78+
{% for key in student['hw'] %}
79+
<li><a target="_blank" href="{{ student['hw'][key] | e }}">{{ key }}</a></li>
80+
{% endfor %}
81+
{% endif %}
82+
</ul>
83+
</div>
84+
</div><!--/span-->
85+
{% if loop.index % 3 == 0 %}
86+
</div>
87+
<div class="row">
88+
{% endif %}
89+
{% endfor %}
90+
</div><!--/row-->
91+
{% endblock %}

0 commit comments

Comments
 (0)