diff --git a/database_api/model_taskitem.py b/database_api/model_taskitem.py index 8e7318ef..28ac5e4a 100644 --- a/database_api/model_taskitem.py +++ b/database_api/model_taskitem.py @@ -4,6 +4,7 @@ from database_api.planexe_db_singleton import db from sqlalchemy_utils import UUIDType from sqlalchemy import JSON +from sqlalchemy.orm import column_property class TaskState(enum.Enum): pending = 1 @@ -70,6 +71,11 @@ class TaskItem(db.Model): # Artifact schema/version marker (legacy snapshots are NULL/1, split-storage snapshots are 2+). run_artifact_layout_version = db.Column(db.Integer, nullable=True, default=None) + # Lightweight admin/UI helpers; avoids loading large payload columns just to render links. + has_generated_report_html = column_property(generated_report_html.isnot(None)) + has_run_zip_snapshot = column_property(run_zip_snapshot.isnot(None)) + has_run_track_activity_jsonl = column_property(run_track_activity_jsonl.isnot(None)) + def __repr__(self): return f"{self.id}: {self.timestamp_created}, {self.state}, {self.prompt!r}, parameters: {self.parameters!r}" diff --git a/frontend_multi_user/src/planexe_modelviews.py b/frontend_multi_user/src/planexe_modelviews.py index 5989cf8b..e28b41e8 100644 --- a/frontend_multi_user/src/planexe_modelviews.py +++ b/frontend_multi_user/src/planexe_modelviews.py @@ -13,6 +13,7 @@ from markupsafe import Markup from flask import url_for, abort, redirect, Response from flask_login import current_user +from sqlalchemy.orm import defer class AdminOnlyModelView(ModelView): """Restrict admin views to authenticated admin users only.""" @@ -135,23 +136,30 @@ class TaskItemView(AdminOnlyModelView): 'prompt': lambda v, c, m, p: m.prompt[:100] + '...' if m.prompt and len(m.prompt) > 100 else m.prompt, 'view_plan': lambda v, c, m, p: Markup( f'View' - ) if m.generated_report_html else '—', + ) if m.has_generated_report_html else '—', 'generated_report_html': lambda v, c, m, p: Markup( - f'Download ({len(m.generated_report_html.encode("utf-8")) / 1024:.1f} KB)' - ) if m.generated_report_html else '—', + f'Download' + ) if m.has_generated_report_html else '—', 'run_zip_snapshot': lambda v, c, m, p: Markup( - f'Download ({len(m.run_zip_snapshot) / 1024:.1f} KB)' - ) if m.run_zip_snapshot else '—', + f'Download' + ) if m.has_run_zip_snapshot else '—', 'run_activity_overview_json': lambda v, c, m, p: ( json.dumps(m.run_activity_overview_json, ensure_ascii=False)[:120] + '...' if m.run_activity_overview_json and len(json.dumps(m.run_activity_overview_json, ensure_ascii=False)) > 120 else (json.dumps(m.run_activity_overview_json, ensure_ascii=False) if m.run_activity_overview_json else '—') ), 'run_track_activity': lambda v, c, m, p: Markup( - f'Download ({((m.run_track_activity_bytes if m.run_track_activity_bytes is not None else len(m.run_track_activity_jsonl.encode("utf-8"))) / 1024):.1f} KB)' - ) if m.run_track_activity_jsonl else '—', + f'Download' + ) if m.has_run_track_activity_jsonl else '—', } + def get_query(self): + return super().get_query().options( + defer(self.model.generated_report_html), + defer(self.model.run_zip_snapshot), + defer(self.model.run_track_activity_jsonl), + ) + class NonceItemView(AdminOnlyModelView): """Custom ModelView for NonceItem""" def __init__(self, model, *args, **kwargs):