Coverage for src/lexigram/admin/services/form_renderer.py: 0%
28 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"""Form Renderer Service for Lexigram Admin.
3Provides a unified interface for rendering schema-driven forms with
4built-in RBAC enforcement.
5"""
7from __future__ import annotations
9from dataclasses import replace as dc_replace
10from typing import Any
12from lexigram.admin.forms.components import FormSchema, FormSchemaGenerator
13from lexigram.admin.rbac.service import PermissionService
14from lexigram.admin.ui.organisms.dynamic_form import DynamicForm
15from lexigram.di.decorators import inject
18@inject
19class FormRenderer:
20 """Service for rendering forms with RBAC integration.
22 Orchestrates schema generation, permission filtering, and
23 component instantiation.
24 """
26 def __init__(
27 self,
28 generator: FormSchemaGenerator,
29 permission_service: PermissionService | None = None,
30 ) -> None:
31 self.generator = generator
32 self.permission_service = permission_service
34 async def render_form(
35 self,
36 model: type | None = None,
37 schema: FormSchema | None = None,
38 user: Any = None,
39 resource_name: str = "unknown",
40 action: str = "",
41 method: str = "POST",
42 initial_data: dict[str, Any] | None = None,
43 submit_text: str = "Submit",
44 hx_post: str | None = None,
45 hx_target: str | None = None,
46 hx_swap: str = "outerHTML",
47 ) -> DynamicForm:
48 """Render a form for the given model/schema and user context.
50 Args:
51 model: Pydantic model class to generate schema from.
52 schema: Explicit FormSchema (overrides model).
53 user: Current user for RBAC filtering.
54 resource_name: Name of the resource for RBAC checks.
55 action: Form action URL.
56 method: HTTP method.
57 initial_data: Initial values for the form.
58 submit_text: Text for the submit button.
59 hx_post: HTMX post URL.
60 hx_target: HTMX target selector.
61 hx_swap: HTMX swap strategy.
63 Returns:
64 A DynamicForm component instance ready for rendering.
65 """
66 # 1. Resolve base schema
67 if schema:
68 base_schema = schema
69 elif model:
70 base_schema = self.generator.from_pydantic(model)
71 else:
72 base_schema = FormSchema()
74 # Update resource name in schema if provided
75 if resource_name != "unknown":
76 base_schema.resource_name = resource_name
78 # 2. Apply RBAC filtering if user is provided
79 if user:
80 form_schema = await base_schema.filter_for_user(
81 user,
82 resource_name,
83 self.permission_service,
84 )
85 else:
86 form_schema = base_schema
88 # 3. Apply initial data as defaults if provided
89 if initial_data:
90 for idx, field in enumerate(form_schema.fields):
91 if field.name in initial_data:
92 form_schema.fields[idx] = dc_replace(
93 field, default=initial_data[field.name]
94 )
96 # 4. Create and return the DynamicForm
97 return DynamicForm(
98 schema=form_schema,
99 action=action,
100 method=method,
101 submit_text=submit_text,
102 hx_post=hx_post,
103 hx_target=hx_target,
104 hx_swap=hx_swap,
105 )