Import Bubble export trees into PageOps and finish CSOC-WP-0001
Add import_content_tree management command and import_tree helper for ADR-0004 sample trees. Document reichelag rehearsal lossiness; mark T02/T04 and the bubble exit workplan finished.
This commit is contained in:
parent
affed64839
commit
8447943dc0
6 changed files with 523 additions and 35 deletions
|
|
@ -0,0 +1,122 @@
|
|||
"""Import a local ADR-0004 space tree into CONTENT_ROOT + Space index."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from coulomb_social.apps.members.models import Member
|
||||
from coulomb_social.apps.spaces import pageops
|
||||
from coulomb_social.apps.spaces.import_tree import import_space_tree
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
"Import pages/ + assets/ from a local Bubble export (or hand-made) tree "
|
||||
"into CONTENT_ROOT and upsert the Space index row. Does not call Bubble."
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"tree",
|
||||
type=str,
|
||||
help="Path to space tree (contains pages/ and optional assets/)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--slug",
|
||||
default="",
|
||||
help="Destination space slug (default: directory name)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--title",
|
||||
default="",
|
||||
help="Override space title (default: index.md frontmatter)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tenant",
|
||||
default="",
|
||||
help="Tenant id (default: DEFAULT_TENANT_ID)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--member-subject",
|
||||
default="",
|
||||
help="OIDC subject of Member to grant owner membership",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--replace",
|
||||
action="store_true",
|
||||
help="Overwrite existing CONTENT_ROOT tree for this slug",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Validate tree and print plan without writing",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
tree = Path(options["tree"]).expanduser()
|
||||
if not tree.is_dir():
|
||||
raise CommandError(f"Tree not found: {tree}")
|
||||
pages = tree / "pages"
|
||||
if not pages.is_dir():
|
||||
raise CommandError(f"Missing pages/ under {tree}")
|
||||
|
||||
tenant = options["tenant"] or settings.DEFAULT_TENANT_ID
|
||||
slug = options["slug"] or tree.name
|
||||
md_files = sorted(pages.glob("*.md"))
|
||||
assets_dir = tree / "assets"
|
||||
asset_count = (
|
||||
sum(1 for p in assets_dir.rglob("*") if p.is_file())
|
||||
if assets_dir.is_dir()
|
||||
else 0
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
f"Plan: import {len(md_files)} page(s), {asset_count} asset(s) "
|
||||
f"→ slug={slug!r} tenant={tenant!r} CONTENT_ROOT={pageops.content_root()}"
|
||||
)
|
||||
if options["dry_run"]:
|
||||
for p in md_files[:20]:
|
||||
self.stdout.write(f" page {p.name}")
|
||||
if len(md_files) > 20:
|
||||
self.stdout.write(f" … {len(md_files) - 20} more")
|
||||
self.stdout.write(self.style.WARNING("Dry run — no writes"))
|
||||
return
|
||||
|
||||
member = None
|
||||
subject = (options["member_subject"] or "").strip()
|
||||
if subject:
|
||||
member = Member.objects.filter(subject=subject, tenant_id=tenant).first()
|
||||
if member is None:
|
||||
# try any tenant match for convenience in local smoke
|
||||
member = Member.objects.filter(subject=subject).first()
|
||||
if member is None:
|
||||
raise CommandError(
|
||||
f"No Member with subject={subject!r} "
|
||||
f"(sign in once or pass a known subject)"
|
||||
)
|
||||
|
||||
try:
|
||||
report = import_space_tree(
|
||||
tree,
|
||||
tenant_id=tenant,
|
||||
slug=slug or None,
|
||||
member=member,
|
||||
title=options["title"] or None,
|
||||
replace=options["replace"],
|
||||
)
|
||||
except pageops.PageOpsError as exc:
|
||||
raise CommandError(exc.message) from exc
|
||||
|
||||
action = "Created" if report.space_created else "Updated"
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"{action} space {report.space_slug!r} title={report.title!r} "
|
||||
f"pages={report.pages_copied} assets={report.assets_copied} "
|
||||
f"path={report.content_path}"
|
||||
)
|
||||
)
|
||||
for w in report.warnings:
|
||||
self.stdout.write(self.style.WARNING(f"warning: {w}"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue