Skip to content

Commit d6e24a9

Browse files
committed
Moved rendering to fieldWidget
1 parent 21c7cbe commit d6e24a9

File tree

6 files changed

+149
-79
lines changed

6 files changed

+149
-79
lines changed

tests/test_zform_field_list.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ def test_add_item(field_list):
2525
new_field = field_list.add_item()
2626
assert len(field_list._items) == 1
2727
assert isinstance(new_field, StringField)
28-
assert new_field.name == "xys.0"
28+
assert new_field.name == "xys"
29+
assert new_field.field_info_args.alias == "xys.0"
2930

3031

3132
def test_clear_items(field_list):
@@ -59,7 +60,8 @@ def test_extra_indices(field_list):
5960
def test_get_new_field_at(field_list):
6061
new_field = field_list._get_new_field_at("xys.2")
6162
assert isinstance(new_field, StringField)
62-
assert new_field.name == "xys.2"
63+
assert new_field.name == "xys"
64+
assert new_field.field_info_args.alias == "xys.2"
6365

6466

6567
def test_field_list_iteration(field_list):
@@ -74,8 +76,10 @@ def test_field_list_indexing(field_list):
7476
field_list.add_item()
7577
field_list.add_item()
7678
assert isinstance(field_list._items[0], StringField)
77-
assert field_list._items[0].name == "xys.0"
78-
assert field_list._items[1].name == "xys.1"
79+
assert field_list._items[0].name == "xys"
80+
assert field_list._items[0].field_info_args.alias == "xys.0"
81+
assert field_list._items[1].name == "xys"
82+
assert field_list._items[1].field_info_args.alias == "xys.1"
7983

8084

8185
def test_field_list_len(field_list):

tests/test_zform_obj_field.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def test_process(object_field):
7373
data = {"name": "Alice", "age": 25}
7474
object_field.process(data)
7575

76-
assert object_field._fields[0]._value == "Alice"
77-
assert object_field._fields[1]._value == 25
76+
assert object_field._fields[0].value == "Alice"
77+
assert object_field._fields[1].value == 25
7878

7979

8080
def test_python_type_with_schema(schema_object_field):

zform/fields/base.py

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,54 @@
1919
T_ = t.TypeVar("T_")
2020

2121

22+
class FieldTransientData:
23+
def __init__(self) -> None:
24+
self._data = None
25+
self.errors = []
26+
self.raw_data = None
27+
self._value = None
28+
29+
@property
30+
def value(self) -> t.Any:
31+
return self._value
32+
33+
@value.setter
34+
def value(self, value: t.Any) -> None:
35+
"""
36+
Set the data associated with the field.
37+
38+
Args:
39+
value: The data to set.
40+
"""
41+
self._value = value
42+
43+
@property
44+
def data(self) -> t.Any:
45+
"""
46+
Get the data associated with the field.
47+
48+
Returns:
49+
t.Any: The data associated with the field.
50+
"""
51+
return self.raw_data if self.raw_data else self._data
52+
53+
@data.setter
54+
def data(self, value: t.Any) -> None:
55+
"""
56+
Set the data associated with the field.
57+
58+
Args:
59+
value: The data to set.
60+
"""
61+
self._data = value
62+
63+
def clear(self):
64+
self.data = None
65+
self.errors = []
66+
self.raw_data = None
67+
self._value = None
68+
69+
2270
class FieldBaseMeta(type):
2371
def __call__(cls, *args: t.Any, **kwargs: t.Any):
2472
field_info_args = kwargs.pop("field_info_args", None)
@@ -65,18 +113,14 @@ def __init__(
65113
disabled: t.Optional[bool] = False,
66114
read_only: t.Optional[bool] = False,
67115
field_info_args: t.Optional[t.Union[AttributeDict, t.Dict]] = None,
116+
help_text: t.Optional[str] = None,
68117
**attrs: t.Any,
69118
) -> None:
70119
# A field is fully configured when __field_info_args__ is provided
71120
assert self.type
72121

73-
self._value = None
74122
self.name = name or "no-name"
75123
self.id = id
76-
77-
self._data = None
78-
self.raw_data = None
79-
self.errors = []
80124
self._default = None
81125
self.attrs = AttributeDict(
82126
attrs,
@@ -86,7 +130,7 @@ def __init__(
86130
"class": class_,
87131
},
88132
)
89-
self.help_text = None
133+
self.help_text = help_text
90134
self.label = None
91135

92136
self._field_info_args = AttributeDict(
@@ -103,7 +147,9 @@ def __init__(
103147
self.id = self._field_info.alias
104148
self.label = FormLabel(self.id, label or self.name.capitalize())
105149
self.help_text = self.help_text or self._field_info_args.description
150+
106151
self._widget: t.Optional[FieldWidget] = None
152+
self._transient_data = FieldTransientData()
107153

108154
@property
109155
def widget(self) -> FieldWidget:
@@ -157,23 +203,41 @@ def required(self) -> bool:
157203

158204
@property
159205
def data(self) -> t.Any:
160-
"""
161-
Get the data associated with the field.
162-
163-
Returns:
164-
t.Any: The data associated with the field.
165-
"""
166-
return self.raw_data if self.raw_data else self._data
206+
return self._transient_data.data
167207

168208
@data.setter
169209
def data(self, value: t.Any) -> None:
210+
self._transient_data.data = value
211+
212+
@property
213+
def errors(self) -> t.List[str]:
214+
return self._transient_data.errors
215+
216+
@errors.setter
217+
def errors(self, value: t.Any) -> None:
218+
self._transient_data.errors = value
219+
220+
@property
221+
def raw_data(self) -> t.Any:
222+
return self._transient_data.raw_data
223+
224+
@raw_data.setter
225+
def raw_data(self, value: t.Any) -> None:
226+
self._transient_data.raw_data = value
227+
228+
@property
229+
def value(self) -> T_:
170230
"""
171-
Set the data associated with the field.
231+
Get the processed value of the field.
172232
173-
Args:
174-
value: The data to set.
233+
Returns:
234+
T_: The processed value of the field.
175235
"""
176-
self._data = value
236+
return t.cast(T_, self._transient_data.value)
237+
238+
@value.setter
239+
def value(self, value: t.Any) -> t.Any:
240+
self._transient_data.value = value
177241

178242
@default.setter
179243
def default(self, value: t.Any) -> None:
@@ -196,24 +260,11 @@ def model_field(self) -> ModelField:
196260
"""
197261
return self.__resolver.model_field
198262

199-
@property
200-
def value(self) -> T_:
263+
def clear(self) -> None:
201264
"""
202-
Get the processed value of the field.
203-
204-
Returns:
205-
T_: The processed value of the field.
206-
"""
207-
return self._value
208-
209-
def clear(self):
265+
Clear the field's value and errors.
210266
"""
211-
Clear all field inputs and validation.
212-
"""
213-
self.raw_data = None
214-
self.errors = []
215-
self._data = None
216-
self._value = None
267+
self._transient_data.clear()
217268

218269
def validate_setup(self) -> None:
219270
"""
@@ -234,13 +285,13 @@ def process(
234285
data: The input data to process.
235286
suppress_error: Whether to suppress validation errors.
236287
"""
237-
self.errors.clear()
288+
self._transient_data.errors.clear()
238289
v_, errors_ = self.model_field.validate(
239290
data, {"processing_data": True}, loc=(self.__class__.__name__,)
240291
)
241292

242293
if not errors_:
243-
self._value = v_
294+
self.value = v_
244295
# export to JSON for UI to understand
245296
self.data = fail_silently(self.model_field.serialize, v_) or v_
246297

@@ -254,6 +305,7 @@ def load(self) -> "FieldBase":
254305
Returns:
255306
FieldBase: The field instance with the default value loaded.
256307
"""
308+
self._transient_data.clear()
257309
self.process(self.default, suppress_error=True)
258310
return self
259311

@@ -343,7 +395,7 @@ def __apply_model_field(self) -> None:
343395
self.__resolver = FormParameterResolver(new_model_field)
344396

345397
async def process_form_data(
346-
self, ctx: IExecutionContext, body: t.Any
398+
self, ctx: IExecutionContext, body: t.Any, **kwargs: t.Any
347399
) -> ResolverResult:
348400
"""
349401
Process form data for the field.
@@ -355,12 +407,12 @@ async def process_form_data(
355407
Returns:
356408
ResolverResult: The result of the form data processing.
357409
"""
358-
res = await self.__resolver.resolve(ctx, body=body)
410+
res = await self.__resolver.resolve(ctx, body=body, **kwargs)
359411
_, self.raw_data = dict(res.raw_data).popitem()
360412
self.errors = format_errors(res.errors)
361413

362414
if not res.errors:
363-
self._value = res.data[self.model_field.name]
415+
self.value = res.data[self.model_field.name]
364416

365417
return res
366418

@@ -452,7 +504,7 @@ def rebuild(
452504
z_field_info_args.update(
453505
name=name,
454506
field_info_args=AttributeDict(
455-
**field_info_args,
507+
field_info_args,
456508
alias=alias,
457509
annotation=annotation or get_form_field_python_type(self),
458510
default=default,

zform/fields/flist.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def get_render_context(self) -> t.Dict:
3535
Dict: A dictionary containing the field and the next index for rendering.
3636
"""
3737
ctx = super().get_render_context()
38-
ctx.update(field=self, next_index=len(self.field._items))
38+
ctx.update(field=self, next_index=len(self.field.items))
3939
return ctx
4040

4141

@@ -79,7 +79,6 @@ class MyModel(BaseModel):
7979
"""
8080

8181
type = "list"
82-
8382
widgetType: t.Type[FieldWidget] = FieldListWidget
8483

8584
def __init__(
@@ -116,7 +115,7 @@ def _get_new_field_at(self, alias: str) -> FieldBase:
116115
FieldBase: A new instance of the base field type.
117116
"""
118117
kwargs = dict(getattr(self._base_field, ZFORM_FIELD_ATTRIBUTES, {}))
119-
kwargs.update(name=alias)
118+
kwargs.update(alias=alias, name=self.name)
120119
instance = self._base_field.create_from_annotation(
121120
annotation=get_form_field_python_type(self._base_field), **kwargs
122121
)
@@ -133,6 +132,16 @@ def validate_setup(self) -> None:
133132
self.model_field.type_
134133
), "Annotation must be a Sequence"
135134

135+
@property
136+
def items(self) -> t.List[FieldBase]:
137+
"""
138+
Get the list of items in the field.
139+
140+
Returns:
141+
List[FieldBase]: The list of items in the field.
142+
"""
143+
return list(self._items)
144+
136145
@property
137146
def python_type(self) -> t.Type:
138147
"""
@@ -160,7 +169,7 @@ def process(
160169
self.add_item().process(item_data, suppress_error=suppress_error)
161170

162171
async def process_form_data(
163-
self, ctx: IExecutionContext, body: t.Any
172+
self, ctx: IExecutionContext, body: t.Any, **kwargs: t.Any
164173
) -> ResolverResult:
165174
"""
166175
Process form data for the entire list of fields.
@@ -186,8 +195,8 @@ async def process_form_data(
186195
self._items.append(field)
187196
res = await field.process_form_data(ctx, body=body)
188197

189-
values.append(res.data[key] if res.data else None)
190-
raw_data[str(index)] = res.raw_data[key]
198+
values.append(res.data[self.name] if res.data else None)
199+
raw_data[str(index)] = res.raw_data[self.name]
191200

192201
if res.errors:
193202
errors.setdefault(self.model_field.alias, []).extend(res.errors)

zform/fields/obj.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class FormSerializer(BaseModel):
6565
"""
6666

6767
type: t.Optional[str] = "json"
68-
6968
widgetType: t.Type[FieldWidget] = ObjectFieldWidget
7069

7170
def __init__(
@@ -139,6 +138,12 @@ def _name_formatter(self, name: str) -> str:
139138
Returns:
140139
str: Formatted name including this field's ID.
141140
"""
141+
if self.field_info_args.alias:
142+
return (
143+
self.field_info_args.alias
144+
+ ("." if self.field_info_args.alias else "")
145+
+ name
146+
)
142147
return self.id + ("." if self.id else "") + name
143148

144149
def __iter__(self) -> t.Iterator[FieldBase]:
@@ -180,7 +185,7 @@ async def _process_form_data(
180185
return values, errors, raw_data
181186

182187
async def process_form_data(
183-
self, ctx: IExecutionContext, body: t.Any
188+
self, ctx: IExecutionContext, body: t.Any, **kwargs: t.Any
184189
) -> ResolverResult:
185190
"""
186191
Process and validate form data for this object field.

0 commit comments

Comments
 (0)