Coverage for src / lexigram / admin / schema / text.py: 32%

76 statements  

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

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4from typing import Any 

5 

6from lexigram.admin.schema.base import SchemaField 

7from lexigram.admin.schema.exceptions import FieldError 

8from lexigram.result import Ok, Result 

9from lexigram.ui import Element, TextInput 

10 

11 

12@dataclass(frozen=True, kw_only=True) 

13class TextField(SchemaField[str]): 

14 """A single-line text input field.""" 

15 

16 def render_form( 

17 self, value: str | None, *, errors: list[str] | None = None 

18 ) -> Element: 

19 kwargs: dict[str, Any] = { 

20 "name": self.name, 

21 "value": value if value is not None else "", 

22 } 

23 if self.placeholder is not None: 

24 kwargs["placeholder"] = self.placeholder 

25 if self.label is not None: 

26 kwargs["label"] = self.label 

27 if errors: 

28 kwargs["error"] = errors[0] 

29 return TextInput(**kwargs).render() 

30 

31 def render_column(self, record: Any, value: str | None) -> Element: 

32 if value is None: 

33 return Element("span", "\u2014", class_="text-muted") 

34 return Element("span", value) 

35 

36 def from_form(self, raw: str | None) -> Result[str | None, FieldError]: 

37 if raw is None: 

38 return Ok(None) 

39 stripped = raw.strip() 

40 if not stripped: 

41 if self.nullable: 

42 return Ok(None) 

43 return Ok("") 

44 return Ok(stripped) 

45 

46 

47@dataclass(frozen=True, kw_only=True) 

48class EmailField(TextField): 

49 """An email input field. 

50 

51 Renders an ``<input type="email">`` form control and displays 

52 the value as a ``mailto:`` link in columns. 

53 """ 

54 

55 def render_form( 

56 self, value: str | None, *, errors: list[str] | None = None 

57 ) -> Element: 

58 kwargs: dict[str, Any] = { 

59 "name": self.name, 

60 "value": value if value is not None else "", 

61 "input_type": "email", 

62 } 

63 if self.placeholder is not None: 

64 kwargs["placeholder"] = self.placeholder 

65 if self.label is not None: 

66 kwargs["label"] = self.label 

67 if errors: 

68 kwargs["error"] = errors[0] 

69 return TextInput(**kwargs).render() 

70 

71 def render_column(self, record: Any, value: str | None) -> Element: 

72 if value is None: 

73 return Element("span", "\u2014", class_="text-muted") 

74 return Element("a", value, href=f"mailto:{value}") 

75 

76 

77@dataclass(frozen=True, kw_only=True) 

78class PasswordField(TextField): 

79 """A password input field. 

80 

81 Renders an ``<input type="password">`` form control and masks 

82 the value in columns. 

83 """ 

84 

85 def render_form( 

86 self, value: str | None, *, errors: list[str] | None = None 

87 ) -> Element: 

88 kwargs: dict[str, Any] = { 

89 "name": self.name, 

90 "value": value if value is not None else "", 

91 "input_type": "password", 

92 } 

93 if self.placeholder is not None: 

94 kwargs["placeholder"] = self.placeholder 

95 if self.label is not None: 

96 kwargs["label"] = self.label 

97 if errors: 

98 kwargs["error"] = errors[0] 

99 return TextInput(**kwargs).render() 

100 

101 def render_column(self, record: Any, value: str | None) -> Element: 

102 if value is None: 

103 return Element("span", "\u2014", class_="text-muted") 

104 return Element("span", "\u2022\u2022\u2022\u2022\u2022\u2022") 

105 

106 

107@dataclass(frozen=True, kw_only=True) 

108class URLField(TextField): 

109 """A URL input field. 

110 

111 Renders an ``<input type="url">`` form control and displays 

112 the value as a clickable link in columns. 

113 """ 

114 

115 def render_form( 

116 self, value: str | None, *, errors: list[str] | None = None 

117 ) -> Element: 

118 kwargs: dict[str, Any] = { 

119 "name": self.name, 

120 "value": value if value is not None else "", 

121 "input_type": "url", 

122 } 

123 if self.placeholder is not None: 

124 kwargs["placeholder"] = self.placeholder 

125 if self.label is not None: 

126 kwargs["label"] = self.label 

127 if errors: 

128 kwargs["error"] = errors[0] 

129 return TextInput(**kwargs).render() 

130 

131 def render_column(self, record: Any, value: str | None) -> Element: 

132 if value is None: 

133 return Element("span", "\u2014", class_="text-muted") 

134 return Element("a", value, href=value, target="_blank")