33import json
44from typing import Any , cast
55
6+ from prism_app .admin_bulk_actions import bulk_status_select_form
67from prism_app .dashboard .dashboard_config_field import DashboardConfigJsonFileField
78from prism_app .database .dashboard_model import DashboardCountry , DashboardStatus
9+ from prism_app .utils import utc_now
810from sqlalchemy .exc import IntegrityError
11+ from sqlalchemy .orm import Session
912from starlette .requests import Request
13+ from starlette_admin .actions import action
1014from starlette_admin .contrib .sqla import ModelView
11- from starlette_admin .exceptions import FormValidationError
15+ from starlette_admin .exceptions import ActionFailed , FormValidationError
1216from starlette_admin .fields import EnumField
17+ from starlette_admin .i18n import ngettext
1318
1419_DASHBOARD_COUNTRY_CHOICES = [
1520 (country .value , country .value ) for country in DashboardCountry
2025 "Change the title in your config JSON or edit the existing dashboard."
2126)
2227
28+ _DASHBOARD_BULK_UPDATE_STATUS_FORM = bulk_status_select_form (DashboardStatus )
29+
2330
2431class DashboardAdminView (ModelView ):
2532 """Create / edit / delete dashboards; path is derived from title and country.
@@ -33,6 +40,7 @@ class DashboardAdminView(ModelView):
3340
3441 label = "Dashboards"
3542 name = "dashboard"
43+ list_template = "dashboard_list.html"
3644 detail_template = "detail_dashboard.html"
3745 edit_template = "edit_no_add_another.html"
3846 create_template = "create_no_add_another.html"
@@ -82,6 +90,61 @@ class DashboardAdminView(ModelView):
8290 "status" ,
8391 "country" ,
8492 ]
93+ actions = ["update_status" , "delete" ]
94+
95+ @action (
96+ name = "update_status" ,
97+ text = "Update status" ,
98+ confirmation = "Update the status of the selected dashboards?" ,
99+ submit_btn_text = "Update status" ,
100+ submit_btn_class = "btn-primary" ,
101+ icon_class = "fa-solid fa-toggle-on" ,
102+ form = _DASHBOARD_BULK_UPDATE_STATUS_FORM ,
103+ )
104+ async def update_status_action (self , request : Request , pks : list [Any ]) -> str :
105+ data = await request .form ()
106+ status_raw = data .get ("status" )
107+ if not status_raw :
108+ raise ActionFailed ("Status is required" )
109+ try :
110+ new_status = DashboardStatus (str (status_raw ))
111+ except ValueError as exc :
112+ raise ActionFailed (f"Invalid status: { status_raw } " ) from exc
113+
114+ session : Session = request .state .session
115+ dashboards = list (await self .find_by_pks (request , pks ))
116+ if not dashboards :
117+ raise ActionFailed ("No accessible dashboards selected" )
118+
119+ now = utc_now ()
120+ for dashboard in dashboards :
121+ dashboard .status = new_status
122+ dashboard .updated_at = now
123+ session .add (dashboard )
124+ session .commit ()
125+
126+ count = len (dashboards )
127+ label = new_status .value
128+ return f"Updated { count } dashboard{ 's' if count != 1 else '' } to { label } ."
129+
130+ @action (
131+ name = "delete" ,
132+ text = "Delete dashboards" ,
133+ confirmation = (
134+ "Are you sure you want to delete the selected dashboards? "
135+ "This cannot be undone."
136+ ),
137+ submit_btn_text = "Yes, delete" ,
138+ submit_btn_class = "btn-danger" ,
139+ icon_class = "fa-solid fa-trash" ,
140+ )
141+ async def delete_action (self , request : Request , pks : list [Any ]) -> str :
142+ affected_rows = await self .delete (request , pks )
143+ return ngettext (
144+ "Dashboard was successfully deleted" ,
145+ "%(count)d dashboards were successfully deleted" ,
146+ affected_rows or 0 ,
147+ ) % {"count" : affected_rows }
85148
86149 async def validate (self , request : Request , data : dict [str , Any ]) -> None :
87150 errors : dict [str , str ] = {}
0 commit comments