Coverage for src/lexigram/admin/resources/config.py: 69%
165 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
1"""Admin resource and table configuration types.
3Provides :class:`TableConfiguration` (static DataTable config) and
4:class:`ResourceConfig` (fluent builder for resource metadata).
5"""
7from __future__ import annotations
9from dataclasses import dataclass
10from typing import Any, Literal
12from lexigram.domain import DomainModel
13from lexigram.validation import ConfigDict, Field
15__all__ = ["ResourceConfig", "TableConfiguration"]
18@dataclass(init=False)
19class TableConfiguration(DomainModel):
20 """Static configuration for a DataTable."""
22 model_config = ConfigDict(arbitrary_types_allowed=True)
24 columns: list[Any] = Field(default_factory=list)
25 actions: list[Any] = Field(default_factory=list)
26 header_actions: list[Any] = Field(default_factory=list)
27 bulk_actions: list[Any] = Field(default_factory=list)
28 filter_options: Any | None = None
29 default_view: str = "tabular"
30 default_layout: str = "stack"
31 action_layout: Literal["horizontal", "stack"] = "horizontal"
32 default_sort_by: str | None = None
33 default_sort_order: str = "asc"
34 per_page: int = 20
35 enable_search: bool = True
36 search_fields: list[str] | None = None
37 reorderable_columns: bool = False
38 group_by: str | None = None
39 resource_name: str | None = None
40 resource_prefix: str | None = None
41 expandable_relationship: str | None = None
42 density: str = "normal"
43 empty_state_title: str | None = None
44 empty_state_message: str | None = None
45 empty_state_icon: str | None = None
47 @property
48 def filters(self) -> Any | None:
49 """Alias for ``filter_options`` used by the toolbar renderer."""
50 return self.filter_options
52 @property
53 def density_css_class(self) -> str:
54 """CSS class for current density setting."""
55 return f"table-density-{self.density}"
57 @property
58 def density_row_height(self) -> str:
59 """Row height in px for current density."""
60 heights = {"compact": "32px", "normal": "48px", "comfortable": "64px"}
61 return heights.get(self.density, "48px")
64class ResourceConfig:
65 """Fluent configuration builder for Resource metadata."""
67 def __init__(self) -> None:
68 self._layout: str = "sidebar"
69 self._view: str = "tabular"
70 self._per_page: int = 20
71 self._default_sort_field: str | None = None
72 self._default_sort_order: str = "asc"
73 self._display_name: str | None = None
74 self._description: str | None = None
75 self._icon: str = "box"
76 self._expandable: bool = False
77 self._record_title_func: Any = None
78 self._columns: list[Any] = []
79 self._actions: list[Any] = []
80 self._filters_list: list[Any] = []
81 self._views_list: list[Any] = []
82 self._action_layout: str = "horizontal"
83 self._form_display_mode: str = "modal"
84 self._name: str | None = None
85 self._group: str | None = None
86 self._group_label: str | None = None
87 self._group_icon: str | None = None
88 self._group_order: int | None = None
89 self._empty_state_title: str | None = None
90 self._empty_state_message: str | None = None
91 self._empty_state_icon: str | None = None
92 self._group_by: str | None = None
94 @staticmethod
95 def builder() -> ResourceConfig:
96 """Return a new :class:`ResourceConfig` builder instance."""
97 return ResourceConfig()
99 def layout(self, layout: Literal["sidebar", "stack"]) -> ResourceConfig:
100 """Set the resource layout."""
101 self._layout = layout
102 return self
104 def name(self, name: str) -> ResourceConfig:
105 """Set the resource registration name."""
106 self._name = name
107 return self
109 def group(
110 self,
111 group: str,
112 *,
113 label: str | None = None,
114 icon: str | None = None,
115 order: int | None = None,
116 ) -> ResourceConfig:
117 """Set the navigation group this resource belongs to.
119 Args:
120 group: Navigation group key.
121 label: Group display label (used when auto-creating the group).
122 icon: Group icon (used when auto-creating the group).
123 order: Group sort order (used when auto-creating the group).
124 """
125 self._group = group
126 if label is not None:
127 self._group_label = label
128 if icon is not None:
129 self._group_icon = icon
130 if order is not None:
131 self._group_order = order
132 return self
134 def view(self, view: Literal["tabular", "grid", "kanban"] | Any) -> ResourceConfig:
135 """Set the default view."""
136 self._view = str(view) if hasattr(view, "__str__") else "tabular"
137 return self
139 def views(self, views: list[Any]) -> ResourceConfig:
140 """Add multiple view definitions."""
141 self._views_list.extend(views)
142 return self
144 def per_page(self, count: int) -> ResourceConfig:
145 """Set items per page."""
146 self._per_page = max(1, count)
147 return self
149 def group_by(self, column: str) -> ResourceConfig:
150 """Set the default grouping column for the resource list view.
152 Users can override the group via the table toolbar dropdown.
154 Args:
155 column: Column name to group rows by.
156 """
157 self._group_by = column
158 return self
160 def sort(self, field: str, order: Literal["asc", "desc"] = "asc") -> ResourceConfig:
161 """Set the default sort field and order."""
162 self._default_sort_field = field
163 self._default_sort_order = order if order in ("asc", "desc") else "asc"
164 return self
166 def label(self, label: str) -> ResourceConfig:
167 """Set the display label."""
168 self._display_name = label
169 return self
171 def resource_name(self, name: str) -> ResourceConfig:
172 """Set the resource name (alias for label)."""
173 self._display_name = name
174 return self
176 def icon(self, icon: str) -> ResourceConfig:
177 """Set the icon name."""
178 self._icon = icon
179 return self
181 def description(self, description: str) -> ResourceConfig:
182 """Set the resource description."""
183 self._description = description
184 return self
186 def empty_state(
187 self,
188 *,
189 title: str | None = None,
190 message: str | None = None,
191 icon: str | None = None,
192 ) -> ResourceConfig:
193 """Override empty-state copy for the resource list view.
195 Args:
196 title: Empty-state heading.
197 message: Empty-state helper text.
198 icon: Empty-state icon (emoji or icon name).
199 """
200 if title is not None:
201 self._empty_state_title = title
202 if message is not None:
203 self._empty_state_message = message
204 if icon is not None:
205 self._empty_state_icon = icon
206 return self
208 def record_title(self, func: Any) -> ResourceConfig:
209 """Set the record title function."""
210 self._record_title_func = func
211 return self
213 def column(self, col: Any) -> ResourceConfig:
214 """Add a single column definition."""
215 self._columns.append(col)
216 return self
218 def columns(self, cols: list[Any]) -> ResourceConfig:
219 """Add multiple column definitions."""
220 self._columns.extend(cols)
221 return self
223 def action(self, action: Any) -> ResourceConfig:
224 """Add a single action."""
225 self._actions.append(action)
226 return self
228 def actions(self, actions: list[Any]) -> ResourceConfig:
229 """Add multiple actions."""
230 self._actions.extend(actions)
231 return self
233 def filter(self, filter_obj: Any) -> ResourceConfig:
234 """Add a single filter."""
235 self._filters_list.append(filter_obj)
236 return self
238 def filters(self, filters: list[Any]) -> ResourceConfig:
239 """Add multiple filters."""
240 self._filters_list.extend(filters)
241 return self
243 def action_layout(self, layout: Literal["horizontal", "stack"]) -> ResourceConfig:
244 """Set the action layout direction."""
245 self._action_layout = layout
246 return self
248 def form_display_mode(
249 self, mode: Literal["page", "modal", "slider"]
250 ) -> ResourceConfig:
251 """Set how forms are displayed."""
252 if mode in ("page", "modal", "slider"):
253 self._form_display_mode = mode
254 return self
256 @property
257 def display_name(self) -> str | None:
258 """Return the configured display name."""
259 return self._display_name
261 @property
262 def default_sort_field(self) -> str | None:
263 """Return the configured sort field."""
264 return self._default_sort_field
266 @property
267 def default_sort_order(self) -> str:
268 """Return the configured sort order."""
269 return self._default_sort_order
271 @property
272 def filters_list(self) -> list[Any]:
273 """Return the configured filters."""
274 return self._filters_list
276 @property
277 def views_list(self) -> list[Any]:
278 """Return the configured views."""
279 return self._views_list