nav updates
This commit is contained in:
parent
3a6bed425b
commit
b00f2c329c
183
courses.py
183
courses.py
|
|
@ -2424,11 +2424,9 @@ def remove_n_analytics(section=0):
|
|||
import csv
|
||||
|
||||
def my_nav_filter(row):
|
||||
# Return True for tabs that should be considered ON for reporting.
|
||||
# A nav item is ON if visibility is 'public' and 'hidden' is n/a or False.
|
||||
# Filter logic: consider a tab ON only when visibility is public and not hidden; used for CSV summary.
|
||||
if str(row.get('visibility', '')).lower() != 'public':
|
||||
return False
|
||||
# Treat missing/"n/a" as not hidden
|
||||
hidden = row.get('hidden')
|
||||
if str(hidden).lower() in ['true']:
|
||||
return False
|
||||
|
|
@ -2437,7 +2435,7 @@ def my_nav_filter(row):
|
|||
|
||||
## Multi-pass course nav tool. Pass 1: index + XLSX; Pass 2: optional hide/show ids.
|
||||
def clean_course_nav_setup_semester(section=0):
|
||||
# Multi-pass course nav tool: Pass 1 indexes tabs + writes XLSX matrix; Pass 2 optionally hides/shows selected tab ids across all courses in the term.
|
||||
# Multi-pass tool: index course nav, write XLSX with x/-/blank, then optionally bulk hide/show by label across the term.
|
||||
TESTING = False # Limit to first 10 courses while testing
|
||||
import openpyxl
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
|
@ -2454,13 +2452,13 @@ def clean_course_nav_setup_semester(section=0):
|
|||
print("Fetching list of all active courses")
|
||||
courses_in_term = getCoursesInTerm(term, 1, 0)
|
||||
if TESTING:
|
||||
print("TESTING mode enabled: limiting to first 10 courses")
|
||||
courses_in_term = courses_in_term[:10]
|
||||
print("TESTING mode enabled: limiting to first 20 courses")
|
||||
courses_in_term = courses_in_term[:20]
|
||||
|
||||
# Collect course records and their tabs
|
||||
all_tabs_by_course = {} # course_id -> list of tab dicts
|
||||
all_tab_ids = {} # tab_id -> label (most recent seen) — kept for Pass 2 id operations
|
||||
any_on_labels = set() # labels that are ON in at least one course
|
||||
all_labels = set() # all labels seen in any course (visible or hidden)
|
||||
|
||||
# Also write a detailed CSV of visible tabs as before
|
||||
nav_out = codecs.open(f'cache/course_nav_summary_{SEM}.csv', 'w', 'utf-8')
|
||||
|
|
@ -2480,6 +2478,8 @@ def clean_course_nav_setup_semester(section=0):
|
|||
T['hidden'] = "n/a"
|
||||
# Track global set of tab ids and a sample label
|
||||
all_tab_ids[str(T.get('id'))] = T.get('label', str(T.get('id')))
|
||||
if T.get('label'):
|
||||
all_labels.add(T.get('label'))
|
||||
# Write summary of ON tabs
|
||||
vals = [C['id'], C['name'], C['course_code'], C.get('start_at', ''), C.get('workflow_state', ''),
|
||||
T.get('label', ''), T.get('position', ''), T.get('hidden', ''), T.get('visibility', ''),
|
||||
|
|
@ -2487,17 +2487,53 @@ def clean_course_nav_setup_semester(section=0):
|
|||
mydict = dict(zip(columns, vals))
|
||||
if my_nav_filter(mydict):
|
||||
nav_writer.writerow(vals)
|
||||
if T.get('label'):
|
||||
any_on_labels.add(T.get('label'))
|
||||
nav_out.flush()
|
||||
all_tabs_by_course[cid] = tabs
|
||||
except Exception as err:
|
||||
print(f"Exception: {err}")
|
||||
|
||||
try:
|
||||
nav_out.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Build XLSX matrix
|
||||
try:
|
||||
# Column order: labels with at least one ON occurrence, alphabetical
|
||||
tab_labels = sorted(any_on_labels, key=lambda x: str(x).lower())
|
||||
# Compute popularity (count of visible 'x' occurrences per label) and sort by popularity then alpha.
|
||||
# First, build quick lookup per course for label visibility/hidden.
|
||||
def tab_status(t):
|
||||
vis = str(t.get('visibility', '')).lower()
|
||||
hid = str(t.get('hidden', '')).lower()
|
||||
if vis == 'public' and hid not in ['true']:
|
||||
return 'x' # present and visible
|
||||
return '-' # present but hidden (includes non-public visibility)
|
||||
|
||||
# Precompute per-course map: label -> status symbol ('x' or '-')
|
||||
status_by_course = {}
|
||||
for C in courses_in_term:
|
||||
cid = str(C['id'])
|
||||
label_to_status = {}
|
||||
for tdict in all_tabs_by_course.get(cid, []) or []:
|
||||
lbl = tdict.get('label')
|
||||
if not lbl:
|
||||
continue
|
||||
cur = label_to_status.get(lbl)
|
||||
new = tab_status(tdict)
|
||||
# If any instance is visible, prefer 'x' over '-'
|
||||
if cur == 'x':
|
||||
continue
|
||||
label_to_status[lbl] = 'x' if new == 'x' else (cur or '-')
|
||||
status_by_course[cid] = label_to_status
|
||||
|
||||
# Popularity counts
|
||||
visible_counts = defaultdict(int)
|
||||
for cid, lmap in status_by_course.items():
|
||||
for lbl, sym in lmap.items():
|
||||
if sym == 'x':
|
||||
visible_counts[lbl] += 1
|
||||
|
||||
# Column order: by decreasing x-count, then alphabetical for ties
|
||||
tab_labels = sorted(all_labels, key=lambda s: (-visible_counts.get(s, 0), str(s).lower()))
|
||||
xlsx_path = f"cache/course_nav_matrix_{SEM}.xlsx"
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
|
|
@ -2505,8 +2541,8 @@ def clean_course_nav_setup_semester(section=0):
|
|||
|
||||
# Headers
|
||||
static_headers = ['course_id', 'course_name', 'first_teacher']
|
||||
# Row 1: labels only (ids omitted per request)
|
||||
row1 = static_headers + tab_labels
|
||||
# Row 1: labels only, sorted by popularity (most x's first)
|
||||
row1 = static_headers + list(tab_labels)
|
||||
ws.append(row1)
|
||||
|
||||
# Rows: one per course
|
||||
|
|
@ -2524,18 +2560,10 @@ def clean_course_nav_setup_semester(section=0):
|
|||
pass
|
||||
|
||||
row = [cid, cname, teacher]
|
||||
# Build a set of labels that are ON for this course
|
||||
on_labels_in_course = set()
|
||||
for tdict in all_tabs_by_course.get(cid, []) or []:
|
||||
visibility = str(tdict.get('visibility', '')).lower()
|
||||
hidden_val = tdict.get('hidden', '')
|
||||
hidden_str = str(hidden_val).lower()
|
||||
if visibility == 'public' and hidden_str not in ['true']:
|
||||
lbl = tdict.get('label')
|
||||
if lbl:
|
||||
on_labels_in_course.add(lbl)
|
||||
course_map = status_by_course.get(cid, {})
|
||||
for lbl in tab_labels:
|
||||
row.append('x' if lbl in on_labels_in_course else '')
|
||||
sym = course_map.get(lbl, '')
|
||||
row.append(sym)
|
||||
ws.append(row)
|
||||
|
||||
# Simple sizing for readability
|
||||
|
|
@ -2548,88 +2576,83 @@ def clean_course_nav_setup_semester(section=0):
|
|||
except Exception as ex:
|
||||
print(f"Failed to write XLSX matrix: {ex}")
|
||||
|
||||
# Optional Pass 2: apply hide/show updates (by id or label)
|
||||
try_apply = input("Apply changes? [n] hide/show selected tab ids or labels across all courses (y/N): ").strip().lower() in ['y', 'yes']
|
||||
# Optional Pass 2: apply hide/show updates (labels only)
|
||||
try_apply = input("Apply changes? [n] hide/show selected labels across all courses (y/N): ").strip().lower() in ['y', 'yes']
|
||||
if not try_apply:
|
||||
print("Pass 1 complete. No changes applied.")
|
||||
return
|
||||
|
||||
# Gather ids to HIDE and SHOW
|
||||
def parse_id_list(s):
|
||||
# Gather labels to HIDE and SHOW
|
||||
def parse_label_list(s):
|
||||
if not s:
|
||||
return []
|
||||
# allow comma/space/newline separated
|
||||
tokens = []
|
||||
for line in s.replace(',', ' ').split():
|
||||
tokens.append(line.strip())
|
||||
return [x for x in tokens if x]
|
||||
# Allow comma-separated list. Spaces are preserved within labels.
|
||||
if '\n' in s:
|
||||
raw = [x.strip() for x in s.split('\n') if x.strip()]
|
||||
else:
|
||||
raw = [x.strip() for x in s.split(',') if x.strip()]
|
||||
return raw
|
||||
|
||||
hide_src = input("Enter tab ids OR labels to HIDE (comma/space separated), or path to file in cache/ (leave blank to skip): ").strip()
|
||||
show_src = input("Enter tab ids OR labels to SHOW (add/unhide), or path to file in cache/ (leave blank to skip): ").strip()
|
||||
hide_src = input("Enter labels to HIDE (comma-separated or newline file path), leave blank to skip: ").strip()
|
||||
show_src = input("Enter labels to SHOW (comma-separated or newline file path), leave blank to skip: ").strip()
|
||||
|
||||
hide_ids = []
|
||||
show_ids = []
|
||||
hide_labels = []
|
||||
show_labels = []
|
||||
# file helper
|
||||
def read_ids_from_path(pth):
|
||||
|
||||
def read_labels_from_path(pth):
|
||||
try:
|
||||
with codecs.open(pth, 'r', 'utf-8') as f:
|
||||
return parse_id_list(f.read())
|
||||
content = f.read()
|
||||
# Prefer newline separation in files (one label per line)
|
||||
return [x.strip() for x in content.splitlines() if x.strip()]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
# Build a set of all labels observed (from all tabs, not only ON)
|
||||
all_labels_seen = set()
|
||||
for _cid, tlist in all_tabs_by_course.items():
|
||||
for t in (tlist or []):
|
||||
if t.get('label'):
|
||||
all_labels_seen.add(t.get('label'))
|
||||
|
||||
def split_ids_labels(raw_list):
|
||||
ids_out, labels_out = [], []
|
||||
for tok in raw_list:
|
||||
if tok in all_tab_ids or re.match(r"^[a-z_]+(_[a-z0-9]+)*$", tok):
|
||||
# Heuristic: looks like an id (e.g., syllabus, pages, context_external_tool_123)
|
||||
ids_out.append(tok)
|
||||
elif tok in all_labels_seen:
|
||||
labels_out.append(tok)
|
||||
else:
|
||||
# Default to label if not recognizable as id
|
||||
labels_out.append(tok)
|
||||
return [str(x) for x in ids_out], labels_out
|
||||
|
||||
if hide_src:
|
||||
if os.path.exists(hide_src):
|
||||
toks = read_ids_from_path(hide_src)
|
||||
hide_labels = read_labels_from_path(hide_src)
|
||||
elif os.path.exists(os.path.join('cache', hide_src)):
|
||||
toks = read_ids_from_path(os.path.join('cache', hide_src))
|
||||
hide_labels = read_labels_from_path(os.path.join('cache', hide_src))
|
||||
else:
|
||||
toks = parse_id_list(hide_src)
|
||||
hide_ids, hide_labels = split_ids_labels(toks)
|
||||
hide_labels = parse_label_list(hide_src)
|
||||
|
||||
if show_src:
|
||||
if os.path.exists(show_src):
|
||||
toks = read_ids_from_path(show_src)
|
||||
show_labels = read_labels_from_path(show_src)
|
||||
elif os.path.exists(os.path.join('cache', show_src)):
|
||||
toks = read_ids_from_path(os.path.join('cache', show_src))
|
||||
show_labels = read_labels_from_path(os.path.join('cache', show_src))
|
||||
else:
|
||||
toks = parse_id_list(show_src)
|
||||
show_ids, show_labels = split_ids_labels(toks)
|
||||
show_labels = parse_label_list(show_src)
|
||||
|
||||
if not (hide_ids or show_ids or hide_labels or show_labels):
|
||||
print("No ids provided. Skipping Pass 2.")
|
||||
# De-duplicate while keeping order roughly intact
|
||||
hide_labels = list(dict.fromkeys(hide_labels))
|
||||
show_labels = list(dict.fromkeys(show_labels))
|
||||
|
||||
if not (hide_labels or show_labels):
|
||||
print("No labels provided. Skipping Pass 2.")
|
||||
return
|
||||
|
||||
print(f"HIDE ids: {hide_ids}")
|
||||
print(f"HIDE labels: {hide_labels}")
|
||||
print(f"SHOW ids: {show_ids}")
|
||||
print(f"SHOW labels: {show_labels}")
|
||||
confirm = input("Proceed with updates? (y/N): ").strip().lower() in ['y', 'yes']
|
||||
if not confirm:
|
||||
print("Aborted. No changes applied.")
|
||||
return
|
||||
|
||||
# Build lookup of labels currently ON per course from the CSV summary (to avoid unnecessary hide calls)
|
||||
on_labels_by_course = defaultdict(set)
|
||||
try:
|
||||
csv_path = f'cache/course_nav_summary_{SEM}.csv'
|
||||
with codecs.open(csv_path, 'r', 'utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
cid = str(row.get('id', '')).strip()
|
||||
lbl = row.get('label')
|
||||
if cid and lbl:
|
||||
on_labels_by_course[cid].add(lbl)
|
||||
except Exception as ex:
|
||||
print(f"Warning: could not read CSV summary for ON labels: {ex}")
|
||||
|
||||
# Apply changes
|
||||
for C in courses_in_term:
|
||||
cid = str(C['id'])
|
||||
|
|
@ -2638,7 +2661,6 @@ def clean_course_nav_setup_semester(section=0):
|
|||
# Refresh tabs to reduce staleness just before applying
|
||||
if course_tabs is None:
|
||||
course_tabs = fetch(f"{url}/api/v1/courses/{cid}/tabs") or []
|
||||
tabs_by_id = {str(t.get('id')): t for t in course_tabs}
|
||||
tabs_by_label = defaultdict(list)
|
||||
for t in course_tabs:
|
||||
lbl = t.get('label')
|
||||
|
|
@ -2653,18 +2675,9 @@ def clean_course_nav_setup_semester(section=0):
|
|||
action = 'HIDE' if hidden_val else 'SHOW'
|
||||
print(f"{action} {cid} {C.get('name','')} -> {tab_id}: {r.status_code}")
|
||||
|
||||
# Hide by ids
|
||||
for tid in hide_ids:
|
||||
if tid in tabs_by_id:
|
||||
set_hidden_for_id(tid, True)
|
||||
|
||||
# Show by ids
|
||||
for tid in show_ids:
|
||||
if tid in tabs_by_id:
|
||||
set_hidden_for_id(tid, False)
|
||||
|
||||
# Hide by labels
|
||||
for lbl in hide_labels:
|
||||
# Hide by labels (only if label is currently ON per CSV)
|
||||
labels_to_hide_here = [lbl for lbl in hide_labels if lbl in on_labels_by_course.get(cid, set())]
|
||||
for lbl in labels_to_hide_here:
|
||||
for t in tabs_by_label.get(lbl, []):
|
||||
tid = str(t.get('id'))
|
||||
set_hidden_for_id(tid, True)
|
||||
|
|
|
|||
Loading…
Reference in New Issue