Coverage for src/lexigram/admin/relations/manager_ext.py: 0%

54 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-24 23:18 +0800

1"""Extended relation manager with inline editing support. 

2 

3.. experimental:: 

4""" 

5 

6from __future__ import annotations 

7 

8from typing import Any 

9 

10from lexigram.admin.exceptions import PermissionDeniedError 

11from lexigram.admin.relations.manager import AbstractRelationManager 

12from lexigram.result import Ok, Result 

13from lexigram.ui import el, render_to_string 

14 

15 

16class RelationManager(AbstractRelationManager): 

17 """Relation manager with inline create/edit/delete support. 

18 

19 Extends AbstractRelationManager with inline editing methods, 

20 permission predicates, and HTMX route handlers. 

21 

22 Example: 

23 class UserPetsRelationManager(RelationManager): 

24 relationship_name = "pets" 

25 

26 @classmethod 

27 def table(cls, table_config=None): 

28 return [] 

29 

30 async def get_query(self): 

31 return [] 

32 """ 

33 

34 # Inline editing policy 

35 inline_create: bool = True 

36 inline_edit: bool = True 

37 inline_delete: bool = True 

38 inline_detach: bool = False 

39 

40 def create_form(self) -> str | None: 

41 """Return the HTML for a create form. 

42 

43 Override to provide a custom form. Returns None by default. 

44 """ 

45 return None 

46 

47 def edit_form(self, record: Any) -> str | None: 

48 """Return the HTML for an edit form pre-filled with the given record. 

49 

50 Override to provide a custom form. Returns None by default. 

51 """ 

52 return None 

53 

54 def can_create( 

55 self, user: Any | None = None 

56 ) -> Result[None, PermissionDeniedError]: 

57 """Check whether the user can create related records.""" 

58 return Ok(None) 

59 

60 def can_edit( 

61 self, record: Any, user: Any | None = None 

62 ) -> Result[None, PermissionDeniedError]: 

63 """Check whether the user can edit the given related record.""" 

64 return Ok(None) 

65 

66 def can_delete( 

67 self, record: Any, user: Any | None = None 

68 ) -> Result[None, PermissionDeniedError]: 

69 """Check whether the user can delete the given related record.""" 

70 return Ok(None) 

71 

72 def can_detach( 

73 self, record: Any, user: Any | None = None 

74 ) -> Result[None, PermissionDeniedError]: 

75 """Check whether the user can detach the given related record.""" 

76 return Ok(None) 

77 

78 async def render(self, request: Any, resource_name: str = "") -> str: 

79 """Render the relation panel as HTML.""" 

80 items = await self.get_query() 

81 rel_name = self.get_relationship_name() 

82 parent_id = self.parent_id 

83 

84 rows: list[Any] = [] 

85 for item in items: 

86 item_id = getattr(item, "id", str(id(item))) 

87 cells: list[Any] = [] 

88 cols = self.table() 

89 if cols: 

90 for col in cols: 

91 value = getattr(item, col.name, "") if hasattr(col, "name") else "" 

92 cells.append(el("td", value)) 

93 else: 

94 cells.append(el("td", item)) 

95 

96 actions: list[Any] = [] 

97 if self.inline_edit: 

98 edit_url = f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/{item_id}/edit" 

99 actions.append( 

100 el( 

101 "a", 

102 "Edit", 

103 href=edit_url, 

104 hx_get=edit_url, 

105 hx_target="closest tr", 

106 hx_swap="outerHTML", 

107 ) 

108 ) 

109 actions.append(" ") 

110 if self.inline_delete: 

111 delete_url = ( 

112 f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/{item_id}" 

113 ) 

114 actions.append( 

115 el( 

116 "a", 

117 "Delete", 

118 href=delete_url, 

119 hx_delete=delete_url, 

120 hx_confirm="Delete this record?", 

121 hx_target="closest tr", 

122 hx_swap="outerHTML", 

123 ) 

124 ) 

125 if actions: 

126 cells.append(el("td", *actions)) 

127 

128 rows.append(el("tr", *cells)) 

129 

130 header: list[Any] = [] 

131 if self.inline_create: 

132 create_url = f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/new" 

133 header.append( 

134 el( 

135 "div", 

136 el( 

137 "a", 

138 "+ Add ", 

139 rel_name, 

140 href=create_url, 

141 hx_get=create_url, 

142 hx_target="this", 

143 hx_swap="outerHTML", 

144 ), 

145 ) 

146 ) 

147 

148 return render_to_string( 

149 el( 

150 "div", 

151 *header, 

152 el("table", el("tbody", *rows)), 

153 class_="relation-panel", 

154 id=f"relation-panel-{rel_name}", 

155 ) 

156 ) 

157 

158 

159__all__ = [ 

160 "RelationManager", 

161]