Coverage for src / lexigram / admin / resources / config.py: 46%
147 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"""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"
44 @property
45 def filters(self) -> Any | None:
46 """Alias for ``filter_options`` used by the toolbar renderer."""
47 return self.filter_options
49 @property
50 def density_css_class(self) -> str:
51 """CSS class for current density setting."""
52 return f"table-density-{self.density}"
54 @property
55 def density_row_height(self) -> str:
56 """Row height in px for current density."""
57 heights = {"compact": "32px", "normal": "48px", "comfortable": "64px"}
58 return heights.get(self.density, "48px")
61class ResourceConfig:
62 """Fluent configuration builder for Resource metadata."""
64 def __init__(self) -> None:
65 self._layout: str = "sidebar"
66 self._view: str = "tabular"
67 self._per_page: int = 20
68 self._default_sort_field: str | None = None
69 self._default_sort_order: str = "asc"
70 self._display_name: str | None = None
71 self._description: str | None = None
72 self._icon: str = "box"
73 self._expandable: bool = False
74 self._record_title_func: Any = None
75 self._columns: list[Any] = []
76 self._actions: list[Any] = []
77 self._filters_list: list[Any] = []
78 self._views_list: list[Any] = []
79 self._action_layout: str = "horizontal"
80 self._form_display_mode: str = "modal"
81 self._name: str | None = None
82 self._group: str | None = None
83 self._group_label: str | None = None
84 self._group_icon: str | None = None
85 self._group_order: int | None = None
87 @staticmethod
88 def builder() -> ResourceConfig:
89 """Return a new :class:`ResourceConfig` builder instance."""
90 return ResourceConfig()
92 def layout(self, layout: Literal["sidebar", "stack"]) -> ResourceConfig:
93 """Set the resource layout."""
94 self._layout = layout
95 return self
97 def name(self, name: str) -> ResourceConfig:
98 """Set the resource registration name."""
99 self._name = name
100 return self
102 def group(
103 self,
104 group: str,
105 *,
106 label: str | None = None,
107 icon: str | None = None,
108 order: int | None = None,
109 ) -> ResourceConfig:
110 """Set the navigation group this resource belongs to.
112 Args:
113 group: Navigation group key.
114 label: Group display label (used when auto-creating the group).
115 icon: Group icon (used when auto-creating the group).
116 order: Group sort order (used when auto-creating the group).
117 """
118 self._group = group
119 if label is not None:
120 self._group_label = label
121 if icon is not None:
122 self._group_icon = icon
123 if order is not None:
124 self._group_order = order
125 return self
127 def view(self, view: Literal["tabular", "grid", "kanban"] | Any) -> ResourceConfig:
128 """Set the default view."""
129 self._view = str(view) if hasattr(view, "__str__") else "tabular"
130 return self
132 def views(self, views: list[Any]) -> ResourceConfig:
133 """Add multiple view definitions."""
134 self._views_list.extend(views)
135 return self
137 def per_page(self, count: int) -> ResourceConfig:
138 """Set items per page."""
139 self._per_page = max(1, count)
140 return self
142 def sort(self, field: str, order: Literal["asc", "desc"] = "asc") -> ResourceConfig:
143 """Set the default sort field and order."""
144 self._default_sort_field = field
145 self._default_sort_order = order if order in ("asc", "desc") else "asc"
146 return self
148 def label(self, label: str) -> ResourceConfig:
149 """Set the display label."""
150 self._display_name = label
151 return self
153 def resource_name(self, name: str) -> ResourceConfig:
154 """Set the resource name (alias for label)."""
155 self._display_name = name
156 return self
158 def icon(self, icon: str) -> ResourceConfig:
159 """Set the icon name."""
160 self._icon = icon
161 return self
163 def description(self, description: str) -> ResourceConfig:
164 """Set the resource description."""
165 self._description = description
166 return self
168 def record_title(self, func: Any) -> ResourceConfig:
169 """Set the record title function."""
170 self._record_title_func = func
171 return self
173 def column(self, col: Any) -> ResourceConfig:
174 """Add a single column definition."""
175 self._columns.append(col)
176 return self
178 def columns(self, cols: list[Any]) -> ResourceConfig:
179 """Add multiple column definitions."""
180 self._columns.extend(cols)
181 return self
183 def action(self, action: Any) -> ResourceConfig:
184 """Add a single action."""
185 self._actions.append(action)
186 return self
188 def actions(self, actions: list[Any]) -> ResourceConfig:
189 """Add multiple actions."""
190 self._actions.extend(actions)
191 return self
193 def filter(self, filter_obj: Any) -> ResourceConfig:
194 """Add a single filter."""
195 self._filters_list.append(filter_obj)
196 return self
198 def filters(self, filters: list[Any]) -> ResourceConfig:
199 """Add multiple filters."""
200 self._filters_list.extend(filters)
201 return self
203 def action_layout(self, layout: Literal["horizontal", "stack"]) -> ResourceConfig:
204 """Set the action layout direction."""
205 self._action_layout = layout
206 return self
208 def form_display_mode(
209 self, mode: Literal["page", "modal", "slider"]
210 ) -> ResourceConfig:
211 """Set how forms are displayed."""
212 if mode in ("page", "modal", "slider"):
213 self._form_display_mode = mode
214 return self
216 @property
217 def display_name(self) -> str | None:
218 """Return the configured display name."""
219 return self._display_name
221 @property
222 def default_sort_field(self) -> str | None:
223 """Return the configured sort field."""
224 return self._default_sort_field
226 @property
227 def default_sort_order(self) -> str:
228 """Return the configured sort order."""
229 return self._default_sort_order
231 @property
232 def filters_list(self) -> list[Any]:
233 """Return the configured filters."""
234 return self._filters_list
236 @property
237 def views_list(self) -> list[Any]:
238 """Return the configured views."""
239 return self._views_list