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
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
1"""Extended relation manager with inline editing support.
3.. experimental::
4"""
6from __future__ import annotations
8from typing import Any
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
16class RelationManager(AbstractRelationManager):
17 """Relation manager with inline create/edit/delete support.
19 Extends AbstractRelationManager with inline editing methods,
20 permission predicates, and HTMX route handlers.
22 Example:
23 class UserPetsRelationManager(RelationManager):
24 relationship_name = "pets"
26 @classmethod
27 def table(cls, table_config=None):
28 return []
30 async def get_query(self):
31 return []
32 """
34 # Inline editing policy
35 inline_create: bool = True
36 inline_edit: bool = True
37 inline_delete: bool = True
38 inline_detach: bool = False
40 def create_form(self) -> str | None:
41 """Return the HTML for a create form.
43 Override to provide a custom form. Returns None by default.
44 """
45 return None
47 def edit_form(self, record: Any) -> str | None:
48 """Return the HTML for an edit form pre-filled with the given record.
50 Override to provide a custom form. Returns None by default.
51 """
52 return None
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)
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)
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)
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)
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
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))
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))
128 rows.append(el("tr", *cells))
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 )
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 )
159__all__ = [
160 "RelationManager",
161]