"""Capture and restore board layouts without copying tasks or access grants.""" from django.db import transaction from .models import ( TaskBoard, TaskBoardAccessScope, TaskColumn, TaskColumnRow, ) def snapshot_board_structure(board: TaskBoard) -> dict: """Capture visible columns, nested columns and swimlanes in stable order.""" columns = list( TaskColumn.objects.filter(board=board, is_archived=False).order_by( "position", "id" ) ) rows = list( TaskColumnRow.objects.filter( column__board=board, column__is_archived=False ).order_by("position", "id") ) children_by_parent: dict[int | None, list[TaskColumn]] = {} for column in columns: children_by_parent.setdefault(column.parent_id, []).append(column) rows_by_column: dict[int, list[TaskColumnRow]] = {} for row in rows: rows_by_column.setdefault(row.column_id, []).append(row) seen: set[int] = set() def serialize(column: TaskColumn) -> dict: if column.id in seen: return {} seen.add(column.id) return { "name": column.name, "position": column.position, "color": column.color, "is_done": column.is_done, "rows": [ {"name": row.name, "position": row.position, "color": row.color} for row in rows_by_column.get(column.id, []) ], "subcolumns": [ value for child in children_by_parent.get(column.id, []) if (value := serialize(child)) ], } return { "columns": [serialize(column) for column in children_by_parent.get(None, [])] } @transaction.atomic def create_board_from_template(template, *, owner, name: str) -> TaskBoard: """Instantiate a private board so a shared template cannot leak user data.""" board = TaskBoard.objects.create( name=name, description=template.description, created_by=owner, access_scope=TaskBoardAccessScope.PRIVATE, ) def create_column(spec: dict, parent: TaskColumn | None = None) -> None: column = TaskColumn.objects.create( board=board, parent=parent, name=spec["name"], position=spec.get("position", 0), color=spec.get("color", ""), is_done=bool(spec.get("is_done", False)), ) for row in spec.get("rows", []): TaskColumnRow.objects.create( column=column, name=row["name"], position=row.get("position", 0), color=row.get("color", ""), ) for child in spec.get("subcolumns", []): create_column(child, column) for column_spec in template.layout.get("columns", []): create_column(column_spec) return board