Coverage for src / lexigram / admin / services / form_renderer.py: 39%

23 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 22:14 +0800

1"""Form Renderer Service for Lexigram Admin. 

2 

3Provides a unified interface for rendering schema-driven forms with 

4built-in RBAC enforcement. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any 

10 

11from lexigram.admin.forms.components import FormSchema, FormSchemaGenerator 

12from lexigram.admin.ui.organisms.dynamic_form import DynamicForm 

13from lexigram.di.decorators import inject 

14 

15 

16@inject 

17class FormRenderer: 

18 """Service for rendering forms with RBAC integration. 

19 

20 Orchestrates schema generation, permission filtering, and 

21 component instantiation. 

22 """ 

23 

24 def __init__(self, generator: FormSchemaGenerator) -> None: 

25 self.generator = generator 

26 

27 def render_form( 

28 self, 

29 model: type | None = None, 

30 schema: FormSchema | None = None, 

31 user: Any = None, 

32 resource_name: str = "unknown", 

33 action: str = "", 

34 method: str = "POST", 

35 initial_data: dict[str, Any] | None = None, 

36 submit_text: str = "Submit", 

37 hx_post: str | None = None, 

38 hx_target: str | None = None, 

39 hx_swap: str = "outerHTML", 

40 ) -> DynamicForm: 

41 """Render a form for the given model/schema and user context. 

42 

43 Args: 

44 model: Pydantic model class to generate schema from. 

45 schema: Explicit FormSchema (overrides model). 

46 user: Current user for RBAC filtering. 

47 resource_name: Name of the resource for RBAC checks. 

48 action: Form action URL. 

49 method: HTTP method. 

50 initial_data: Initial values for the form. 

51 submit_text: Text for the submit button. 

52 hx_post: HTMX post URL. 

53 hx_target: HTMX target selector. 

54 hx_swap: HTMX swap strategy. 

55 

56 Returns: 

57 A DynamicForm component instance ready for rendering. 

58 """ 

59 # 1. Resolve base schema 

60 if schema: 

61 base_schema = schema 

62 elif model: 

63 base_schema = self.generator.from_pydantic(model) 

64 else: 

65 base_schema = FormSchema() 

66 

67 # Update resource name in schema if provided 

68 if resource_name != "unknown": 

69 base_schema.resource_name = resource_name 

70 

71 # 2. Apply RBAC filtering if user is provided 

72 form_schema = base_schema.filter_for_user(user) if user else base_schema # type: ignore[attr-defined] 

73 

74 # 3. Apply initial data as defaults if provided 

75 if initial_data: 

76 for field in form_schema.fields: 

77 if field.name in initial_data: 

78 field.default = initial_data[field.name] 

79 

80 # 4. Create and return the DynamicForm 

81 return DynamicForm( 

82 schema=form_schema, 

83 action=action, 

84 method=method, 

85 submit_text=submit_text, 

86 hx_post=hx_post, 

87 hx_target=hx_target, 

88 hx_swap=hx_swap, 

89 )