Coverage for src / lexigram / admin / relations / manager_ext.py: 35%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +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
15class RelationManager(AbstractRelationManager):
16 """Relation manager with inline create/edit/delete support.
18 Extends AbstractRelationManager with inline editing methods,
19 permission predicates, and HTMX route handlers.
21 Example:
22 class UserPetsRelationManager(RelationManager):
23 relationship_name = "pets"
25 @classmethod
26 def table(cls, table_config=None):
27 return []
29 async def get_query(self):
30 return []
31 """
33 # Inline editing policy
34 inline_create: bool = True
35 inline_edit: bool = True
36 inline_delete: bool = True
37 inline_detach: bool = False
39 def create_form(self) -> str | None:
40 """Return the HTML for a create form.
42 Override to provide a custom form. Returns None by default.
43 """
44 return None
46 def edit_form(self, record: Any) -> str | None:
47 """Return the HTML for an edit form pre-filled with the given record.
49 Override to provide a custom form. Returns None by default.
50 """
51 return None
53 def can_create(
54 self, user: Any | None = None
55 ) -> Result[None, PermissionDeniedError]:
56 """Check whether the user can create related records."""
57 return Ok(None)
59 def can_edit(
60 self, record: Any, user: Any | None = None
61 ) -> Result[None, PermissionDeniedError]:
62 """Check whether the user can edit the given related record."""
63 return Ok(None)
65 def can_delete(
66 self, record: Any, user: Any | None = None
67 ) -> Result[None, PermissionDeniedError]:
68 """Check whether the user can delete the given related record."""
69 return Ok(None)
71 def can_detach(
72 self, record: Any, user: Any | None = None
73 ) -> Result[None, PermissionDeniedError]:
74 """Check whether the user can detach the given related record."""
75 return Ok(None)
77 async def render(self, request: Any, resource_name: str = "") -> str:
78 """Render the relation panel as HTML."""
79 items = await self.get_query()
80 rel_name = self.get_relationship_name()
81 parent_id = self.parent_id
83 rows_html = ""
84 for item in items:
85 item_id = getattr(item, "id", str(id(item)))
86 row_cells = ""
87 cols = self.table()
88 if cols:
89 for col in cols:
90 value = getattr(item, col.name, "") if hasattr(col, "name") else ""
91 row_cells += f"<td>{value}</td>"
92 else:
93 row_cells = f"<td>{item}</td>"
95 actions_html = ""
96 if self.inline_edit:
97 edit_url = f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/{item_id}/edit"
98 actions_html += f'<a href="{edit_url}" hx-get="{edit_url}" hx-target="closest tr" hx-swap="outerHTML">Edit</a> '
99 if self.inline_delete:
100 delete_url = (
101 f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/{item_id}"
102 )
103 actions_html += f'<a href="{delete_url}" hx-delete="{delete_url}" hx-confirm="Delete this record?" hx-target="closest tr" hx-swap="outerHTML">Delete</a>'
104 if actions_html:
105 row_cells += f"<td>{actions_html}</td>"
107 rows_html += f"<tr>{row_cells}</tr>"
109 header_html = ""
110 if self.inline_create:
111 create_url = f"/admin/{resource_name}/{parent_id}/relations/{rel_name}/new"
112 header_html = f'<div><a href="{create_url}" hx-get="{create_url}" hx-target="this" hx-swap="outerHTML">+ Add {rel_name}</a></div>'
114 return f'<div class="relation-panel" id="relation-panel-{rel_name}">{header_html}<table><tbody>{rows_html}</tbody></table></div>'
117__all__ = [
118 "RelationManager",
119]