From 0724816c88d9a4621bab540c379ad8f38798e5e1 Mon Sep 17 00:00:00 2001 From: coja Date: Sat, 2 May 2026 23:18:51 +0200 Subject: [PATCH] [Jinja2] init --- Makefile | 5 +- build_pages.py | 70 +++++---- prep.py | 252 +++++++++++++++++++------------- requirements.txt | 1 + site/deconference.html | 48 ++---- template/base.html | 65 ++++++++ template/events-en.html | 13 +- template/events-sr.html | 12 +- template/events_archive-en.html | 12 +- template/events_archive-sr.html | 12 +- template/page-en.html | 95 +----------- template/page-sr.html | 96 +----------- todo.md | 21 +++ 13 files changed, 350 insertions(+), 352 deletions(-) create mode 100644 template/base.html create mode 100644 todo.md diff --git a/Makefile b/Makefile index 153c6d7..aaaa913 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ help: @echo "Available commands:" - @echo " make prep - Create venv and install requirements" + @echo " make build - Full website build sequence" @echo " make events - Update site from CSV (build pages + images)" @echo " make dev - Start development server" @echo " make stop - Stop development server" - @echo " make build - Full website build sequence" + @echo " make prep - Create venv and install requirements" @echo " make help - Show this help message" prep: @@ -28,3 +28,4 @@ dev: stop: nginx -p . -s stop + diff --git a/build_pages.py b/build_pages.py index 8098634..4c02069 100755 --- a/build_pages.py +++ b/build_pages.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python3 +from jinja2 import Environment, FileSystemLoader import os PAGES = [ @@ -14,34 +14,52 @@ PAGES = [ {'name': 'deconference', 'titleSR': 'Dekonferencija', 'titleEN': 'Deconference', 'style': 'deconference'}, ] -def buildPage(filename: str, pageTitle: str, pageHtml: str, pageStyle: str, template: str) -> str: - template = template.replace('', pageTitle) - style = '' if not pageStyle else f'' - template = template.replace('', style) - template = template.replace('PAGE_NAME', filename) - template = template.replace('', pageHtml) - return template +env = Environment(loader=FileSystemLoader('template')) def main(): os.makedirs('site/en/', exist_ok=True) - with open('template/page-en.html') as fTempEN, open('template/page-sr.html') as fTempSR: - templateSR = fTempSR.read() - templateEN = fTempEN.read() - for page in PAGES: - with open(f'pages/sr/{page["name"]}.html') as f: - pageHtml = "
Studenti su nasli bug
" - pageHtml += f.read() - html = buildPage(page['name'], page['titleSR'], pageHtml, page['style'], templateSR) - f = open(f'site/{page["name"]}.html', 'w') - f.write(html) - f.close() - with open(f'pages/en/{page["name"]}.html') as f: - pageHtml = "
Students found the bug
" - pageHtml += f.read() - html = buildPage(page['name'], page['titleEN'], pageHtml, page['style'], templateEN) - f = open(f'site/en/{page["name"]}.html', 'w') - f.write(html) - f.close() + for page in PAGES: + # Build SR Page + with open(f'pages/sr/{page["name"]}.html') as f: + page_content = "
Studenti su nasli bug' if page['style'] else '', + lang="sr", + sr_link=f"/en/{page['name']}" + ) + + sr_filename = "index.html" if page['name'] == 'index' else f"{page['name']}.html" + with open(f'site/{sr_filename}', 'w') as f: + f.write(sr_html) + + # Build EN Page + with open(f'pages/en/{page["name"]}.html') as f: + page_content = "
Students found the bug' if page['style'] else '', + lang="en", + sr_link=f"/{page['name']}" + ) + + en_filename = "index.html" if page['name'] == 'index' else f"{page['name']}.html" + with open(f'site/en/{en_filename}', 'w') as f: + f.write(en_html) if __name__ == '__main__': main() + + + + + + + + diff --git a/prep.py b/prep.py index 2171a92..5de939c 100755 --- a/prep.py +++ b/prep.py @@ -1,7 +1,7 @@ -#! /usr/bin/env python3 - +from jinja2 import Environment, FileSystemLoader import csv from datetime import datetime +import os DAYS_SR = ["PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED"] DAYS_EN = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"] @@ -18,6 +18,8 @@ TYPES_DICT = { "party": ("zabava", "entertainment"), } +env = Environment(loader=FileSystemLoader('template')) + def load_events(csv_path:str) -> list[dict]: events = [] with open(csv_path, encoding='utf-8') as csv_file: @@ -39,51 +41,11 @@ def load_events(csv_path:str) -> list[dict]: events.append(current_event) return events - -def build_html(events: list[dict], dayNames: list[str], typesNames: dict) -> str: - events_html = [] - for event in events: - title = event["title"] - location = event["location"] - date = event["date"] - date = dayNames[date.weekday()]+", "+str(date.day)+". "+str(date.month)+". "+str(date.year)+", " - time = event["time"]+"h" - event_html = [] - event_html.append(f"
{date} {time}
") - if event["link"] != "": - event_html.append(f"") - else: - event_html.append(f"
{title}
") - if "https://" in location: - place,link = location.split("https://") - event_html.append(f"") - else: - event_html.append(f"
@{location.strip()}
") - - if len(event["types"]) != 0: - types_list = "
" - last_item = event["types"][-1] - for t in event["types"]: - if typesNames.get(t) is not None: - types_list += typesNames.get(t) - if t != last_item: - types_list += ', ' - else: - print(f"Unknown type {t}!") - types_list += "
" - event_html.append(types_list) - - event_html = "".join(event_html) - events_html.append(f"\n
{event_html}
") - return events_html - def build_ical(events: list[dict]) -> str: today = datetime.today().now() - # Header events_ical = "" with open("template/head.ical", "r") as file: events_ical += file.read() - # Events for event in events: title = event["title"] location = event["location"] @@ -92,85 +54,165 @@ def build_ical(events: list[dict]) -> str: url = event["link"] uid = str(date.month).zfill(2) + str(date.day).zfill(2) + time[:2] - date = str(date.year) + str(date.month).zfill(2) + str(date.day).zfill(2) + date_str = str(date.year) + str(date.month).zfill(2) + str(date.day).zfill(2) created = str(today.year) + str(today.month).zfill(2) + str(today.day).zfill(2) + "T" + str(today.hour).zfill(2) + str(today.minute).zfill(2) + str(today.second).zfill(2) + "Z" - date = date + "T" + time.replace(":", "") + "00" + date_str = date_str + "T" + time.replace(":", "") + "00" - event_template = "" - with open("template/event.ical", "r") as file: - event_template += file.read() - event_template = event_template.replace("", uid) - event_template = event_template.replace("", created) - event_template = event_template.replace("", date) - event_template = event_template.replace("", title) - event_template = event_template.replace("", url) - if location.startswith("DC Krov"): - event_template = event_template.replace("", "DC Krov\\, Kraljice Marije 47\\, 6\\, Beograd\\, Serbia") - elif location.startswith("Matematički fakultet (Učionica 153)"): - event_template = event_template.replace("", "Matematički fakultet\\, Svetog Nikole 39\\, Beograd\\, Serbia") - else: - event_template = event_template.replace("", location) + event_template_str = env.get_template("event.ical").render( + UID=uid, + CREATED=created, + DATE=date_str, + TITLE=title, + URL=url, + LOCATION="DC Krov\\, Kraljice Marije 47\\, 6\\, Beograd\\, Serbia" if location.startswith("DC Krov") else ("Matematički fakultet\\, Svetog Nikole 39\\, Beograd\\, Serbia" if location.startswith("Matematički fakultet (Učionica 153)") else location) + ) + events_ical += event_template_str - - events_ical += event_template - # Footer with open("template/end.ical", "r") as file: events_ical += file.read() return events_ical -events = sorted(load_events("dogadjaji.csv"), key=lambda e: e["date"]) +def render_page(template_name, output_path, context): + template = env.get_template(template_name) + with open(output_path, "w") as file: + file.write(template.render(context)) +# Main execution +events = sorted(load_events("dogadjaji.csv"), key=lambda e: e["date"]) today = datetime.today().date() -past_events = list(filter(lambda e: e["date"] <= today, events)) -past_events.reverse() -new_events = list(filter(lambda e: e["date"] >= today, events)) +past_events = sorted([e for e in events if e["date"] <= today], key=lambda e: e["date"], reverse=True) +new_events = [e for e in events if e["date"] >= today] +sr_types = {k: v[0] for k, v in TYPES_DICT.items()} +en_types = {k: v[1] for k, v in TYPES_DICT.items()} -page_template = "" +# Build Serbian Pages +render_page("events-sr.html", "pages/sr/events.html", { + "lang": "sr", + "title": "Događaji", + "sr_link": "/events_archive", + "events_html": env.from_string(""" + {% for event in events %} +
+
{{ event.date.strftime('%a, %d. %b. %Y') }}, {{ event.time }}h
+ {% if event.link %} + + {% else %} +
{{ event.title }}
+ {% endif %} + {% if 'https://' in event.location %} + {% set place, link = event.location.split('https://') %} + + {% else %} +
@{{ event.location.strip() }}
+ {% endif %} + {% if event.types %} +
+ {% for t in event.types %} + {{ types_names.get(t, t) }}{% if not loop.last %}, {% endif %} + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + """).render(events=new_events, types_names=sr_types) +}) -sr_types = {} -en_types = {} +render_page("events-en.html", "pages/en/events.html", { + "lang": "en", + "title": "Events", + "sr_link": "/events_archive", + "events_html": env.from_string(""" + {% for event in events %} +
+
{{ event.date.strftime('%a, %d. %b. %Y') }}, {{ event.time }}h
+ {% if event.link %} + + {% else %} +
{{ event.title }}
+ {% endif %} + {% if 'https://' in event.location %} + {% set place, link = event.location.split('https://') %} + + {% else %} +
@{{ event.location.strip() }}
+ {% endif %} + {% if event.types %} +
+ {% for t in event.types %} + {{ types_names.get(t, t) }}{% if not loop.last %}, {% endif %} + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + """).render(events=new_events, types_names=en_types) +}) -for key, value_pair in TYPES_DICT.items(): - sr_types[key] = value_pair[0] - en_types[key] = value_pair[1] +# Build Archive Pages +render_page("events_archive-sr.html", "pages/sr/events_archive.html", { + "lang": "sr", + "title": "Arhiva događaja", + "sr_link": "/events", + "events_html": env.from_string(""" + {% for event in events %} +
+
{{ event.date.strftime('%a, %d. %b. %Y') }}, {{ event.time }}h
+ {% if event.link %} + + {% else %} +
{{ event.title }}
+ {% endif %} + {% if 'https://' in event.location %} + {% set place, link = event.location.split('https://') %} + + {% else %} +
@{{ event.location.strip() }}
+ {% endif %} + {% if event.types %} +
+ {% for t in event.types %} + {{ types_names.get(t, t) }}{% if not loop.last %}, {% endif %} + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + """).render(events=past_events, types_names=sr_types) +}) -# Build Serbian Events page -new_events_html = build_html(new_events, DAYS_SR, sr_types) -with open("template/events-sr.html", "r") as file: - page_template = ([line for line in file]) - -with open("pages/sr/events.html", "w") as file: - file.writelines(page_template + new_events_html) - -# Build English Events page -new_events_html = build_html(new_events, DAYS_EN, en_types) -with open("template/events-en.html", "r") as file: - page_template = ([line for line in file]) - -with open("pages/en/events.html", "w") as file: - file.writelines(page_template + new_events_html) - -# Build Serbian Archive page -past_events_html = build_html(past_events, DAYS_SR, sr_types) -with open("template/events_archive-sr.html", "r") as file: - page_template = ([line for line in file]) - -with open("pages/sr/events_archive.html", "w") as file: - file.writelines(page_template + past_events_html) - -# Build English Archive page -past_events_html = build_html(past_events, DAYS_EN, en_types) -with open("template/events_archive-en.html", "r") as file: - page_template = ([line for line in file]) - -with open("pages/en/events_archive.html", "w") as file: - file.writelines(page_template + past_events_html) - -new_events_ical = build_ical(new_events) +render_page("events_archive-en.html", "pages/en/events_archive.html", { + "lang": "en", + "title": "Events archive", + "sr_link": "/en/events", + "events_html": env.from_string(""" + {% for event in events %} +
+
{{ event.date.strftime('%a, %d. %b. %Y') }}, {{ event.time }}h
+ {% if event.link %} + + {% else %} +
{{ event.title }}
+ {% endif %} + {% if 'https://' in event.location %} + {% set place, link = event.location.split('https://') %} + + {% else %} +
@{{ event.location.strip() }}
+ {% endif %} + {% if event.types %} +
+ {% for t in event.types %} + {{ types_names.get(t, t) }}{% if not loop.last %}, {% endif %} + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + """).render(events=past_events, types_names=en_types) +}) # Build ical with open("site/events.ical", "w") as file: file.write(build_ical(new_events)) - diff --git a/requirements.txt b/requirements.txt index d8cb4b8..e1ee0f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ freetype-py python-dateutil feedgen pillow +jinja2 diff --git a/site/deconference.html b/site/deconference.html index 0de7e7c..2174b35 100644 --- a/site/deconference.html +++ b/site/deconference.html @@ -14,50 +14,26 @@ - - - + Dekonferencija Decentrala - +
- - EN + SR
-
-
Studenti su nasli bug

Dekonferencija

+ +
+
Studenti su nasli bugDekonferencija

Program

11:00 Otvaranje

@@ -93,18 +69,20 @@

Decentrala prihvata donacije isključivo od fizičkih lica.

-
+
+
- + \ No newline at end of file diff --git a/template/base.html b/template/base.html new file mode 100644 index 0000000..be1279d --- /dev/null +++ b/template/base.html @@ -0,0 +1,65 @@ + + + + + + + + + + {% block extra_styles %}{% endblock %} + + + {{ title }} Decentrala + + + +
+ + + SR +
+
+ {% block content %}{% endblock %} +
+ + + diff --git a/template/events-en.html b/template/events-en.html index 5e2bce4..985843c 100644 --- a/template/events-en.html +++ b/template/events-en.html @@ -1,15 +1,17 @@ -

Events

+{% block title %}Events{% endblock %} + +{% block content %} +

Events

Following list contains all forthcoming events. Held events are listed in - archive. + archive.

Events are also available as an ical file.

-

We also provide ical file

Short description @@ -17,3 +19,8 @@ how to find a space

+ +
+ {{ events_html|safe }} +
+{% endblock %} diff --git a/template/events-sr.html b/template/events-sr.html index f5633d1..b1c1571 100644 --- a/template/events-sr.html +++ b/template/events-sr.html @@ -1,9 +1,12 @@ -

Događaji

+{% block title %}Događaji{% endblock %} + +{% block content %} +

Događaji

Naredna lista sadrži sve predstojeće događaje. Za listu održanih događaja - pogledaj arhivu. + pogledaj arhivu.

Događaje možeš učitati i sa @@ -16,3 +19,8 @@ kako pronaci prostor

+ +
+ {{ events_html|safe }} +
+{% endblock %} diff --git a/template/events_archive-en.html b/template/events_archive-en.html index 2a8ca0d..8e6a22e 100644 --- a/template/events_archive-en.html +++ b/template/events_archive-en.html @@ -1,6 +1,14 @@ -

Events archive

+{% block title %}Events archive{% endblock %} + +{% block content %} +

Events archive

All events that we organized so far. You can find future events on - Events page + Events page


+ +
+ {{ events_html|safe }} +
+{% endblock %} diff --git a/template/events_archive-sr.html b/template/events_archive-sr.html index 5b696bd..5d8d6bc 100644 --- a/template/events_archive-sr.html +++ b/template/events_archive-sr.html @@ -1,6 +1,14 @@ -

Arhiva događaja

+{% block title %}Arhiva događaja{% endblock %} + +{% block content %} +

Arhiva događaja

Svi događaji koje smo do sada organzivali. Predstojeće događaje možeš naći - ovde + ovde


+ +
+ {{ events_html|safe }} +
+{% endblock %} diff --git a/template/page-en.html b/template/page-en.html index b213d7f..a043481 100644 --- a/template/page-en.html +++ b/template/page-en.html @@ -1,90 +1,11 @@ - - - - - - +{% extends "base.html" %} - - - - - - - - <!--TITLE--> Decentrala - - - -
- - - SR -
-
-
- -
-
- - - +{% block content %} +
+ {{ content|safe }} +
+{% endblock %} diff --git a/template/page-sr.html b/template/page-sr.html index 0290182..a043481 100644 --- a/template/page-sr.html +++ b/template/page-sr.html @@ -1,91 +1,11 @@ - - - - - - +{% extends "base.html" %} - - - - - - - - <!--TITLE--> Decentrala - - - -
- - - EN -
-
-
- -
-
- - - +{% block content %} +
+ {{ content|safe }} +
+{% endblock %} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..3abc1ae --- /dev/null +++ b/todo.md @@ -0,0 +1,21 @@ +1. High Impact: Reliability & Security +* Adopt a Templating Engine (e.g., Jinja2): + * Problem: The current build scripts (build_pages.py, prep.py) use manual string replacement (e.g., .replace('', ...)). This is brittle and prone to errors as the site grows. + * Benefit: Using Jinja2 would make templates much more powerful (loops, conditionals) and, crucially, would prevent XSS vulnerabilities by automatically escaping data from your CSV files. + +* Use csv.DictReader in Python: + * Problem: prep.py accesses CSV columns by index (e.g., row[0]). If you add a column to dogadjaji.csv, the build will break. + * Benefit: Accessing by name (row['title']) makes the code resilient to data schema changes. + +2. Medium Impact: Developer Experience (DX) +* Code Linting: + * Benefit: Adding black or flake8 for Python and prettier for HTML/CSS would ensure consistent style across the repository. + +3. Low Impact: Performance & Modernization +* CSS Consolidation: + * Problem: There are many small CSS files (one per page). + * Benefit: While fine for a small site, consolidating these or using a preprocessor like Sass would make managing global styles easier. + +* Asset Optimization: + * Benefit: Implementing automated image compression (for the event posters) would reduce the final site payload. +Which of these areas would you like me to focus on first? I recommend starting with the Jinja2 migration as it solves both maintainability and security issues.