from django.core.management.base import BaseCommand from django.db import connections from django.utils import timezone from django.utils.dateparse import parse_datetime from apps.guides.models import GuidePage SELECT_PAGES_SQL = ( 'SELECT path, title, content, "updatedAt" FROM pages ' 'WHERE path LIKE %s AND "isPublished" = true AND "isPrivate" = false' ) class Command(BaseCommand): help = "One-way sync of Wiki.js user-guides pages into GuidePage (read-only on wikijs)." def handle(self, *args, **options): created = 0 updated = 0 stale = [] existing = {page.path: page for page in GuidePage.objects.all()} known_paths = set() with connections["wikijs"].cursor() as cursor: cursor.execute(SELECT_PAGES_SQL, ["user-guides/%"]) rows = cursor.fetchall() for path, title, content, updated_at_raw in rows: audience = GuidePage.map_audience(path) if audience is None: continue known_paths.add(path) parsed = parse_datetime(updated_at_raw) if updated_at_raw else None updated_at = parsed or timezone.now() page = existing.pop(path, None) if page is None: GuidePage.objects.create( path=path, title=title, audience=audience, content=content, updated_at=updated_at, ) created += 1 continue changed = ( page.title != title or page.content != content or page.audience != audience or page.updated_at != updated_at ) if changed: page.title = title page.content = content page.audience = audience page.updated_at = updated_at page.save(update_fields=["title", "content", "audience", "updated_at", "synced_at"]) updated += 1 removed = 0 for path in existing: if path not in known_paths: stale.append(path) if stale: removed = GuidePage.objects.filter(path__in=stale).delete()[0] self.stdout.write( self.style.SUCCESS( f"sync_guides complete: {created} created, {updated} updated, {removed} removed." ) ) self.stdout.write(f"Total GuidePage rows: {GuidePage.objects.count()}.")