Coverage for src / lexigram / admin / schema / datetime_.py: 26%
101 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
1from __future__ import annotations
3from dataclasses import dataclass
4from datetime import date, datetime, time
5from typing import Any
7from lexigram.admin.schema.base import SchemaField
8from lexigram.admin.schema.exceptions import FieldError
9from lexigram.result import Err, Ok, Result
10from lexigram.ui import DateInput, Element, TimePicker
13@dataclass(frozen=True, kw_only=True)
14class DateField(SchemaField[date]):
15 """A date input field."""
17 def render_form(
18 self, value: date | None, *, errors: list[str] | None = None
19 ) -> Element:
20 kwargs: dict[str, Any] = {
21 "name": self.name,
22 "value": value.isoformat() if value is not None else "",
23 }
24 if self.label is not None:
25 kwargs["label"] = self.label
26 if errors:
27 kwargs["error"] = errors[0]
28 return DateInput(**kwargs).render()
30 def render_column(self, record: Any, value: date | None) -> Element:
31 if value is None:
32 return Element("span", "\u2014", class_="text-muted")
33 return Element("span", value.strftime("%B %d, %Y"))
35 def from_form(self, raw: str | None) -> Result[date | None, FieldError]:
36 if raw is None:
37 return Ok(None)
38 stripped = raw.strip()
39 if not stripped:
40 if self.nullable:
41 return Ok(None)
42 return Err(FieldError("Must be a valid date"))
43 try:
44 parsed = date.fromisoformat(stripped)
45 return Ok(parsed)
46 except (ValueError, TypeError):
47 return Err(FieldError("Must be a valid date"))
49 def to_form(self, value: date | None) -> str:
50 if value is None:
51 return ""
52 return value.isoformat()
55@dataclass(frozen=True, kw_only=True)
56class DateTimeField(SchemaField[datetime]):
57 """A datetime input field."""
59 def render_form(
60 self, value: datetime | None, *, errors: list[str] | None = None
61 ) -> Element:
62 kwargs: dict[str, Any] = {
63 "name": self.name,
64 "value": value.isoformat() if value is not None else "",
65 }
66 if self.label is not None:
67 kwargs["label"] = self.label
68 if errors:
69 kwargs["error"] = errors[0]
70 return DateInput(input_type="datetime-local", **kwargs).render()
72 def render_column(self, record: Any, value: datetime | None) -> Element:
73 if value is None:
74 return Element("span", "\u2014", class_="text-muted")
75 return Element("span", value.strftime("%B %d, %Y, %-I:%M %p"))
77 def from_form(self, raw: str | None) -> Result[datetime | None, FieldError]:
78 if raw is None:
79 return Ok(None)
80 stripped = raw.strip()
81 if not stripped:
82 if self.nullable:
83 return Ok(None)
84 return Err(FieldError("Must be a valid datetime"))
85 try:
86 parsed = datetime.fromisoformat(stripped)
87 return Ok(parsed)
88 except (ValueError, TypeError):
89 return Err(FieldError("Must be a valid datetime"))
91 def to_form(self, value: datetime | None) -> str:
92 if value is None:
93 return ""
94 return value.isoformat()
97@dataclass(frozen=True, kw_only=True)
98class TimeField(SchemaField[time]):
99 """A time input field."""
101 def render_form(
102 self, value: time | None, *, errors: list[str] | None = None
103 ) -> Element:
104 kwargs: dict[str, Any] = {
105 "name": self.name,
106 "value": value.strftime("%H:%M") if value is not None else "",
107 }
108 if self.label is not None:
109 kwargs["label"] = self.label
110 if errors:
111 kwargs["error"] = errors[0]
112 return TimePicker(**kwargs).render()
114 def render_column(self, record: Any, value: time | None) -> Element:
115 if value is None:
116 return Element("span", "\u2014", class_="text-muted")
117 return Element("span", value.strftime("%-I:%M %p"))
119 def from_form(self, raw: str | None) -> Result[time | None, FieldError]:
120 if raw is None:
121 return Ok(None)
122 stripped = raw.strip()
123 if not stripped:
124 if self.nullable:
125 return Ok(None)
126 return Err(FieldError("Must be a valid time"))
127 try:
128 parts = stripped.split(":")
129 hour = int(parts[0])
130 minute = int(parts[1])
131 parsed = time(hour, minute)
132 return Ok(parsed)
133 except (ValueError, TypeError, IndexError):
134 return Err(FieldError("Must be a valid time"))
136 def to_form(self, value: time | None) -> str:
137 if value is None:
138 return ""
139 return value.strftime("%H:%M")