other changes
This commit is contained in:
parent
e9b10767d6
commit
20e2b0e3d7
298
content.py
298
content.py
|
|
@ -1,4 +1,4 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
#saved_titles = json.loads( codecs.open('cache/saved_youtube_titles.json','r','utf-8').read() )
|
#saved_titles = json.loads( codecs.open('cache/saved_youtube_titles.json','r','utf-8').read() )
|
||||||
|
|
||||||
|
|
@ -1235,6 +1235,299 @@ def download_web():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def flowgrid():
|
||||||
|
# a tiny DSL for lane/step "flow grid" diagrams rendered to HTML.
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional, Dict
|
||||||
|
import re
|
||||||
|
import html
|
||||||
|
|
||||||
|
# ---------------------- Data model ----------------------
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Step:
|
||||||
|
code: str
|
||||||
|
label: str
|
||||||
|
weeks: Optional[str] = None
|
||||||
|
hours: Optional[str] = None
|
||||||
|
tag: Optional[str] = None # 'req' | 'rec' | None
|
||||||
|
desc: Optional[str] = None # override/extra text for subline
|
||||||
|
klass: Optional[str] = None # additional css class
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Lane:
|
||||||
|
name: str
|
||||||
|
steps: List[Step] = field(default_factory=list)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Doc:
|
||||||
|
title: str
|
||||||
|
lanes: List[Lane] = field(default_factory=list)
|
||||||
|
css_vars: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
# ---------------------- Parser ----------------------
|
||||||
|
|
||||||
|
def parse_spec(text: str) -> Doc:
|
||||||
|
"""
|
||||||
|
DSL syntax:
|
||||||
|
- Comments start with '#'
|
||||||
|
- KEY: value (supported keys: TITLE, VAR)
|
||||||
|
VAR: --step=260px; --arrow=34px; --done=110px (semicolon separated; optional)
|
||||||
|
- LANE: <name>
|
||||||
|
STEP: CODE | LABEL | weeks=2; hours=20; tag=req
|
||||||
|
STEP: CODE | LABEL | desc=1 hour on-site; tag=rec
|
||||||
|
- Empty lines are ignored.
|
||||||
|
- Indentation is optional and only for readability.
|
||||||
|
"""
|
||||||
|
title = "Untitled Diagram"
|
||||||
|
lanes: List[Lane] = []
|
||||||
|
current_lane: Optional[Lane] = None
|
||||||
|
css_vars: Dict[str, str] = {}
|
||||||
|
|
||||||
|
for raw in text.splitlines():
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# KEY: value
|
||||||
|
m = re.match(r'(?i)TITLE\s*:\s*(.+)$', line)
|
||||||
|
if m:
|
||||||
|
title = m.group(1).strip()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# VAR line
|
||||||
|
m = re.match(r'(?i)VAR\s*:\s*(.+)$', line)
|
||||||
|
if m:
|
||||||
|
# semicolon separated k=v; allow CSS custom props like --step=300px
|
||||||
|
blob = m.group(1)
|
||||||
|
parts = [p.strip() for p in blob.split(";") if p.strip()]
|
||||||
|
for p in parts:
|
||||||
|
if "=" in p:
|
||||||
|
k, v = p.split("=", 1)
|
||||||
|
css_vars[k.strip()] = v.strip()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# LANE
|
||||||
|
m = re.match(r'(?i)LANE\s*:\s*(.+)$', line)
|
||||||
|
if m:
|
||||||
|
current_lane = Lane(name=m.group(1).strip())
|
||||||
|
lanes.append(current_lane)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# STEP
|
||||||
|
m = re.match(r'(?i)STEP\s*:\s*(.+)$', line)
|
||||||
|
if m:
|
||||||
|
if current_lane is None:
|
||||||
|
raise ValueError("STEP appears before any LANE is defined.")
|
||||||
|
body = m.group(1)
|
||||||
|
# Expect: CODE | LABEL | attrs
|
||||||
|
parts = [p.strip() for p in body.split("|")]
|
||||||
|
if len(parts) < 2:
|
||||||
|
raise ValueError(f"STEP needs 'CODE | LABEL | ...' got: {body}")
|
||||||
|
code = parts[0]
|
||||||
|
label = parts[1]
|
||||||
|
attrs_blob = parts[2] if len(parts) >=3 else ""
|
||||||
|
|
||||||
|
# Parse attrs: key=value; key=value
|
||||||
|
step_kwargs = {}
|
||||||
|
if attrs_blob:
|
||||||
|
for kv in [a.strip() for a in attrs_blob.split(";") if a.strip()]:
|
||||||
|
if "=" in kv:
|
||||||
|
k, v = kv.split("=", 1)
|
||||||
|
step_kwargs[k.strip().lower()] = v.strip()
|
||||||
|
else:
|
||||||
|
# allow bare tag 'req' or 'rec'
|
||||||
|
if kv.lower() in ("req", "rec"):
|
||||||
|
step_kwargs["tag"] = kv.lower()
|
||||||
|
|
||||||
|
step = Step(
|
||||||
|
code=code,
|
||||||
|
label=label,
|
||||||
|
weeks=step_kwargs.get("weeks") or step_kwargs.get("w"),
|
||||||
|
hours=step_kwargs.get("hours") or step_kwargs.get("hrs") or step_kwargs.get("h"),
|
||||||
|
tag=normalize_tag(step_kwargs.get("tag")),
|
||||||
|
desc=step_kwargs.get("desc"),
|
||||||
|
klass=step_kwargs.get("class") or step_kwargs.get("klass"),
|
||||||
|
)
|
||||||
|
current_lane.steps.append(step)
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise ValueError(f"Unrecognized line: {line}")
|
||||||
|
|
||||||
|
return Doc(title=title, lanes=lanes, css_vars=css_vars)
|
||||||
|
|
||||||
|
def normalize_tag(tag: Optional[str]) -> Optional[str]:
|
||||||
|
if not tag:
|
||||||
|
return None
|
||||||
|
t = tag.lower().strip()
|
||||||
|
if t in ("req", "required"):
|
||||||
|
return "req"
|
||||||
|
if t in ("rec", "recommended"):
|
||||||
|
return "rec"
|
||||||
|
if t in ("none", "na", "n/a", "optional"):
|
||||||
|
return None
|
||||||
|
return t
|
||||||
|
|
||||||
|
# ---------------------- HTML rendering ----------------------
|
||||||
|
|
||||||
|
BASE_CSS = r"""
|
||||||
|
:root{
|
||||||
|
--ink:#0f172a;
|
||||||
|
--reqBorder:#2e7d32; --reqFill:#eef7ef;
|
||||||
|
--recBorder:#8a8a8a; --recFill:#ffffff;
|
||||||
|
--doneBorder:#9ca3af; --doneInk:#475569;
|
||||||
|
--modeCol:180px; --gap:12px;
|
||||||
|
--step:260px; --arrow:34px; --done:110px;
|
||||||
|
}
|
||||||
|
html,body{margin:0;background:#f6f7fb;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--ink)}
|
||||||
|
.wrap{margin:24px auto 48px;padding:0 16px}
|
||||||
|
h1{font-size:22px;margin:0 0 8px}
|
||||||
|
.legend{display:flex;gap:18px;align-items:center;font-size:14px;margin:6px 0 24px}
|
||||||
|
.tag{display:inline-block;padding:2px 8px;border-radius:999px;border:1.5px solid var(--reqBorder);background:var(--reqFill);font-size:12px}
|
||||||
|
.tag.rec{border-color:var(--recBorder);background:var(--recFill);border-style:dashed}
|
||||||
|
.grid{display:flex;flex-direction:column;gap:18px}
|
||||||
|
.lane{display:grid;grid-template-columns:var(--modeCol) 1fr;gap:var(--gap);align-items:center;background:#ffffffcc;padding:12px;border-radius:12px}
|
||||||
|
.mode{font-weight:700;text-align:center;background:#fff;padding:16px 10px}
|
||||||
|
.flow{display:grid;align-items:center;gap:8px;padding:8px 0;}
|
||||||
|
.header {grid-column: span 4; }
|
||||||
|
.step{border-radius:10px;padding:10px 12px;border:2px solid var(--reqBorder);background:var(--reqFill);min-height:64px}
|
||||||
|
.step .title{font-weight:700}
|
||||||
|
.step .sub{font-size:12px;opacity:.8}
|
||||||
|
.step.rec{border-color:var(--recBorder);border-style:dashed;background:var(--recFill)}
|
||||||
|
.slot{}
|
||||||
|
.arrow{font-size:22px;line-height:1;text-align:center}
|
||||||
|
.arrow.blank{color:transparent}
|
||||||
|
.done{justify-self:start;border-radius:999px;border:2px dashed var(--doneBorder);padding:10px 14px;color:var(--doneInk);background:#fff;text-align:center}
|
||||||
|
@media (max-width:900px){
|
||||||
|
.lane{grid-template-columns:1fr}
|
||||||
|
.mode{order:-1}
|
||||||
|
.flow{grid-template-columns:1fr; background:none}
|
||||||
|
.arrow{display:none}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def format_sub(step: Step) -> str:
|
||||||
|
if step.desc:
|
||||||
|
core = html.escape(step.desc)
|
||||||
|
else:
|
||||||
|
bits = [html.escape(step.label)]
|
||||||
|
wh = []
|
||||||
|
if step.weeks:
|
||||||
|
wh.append(f"{html.escape(str(step.weeks))} weeks")
|
||||||
|
if step.hours:
|
||||||
|
wh.append(f"~{html.escape(str(step.hours))} hrs")
|
||||||
|
if wh:
|
||||||
|
bits.append(" · " + " (".join([wh[0], " ".join(wh[1:])]) + ")" if len(wh)>1 else " · " + wh[0])
|
||||||
|
# Actually, the original used " · 2 weeks (~20 hrs)"
|
||||||
|
# Let's just do that directly:
|
||||||
|
if step.weeks and step.hours:
|
||||||
|
bits[-1] = f" · {html.escape(str(step.weeks))} weeks (~{html.escape(str(step.hours))} hrs)"
|
||||||
|
# Combine
|
||||||
|
core = "".join(bits)
|
||||||
|
# Tag
|
||||||
|
if step.tag == "req":
|
||||||
|
tag_html = '<span class="tag">Required</span>'
|
||||||
|
elif step.tag == "rec":
|
||||||
|
tag_html = '<span class="tag rec">Recommended</span>'
|
||||||
|
else:
|
||||||
|
tag_html = ""
|
||||||
|
if tag_html:
|
||||||
|
return f'{core} · {tag_html}'
|
||||||
|
return core
|
||||||
|
|
||||||
|
def render_html(doc: Doc) -> str:
|
||||||
|
max_steps = max((len(l.steps) for l in doc.lanes), default=1)
|
||||||
|
# grid-template-columns: repeat(max_steps, var(--step) var(--arrow)) var(--done)
|
||||||
|
pairs = " ".join(["var(--step) var(--arrow)"] * max_steps) + " var(--done)"
|
||||||
|
css_vars_block = ""
|
||||||
|
if doc.css_vars:
|
||||||
|
css_vars_block = ":root{\n" + "\n".join([f" {k}: {v};" for k,v in doc.css_vars.items()]) + "\n}\n"
|
||||||
|
|
||||||
|
html_parts = []
|
||||||
|
html_parts.append("<!DOCTYPE html><html><head><meta charset='utf-8'>")
|
||||||
|
html_parts.append("<meta name='viewport' content='width=device-width, initial-scale=1'>")
|
||||||
|
html_parts.append("<title>" + html.escape(doc.title) + "</title>")
|
||||||
|
html_parts.append("<style>")
|
||||||
|
html_parts.append(BASE_CSS)
|
||||||
|
if css_vars_block:
|
||||||
|
html_parts.append(css_vars_block)
|
||||||
|
html_parts.append(f".flow{{grid-template-columns:{pairs};}}")
|
||||||
|
html_parts.append("</style></head><body>")
|
||||||
|
html_parts.append("<div class='wrap'><div class='grid'>")
|
||||||
|
|
||||||
|
# Header/Title lane
|
||||||
|
html_parts.append("<div class='lane'><div class='mode'> </div><div class='flow'><div class='header'><h1>")
|
||||||
|
html_parts.append(html.escape(doc.title))
|
||||||
|
html_parts.append("</h1></div></div></div>")
|
||||||
|
|
||||||
|
for lane in doc.lanes:
|
||||||
|
html_parts.append("<div class='lane'>")
|
||||||
|
html_parts.append(f"<div class='mode'>{html.escape(lane.name)}</div>")
|
||||||
|
html_parts.append("<div class='flow'>")
|
||||||
|
for idx, step in enumerate(lane.steps):
|
||||||
|
cls = "step"
|
||||||
|
if step.tag == "rec":
|
||||||
|
cls += " rec"
|
||||||
|
if step.klass:
|
||||||
|
cls += " " + html.escape(step.klass)
|
||||||
|
html_parts.append(f"<div class='{cls}'>")
|
||||||
|
html_parts.append(f"<div class='title'>{html.escape(step.code)}</div>")
|
||||||
|
html_parts.append(f"<div class='sub'>{format_sub(step)}</div>")
|
||||||
|
html_parts.append("</div>") # step
|
||||||
|
# arrow after every step unless it's the last visible step
|
||||||
|
html_parts.append("<div class='arrow'>→</div>")
|
||||||
|
# Fill remaining slots (if any)
|
||||||
|
for _ in range(max_steps - len(lane.steps)):
|
||||||
|
html_parts.append("<div class='slot'></div>")
|
||||||
|
html_parts.append("<div class='arrow blank'>→</div>")
|
||||||
|
|
||||||
|
# Done bubble
|
||||||
|
html_parts.append("<div class='done'>Done</div>")
|
||||||
|
html_parts.append("</div></div>") # flow + lane
|
||||||
|
|
||||||
|
html_parts.append("</div></div></body></html>")
|
||||||
|
return "".join(html_parts)
|
||||||
|
|
||||||
|
|
||||||
|
spec_text = '''
|
||||||
|
TITLE: Online Teaching Requirements and Recommendations
|
||||||
|
# Optional CSS overrides
|
||||||
|
VAR: --step=180px; --modeCol=180px
|
||||||
|
|
||||||
|
LANE: In Person (with Canvas)
|
||||||
|
STEP: GOTT 1 | Intro to Online Teaching with Canvas | weeks=2; hours=20; tag=rec
|
||||||
|
|
||||||
|
LANE: Online
|
||||||
|
STEP: GOTT 1 | Intro to Online Teaching with Canvas | weeks=2; hours=20; tag=req
|
||||||
|
STEP: GOTT 2 | Introduction to Asynchronous Online Teaching and Learning | weeks=4; hours=40; tag=req
|
||||||
|
|
||||||
|
LANE: Hybrid
|
||||||
|
STEP: GOTT 1 | Intro to Online Teaching with Canvas | weeks=2; hours=20; tag=req
|
||||||
|
STEP: GOTT 2 | Introduction to Asynchronous Online Teaching and Learning | weeks=4; hours=40; tag=req
|
||||||
|
STEP: GOTT 5 | Essentials of Blended Learning | weeks=2; hours=20; tag=rec
|
||||||
|
|
||||||
|
LANE: Online Live
|
||||||
|
STEP: GOTT 1 | Intro to Online Teaching with Canvas | weeks=2; hours=20; tag=req
|
||||||
|
STEP: GOTT 2 | Introduction to Asynchronous Online Teaching and Learning | weeks=4; hours=40; tag=req
|
||||||
|
STEP: GOTT 6 | Introduction to Live Online Teaching and Learning | weeks=2; hours=20; tag=rec
|
||||||
|
|
||||||
|
LANE: HyFlex
|
||||||
|
STEP: GOTT 1 | Intro to Online Teaching with Canvas | weeks=2; hours=20; tag=req
|
||||||
|
STEP: GOTT 2 | Introduction to Asynchronous Online Teaching and Learning | weeks=4; hours=40; tag=req
|
||||||
|
STEP: GOTT 6 | Introduction to Live Online Teaching and Learning | weeks=2; hours=20; tag=rec
|
||||||
|
# You can override the subline using desc=
|
||||||
|
STEP: HyFlex Tech Training | ~1 hour on-site | desc=~1 hour on-site; tag=rec
|
||||||
|
|
||||||
|
'''
|
||||||
|
doc = parse_spec(spec_text)
|
||||||
|
out_html = render_html(doc)
|
||||||
|
Path('cache/flow.html').write_text(out_html, encoding="utf-8")
|
||||||
|
print(f"Wrote cache/flow.html")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1256,7 +1549,8 @@ if __name__ == "__main__":
|
||||||
20: ['create support page', create_support_page],
|
20: ['create support page', create_support_page],
|
||||||
21: ['add support page to all shells in semester', add_support_page_full_semester],
|
21: ['add support page to all shells in semester', add_support_page_full_semester],
|
||||||
22: ['fetch all modules / items', check_modules_for_old_orientation],
|
22: ['fetch all modules / items', check_modules_for_old_orientation],
|
||||||
30: ['media fetch', media_testing]
|
30: ['media fetch', media_testing],
|
||||||
|
40: ['flow grid', flowgrid],
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]):
|
if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]):
|
||||||
|
|
|
||||||
4
tasks.py
4
tasks.py
|
|
@ -183,8 +183,8 @@ def convert_to_pdf(name1, name2):
|
||||||
|
|
||||||
# Build (docx/pdf) certificates for gott graduates
|
# Build (docx/pdf) certificates for gott graduates
|
||||||
def certificates_gott_build():
|
def certificates_gott_build():
|
||||||
course = "gott_1_su25"
|
course = "gott_1_fa25"
|
||||||
coursedate = "Summer 2025"
|
coursedate = "Fall 2025"
|
||||||
certificate = "gott 1 template.docx"
|
certificate = "gott 1 template.docx"
|
||||||
|
|
||||||
#course = "gott_4_su25"
|
#course = "gott_4_su25"
|
||||||
|
|
|
||||||
8
users.py
8
users.py
|
|
@ -2234,19 +2234,21 @@ def training_find_goos():
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def cross_ref_training():
|
def cross_ref_training():
|
||||||
|
from semesters import find_term
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.chart import BarChart, Series, Reference
|
from openpyxl.chart import BarChart, Series, Reference
|
||||||
from openpyxl.styles import PatternFill, Border, Side, Alignment, Protection, Font, Fill
|
from openpyxl.styles import PatternFill, Border, Side, Alignment, Protection, Font, Fill
|
||||||
wb = load_workbook("C:/Users/phowell/Downloads/GOTT_Completion_masterlist 2023 DEC.xlsx")
|
wb = load_workbook("C:/Users/phowell/Downloads/GOTT_Completion_masterlist 2023 DEC.xlsx")
|
||||||
print(wb.sheetnames)
|
print(wb.sheetnames)
|
||||||
|
|
||||||
term = "202570"
|
term = find_term("fa25")
|
||||||
|
|
||||||
# Fetch from Canvas DB. Make sure its recently updated.
|
# Fetch from Canvas DB. Make sure its recently updated.
|
||||||
# Also relies on schedule being in database. Run localcache2.courses_to_sched()
|
# Also relies on schedule being in database. Run localcache2.courses_to_sched()
|
||||||
# OR localcache2.refresh_semester_schedule_db()
|
# OR localcache2.refresh_semester_schedule_db()
|
||||||
|
|
||||||
#courses = all_2x_sem_courses_teachers('202550', '202570') #
|
#courses = all_2x_sem_courses_teachers('202550', '202570') #
|
||||||
courses = all_sem_courses_teachers(term)
|
courses = all_sem_courses_teachers(term['bannercode'])
|
||||||
|
|
||||||
|
|
||||||
# report for email
|
# report for email
|
||||||
|
|
@ -2262,7 +2264,7 @@ def cross_ref_training():
|
||||||
if ask2.strip()=='y': RELOAD_SCHEDULE = 1
|
if ask2.strip()=='y': RELOAD_SCHEDULE = 1
|
||||||
|
|
||||||
if RELOAD_SCHEDULE:
|
if RELOAD_SCHEDULE:
|
||||||
refresh_semester_schedule_db(term)
|
refresh_semester_schedule_db(term['code'])
|
||||||
|
|
||||||
if RELOAD_TEACHERS:
|
if RELOAD_TEACHERS:
|
||||||
teacherRolesUpdateCache()
|
teacherRolesUpdateCache()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue