ixg_platform/apps/guides/markdown.py
ixgadmin d74f0814da Finish the wiki root move, zone map view and spec approval
- Follow home/user-guides/ through one GUIDE_PATH_PREFIX constant.
- Add a read-only Origin Zone detail view with the map as the main panel.
- Name due diligence explicitly and add its section headings.
- Add admin-only, four-eyes specification approval (portal and API).
- Empty the New-zone geometry placeholder instead of showing null.
2026-10-27 17:41:08 +00:00

114 lines
3.3 KiB
Python

import posixpath
import re
from urllib.parse import urlparse
import bleach
from django.conf import settings
from markdown import markdown
from .models import GUIDE_PATH_PREFIX
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/")
# Links authored in the wiki are absolute: `/en/home/user-guides/<x>` is a guide
# that this help centre mirrors, while any other `/en/<x>` is a wiki-only page.
WIKI_GUIDE_PREFIX = f"/en/{GUIDE_PATH_PREFIX}"
WIKI_PAGE_PREFIX = "/en/"
_IMAGE_SRC_RE = re.compile(r'(<img\b[^>]*?\bsrc=")([^"]*)(")', re.IGNORECASE)
_ANCHOR_HREF_RE = re.compile(r'(<a\b[^>]*?\bhref=")([^"]*)(")', re.IGNORECASE)
def _rewrite_absolute_anchor(prefix, href, suffix, help_root):
"""Point an absolute wiki link at somewhere that actually resolves.
These hrefs cannot be left alone. Every app host serves only its own
portal (/portal/, /ops/, /admin/) and has no `/en/` route, so an untouched
`/en/...` link is a 404 for whoever clicks it inside the help centre.
- `/en/home/user-guides/<x>` is mirrored in-app -> the in-app help path.
- any other `/en/<x>` exists only in the wiki -> the wiki public base.
"""
path = href.split("#", 1)[0].split("?", 1)[0]
if path.startswith(WIKI_GUIDE_PREFIX):
target = path[len(WIKI_GUIDE_PREFIX):].strip("/")
if target:
return f'{prefix}{help_root}{target}/{suffix}'
return f'{prefix}{help_root}{suffix}'
if path.startswith(WIKI_PAGE_PREFIX):
return f'{prefix}{settings.WIKI_PUBLIC_BASE}{path}{suffix}'
return None
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)
if href.startswith("/"):
rewritten = _rewrite_absolute_anchor(prefix, href, suffix, help_root)
return rewritten if rewritten is not None else match.group(0)
resolved = posixpath.normpath(posixpath.join(base_folder, href))
if resolved.startswith(GUIDE_PATH_PREFIX):
suffix_path = resolved[len(GUIDE_PATH_PREFIX):]
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,
)