- New apps/guides app with GuidePage model and audience mapping by wiki path - sync_guides management command: one-way read-only sync from wikijs DB - Read-only wikijs_ro DB role + WIKI_DB_* env config (secrets, gitignored) - Markdown rendering with bleach sanitisation; rewrites internal guide links to the portal help root and wiki image paths to WIKI_PUBLIC_BASE - /portal/help/ (buyer) and /ops/help/ (ops) index + detail views with role-based audience filtering (404 on cross-role guides) - Help nav item added to both portal base templates - Tests: audience filtering, markdown sanitisation, link rewriting, sync command
83 lines
2 KiB
Python
83 lines
2 KiB
Python
import posixpath
|
|
import re
|
|
from urllib.parse import urlparse
|
|
|
|
import bleach
|
|
from django.conf import settings
|
|
from markdown import markdown
|
|
|
|
ALLOWED_TAGS = [
|
|
"p",
|
|
"br",
|
|
"strong",
|
|
"em",
|
|
"ul",
|
|
"ol",
|
|
"li",
|
|
"table",
|
|
"thead",
|
|
"tbody",
|
|
"tr",
|
|
"th",
|
|
"td",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"h4",
|
|
"a",
|
|
"code",
|
|
"pre",
|
|
"blockquote",
|
|
"hr",
|
|
"img",
|
|
"span",
|
|
"div",
|
|
]
|
|
|
|
ALLOWED_ATTRIBUTES = {
|
|
"a": ["href", "title"],
|
|
"img": ["src", "alt", "title"],
|
|
"th": ["align"],
|
|
"td": ["align"],
|
|
}
|
|
|
|
ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
|
|
|
|
EXTENSIONS = ["tables", "fenced_code", "sane_lists"]
|
|
|
|
WIKI_IMAGE_PREFIXES = ("/buyer-guide/", "/ops-guide/")
|
|
|
|
_IMAGE_SRC_RE = re.compile(r'(<img\b[^>]*?\bsrc=")([^"]*)(")', re.IGNORECASE)
|
|
_ANCHOR_HREF_RE = re.compile(r'(<a\b[^>]*?\bhref=")([^"]*)(")', re.IGNORECASE)
|
|
|
|
|
|
def _rewrite_anchor(match, base_folder, help_root):
|
|
prefix, href, suffix = match.groups()
|
|
parsed = urlparse(href)
|
|
if parsed.scheme or href.startswith(("#", "/")):
|
|
return match.group(0)
|
|
resolved = posixpath.normpath(posixpath.join(base_folder, href))
|
|
if resolved.startswith("user-guides/"):
|
|
suffix_path = resolved[len("user-guides/"):]
|
|
return f'{prefix}{help_root}{suffix_path}/{suffix}'
|
|
return match.group(0)
|
|
|
|
|
|
def _rewrite_image(match):
|
|
prefix, src, suffix = match.groups()
|
|
if src.startswith(WIKI_IMAGE_PREFIXES):
|
|
return f'{prefix}{settings.WIKI_PUBLIC_BASE}{src}{suffix}'
|
|
return match.group(0)
|
|
|
|
|
|
def render_markdown(content, page_path, help_root):
|
|
html = markdown(content or "", extensions=EXTENSIONS)
|
|
base_folder = posixpath.dirname(page_path)
|
|
html = _ANCHOR_HREF_RE.sub(lambda m: _rewrite_anchor(m, base_folder, help_root), html)
|
|
html = _IMAGE_SRC_RE.sub(_rewrite_image, html)
|
|
return bleach.clean(
|
|
html,
|
|
tags=ALLOWED_TAGS,
|
|
attributes=ALLOWED_ATTRIBUTES,
|
|
protocols=ALLOWED_PROTOCOLS,
|
|
)
|