Coverage for src/lexigram/admin/actions/row_manager/manager.py: 78%

102 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1""" 

2Row action manager implementation. 

3 

4Provides the main RowActionManager class for managing row actions. 

5""" 

6 

7from __future__ import annotations 

8 

9from collections.abc import Callable 

10from typing import Any 

11 

12from lexigram.admin.actions.row_manager.actions import ( 

13 BASIC_ROW_ACTIONS, 

14 create_delete_action, 

15 create_duplicate_action, 

16 create_edit_action, 

17 create_view_action, 

18) 

19from lexigram.admin.actions.row_manager.groups import ActionGroupManager 

20from lexigram.admin.actions.row_manager.shortcuts import KeyboardShortcutManager 

21from lexigram.admin.actions.row_manager.types import ( 

22 ActionPosition, 

23 ActionStyle, 

24 IRowDataSource, 

25 RowAction, 

26) 

27 

28 

29class RowActionManager: 

30 """Manager for row-level actions in data tables.""" 

31 

32 def __init__(self, data_source: IRowDataSource[Any] | None = None): 

33 """Initialize row action manager. 

34 

35 Args: 

36 data_source: Optional data source for built-in actions 

37 """ 

38 self.data_source = data_source 

39 self._actions: list[RowAction] = [] 

40 self._group_manager = ActionGroupManager() 

41 self._shortcut_manager = KeyboardShortcutManager() 

42 

43 # Initialize with default actions 

44 self._initialize_default_actions() 

45 

46 def _initialize_default_actions(self) -> None: 

47 """Initialize default row actions.""" 

48 for action in BASIC_ROW_ACTIONS: 

49 self.add_action(action) 

50 

51 def add_action(self, action: RowAction) -> None: 

52 """Add a row action.""" 

53 self._actions.append(action) 

54 self._shortcut_manager.register_action(action) 

55 

56 def remove_action(self, action_name: str) -> None: 

57 """Remove a row action.""" 

58 self._actions = list( 

59 filter(lambda action: action.name != action_name, self._actions), 

60 ) 

61 # Note: Keyboard shortcuts are managed by the shortcut manager 

62 

63 def get_action(self, name: str) -> RowAction | None: 

64 """Get an action by name.""" 

65 # Check direct actions 

66 for action in self._actions: 

67 if action.name == name: 

68 return action 

69 

70 # Check actions in groups 

71 for group in self._group_manager.get_all_groups(): 

72 for action in group.actions: 

73 if action.name == name: 

74 return action 

75 

76 return None 

77 

78 def get_all_actions(self) -> list[RowAction]: 

79 """Get all direct actions (not including grouped actions).""" 

80 return self._actions.copy() 

81 

82 def get_visible_actions( 

83 self, 

84 record: Any, 

85 position: ActionPosition | None = None, 

86 ) -> list[RowAction]: 

87 """Get visible actions for a record.""" 

88 actions = [] 

89 

90 for action in self._actions: 

91 # Check position filter 

92 if position and action.position != position: 

93 continue 

94 

95 # Check visibility 

96 if action.visible(record): 

97 actions.append(action) 

98 

99 return actions 

100 

101 def get_all_visible_actions(self, record: Any) -> list[RowAction]: 

102 """Get all visible actions for a record (including grouped actions).""" 

103 actions = self.get_visible_actions(record) 

104 

105 # Add visible actions from groups 

106 for group in self._group_manager.get_visible_groups(record): 

107 actions.extend(group.actions) 

108 

109 return actions 

110 

111 async def execute_action( 

112 self, 

113 action_name: str, 

114 record_id: Any, 

115 record: Any | None = None, 

116 ) -> Any: 

117 """Execute a row action.""" 

118 action = self.get_action(action_name) 

119 if not action: 

120 raise ValueError(f"Action not found: {action_name}") 

121 

122 if not action.handler: 

123 raise ValueError(f"Action has no handler: {action_name}") 

124 

125 return await action.handler(record_id) 

126 

127 def handle_keyboard_shortcut( 

128 self, 

129 shortcut: str, 

130 record_id: Any, 

131 record: Any | None = None, 

132 ) -> RowAction | None: 

133 """Handle a keyboard shortcut.""" 

134 return self._shortcut_manager.execute_shortcut(shortcut, record_id, record) 

135 

136 def get_keyboard_shortcuts(self) -> dict[str, str]: 

137 """Get all registered keyboard shortcuts.""" 

138 return self._shortcut_manager.get_registered_shortcuts_with_labels() 

139 

140 # Action group management 

141 def create_action_group( 

142 self, 

143 name: str, 

144 label: str, 

145 actions: list[RowAction], 

146 **kwargs, 

147 ) -> None: 

148 """Create an action group (dropdown menu).""" 

149 self._group_manager.create_group(name, label, actions, **kwargs) 

150 

151 def get_visible_groups(self, record: Any) -> list: 

152 """Get visible action groups for a record.""" 

153 return self._group_manager.get_visible_groups(record) 

154 

155 # Standard actions 

156 def add_view_action(self, **kwargs) -> None: 

157 """Add a view action.""" 

158 action = create_view_action(**kwargs) 

159 self.add_action(action) 

160 

161 def add_edit_action(self, **kwargs) -> None: 

162 """Add an edit action.""" 

163 action = create_edit_action(**kwargs) 

164 self.add_action(action) 

165 

166 def add_delete_action(self, **kwargs) -> None: 

167 """Add a delete action.""" 

168 action = create_delete_action(**kwargs) 

169 if self.data_source: 

170 action = RowAction( 

171 name=action.name, 

172 label=action.label, 

173 handler=self.delete_record, 

174 icon=action.icon, 

175 style=action.style, 

176 position=action.position, 

177 confirm=action.confirm, 

178 confirm_message=action.confirm_message, 

179 url=action.url, 

180 method=action.method, 

181 open_in_modal=action.open_in_modal, 

182 keyboard_shortcut=action.keyboard_shortcut, 

183 visible=action.visible, 

184 disabled=action.disabled, 

185 tooltip=action.tooltip, 

186 badge=action.badge, 

187 group=action.group, 

188 metadata=action.metadata, 

189 ) 

190 self.add_action(action) 

191 

192 def add_duplicate_action(self, **kwargs) -> None: 

193 """Add a duplicate action.""" 

194 action = create_duplicate_action(**kwargs) 

195 if self.data_source: 

196 action = RowAction( 

197 name=action.name, 

198 label=action.label, 

199 handler=self.duplicate_record, 

200 icon=action.icon, 

201 style=action.style, 

202 position=action.position, 

203 confirm=action.confirm, 

204 confirm_message=action.confirm_message, 

205 url=action.url, 

206 method=action.method, 

207 open_in_modal=action.open_in_modal, 

208 keyboard_shortcut=action.keyboard_shortcut, 

209 visible=action.visible, 

210 disabled=action.disabled, 

211 tooltip=action.tooltip, 

212 badge=action.badge, 

213 group=action.group, 

214 metadata=action.metadata, 

215 ) 

216 self.add_action(action) 

217 

218 def add_custom_action( 

219 self, 

220 name: str, 

221 label: str, 

222 handler: Callable[[Any], Any], 

223 **kwargs, 

224 ) -> None: 

225 """Add a custom row action.""" 

226 action = RowAction( 

227 name=name, 

228 label=label, 

229 handler=handler, 

230 icon=kwargs.get("icon"), 

231 style=kwargs.get("style", ActionStyle.SECONDARY), 

232 confirm=kwargs.get("confirm", False), 

233 confirm_message=kwargs.get("confirm_message"), 

234 keyboard_shortcut=kwargs.get("keyboard_shortcut"), 

235 tooltip=kwargs.get("tooltip"), 

236 visible=kwargs.get("visible", lambda _: True), 

237 disabled=kwargs.get("disabled", lambda _: False), 

238 metadata=kwargs.get("metadata", {}), 

239 ) 

240 self.add_action(action) 

241 

242 # Data source operations 

243 async def get_record(self, record_id: Any) -> Any: 

244 """Get a record by ID.""" 

245 if self.data_source: 

246 return await self.data_source.get_by_id(record_id) 

247 return None 

248 

249 async def delete_record(self, record_id: Any) -> bool: 

250 """Delete a record.""" 

251 if self.data_source: 

252 return await self.data_source.delete(record_id) 

253 return False 

254 

255 async def duplicate_record(self, record_id: Any) -> Any: 

256 """Duplicate a record.""" 

257 if self.data_source: 

258 return await self.data_source.duplicate(record_id) 

259 return None 

260 

261 # Utility methods 

262 def clear_all_actions(self) -> None: 

263 """Clear all actions and shortcuts.""" 

264 self._actions.clear() 

265 self._group_manager.clear_groups() 

266 self._shortcut_manager.clear_all_shortcuts() 

267 

268 def get_action_count(self) -> int: 

269 """Get total number of actions.""" 

270 direct_actions = len(self._actions) 

271 grouped_actions = sum( 

272 len(group.actions) for group in self._group_manager.get_all_groups() 

273 ) 

274 return direct_actions + grouped_actions 

275 

276 def get_group_count(self) -> int: 

277 """Get number of action groups.""" 

278 return len(self._group_manager.get_all_groups())