codeforms 0.1.1__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeforms-0.1.1 → codeforms-0.2.1}/.gitignore +4 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/PKG-INFO +259 -1
- {codeforms-0.1.1 → codeforms-0.2.1}/README.md +257 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/examples/basic_usage.py +7 -5
- codeforms-0.2.1/examples/conditional_visibility.py +105 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/examples/custom_fields.py +7 -4
- codeforms-0.2.1/examples/dependent_options.py +74 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/examples/i18n_usage.py +25 -17
- codeforms-0.2.1/examples/wizard_form.py +137 -0
- codeforms-0.2.1/pyproject.toml +49 -0
- codeforms-0.2.1/src/codeforms/__init__.py +87 -0
- codeforms-0.2.1/src/codeforms/export.py +624 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/src/codeforms/fields.py +129 -31
- codeforms-0.2.1/src/codeforms/forms.py +948 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/src/codeforms/i18n.py +15 -26
- {codeforms-0.1.1 → codeforms-0.2.1}/src/codeforms/registry.py +56 -23
- {codeforms-0.1.1 → codeforms-0.2.1}/tests/conftest.py +0 -1
- {codeforms-0.1.1 → codeforms-0.2.1}/tests/test_basic_usage.py +6 -2
- codeforms-0.2.1/tests/test_dependent_options.py +120 -0
- codeforms-0.2.1/tests/test_dynamic_visibility.py +262 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/tests/test_i18n.py +18 -13
- codeforms-0.2.1/tests/test_json_schema_export.py +640 -0
- codeforms-0.2.1/tests/test_phase2_compat.py +307 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/tests/test_registry.py +54 -29
- codeforms-0.2.1/tests/test_wizard_steps.py +389 -0
- codeforms-0.1.1/pyproject.toml +0 -25
- codeforms-0.1.1/src/codeforms/__init__.py +0 -34
- codeforms-0.1.1/src/codeforms/export.py +0 -331
- codeforms-0.1.1/src/codeforms/forms.py +0 -527
- {codeforms-0.1.1 → codeforms-0.2.1}/.github/workflows/python-publish.yml +0 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/.github/workflows/tests.yml +0 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/.python-version +0 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/LICENSE +0 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/tests/__init__.py +0 -0
- {codeforms-0.1.1 → codeforms-0.2.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeforms
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Python library for creating, validating, and rendering web forms using Pydantic
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.9
|
|
|
8
8
|
Requires-Dist: pydantic[email]>=2.0
|
|
9
9
|
Provides-Extra: dev
|
|
10
10
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
11
12
|
Description-Content-Type: text/markdown
|
|
12
13
|
|
|
13
14
|
# codeforms
|
|
@@ -179,11 +180,115 @@ print(dict_output)
|
|
|
179
180
|
| `html` | Semantic HTML |
|
|
180
181
|
| `html_bootstrap4` | HTML with Bootstrap 4 classes |
|
|
181
182
|
| `html_bootstrap5` | HTML with Bootstrap 5 classes |
|
|
183
|
+
| `json_schema` | [JSON Schema](http://json-schema.org/draft-07/schema#) (draft-07) |
|
|
182
184
|
| `json` | JSON representation of the form |
|
|
183
185
|
| `dict` | Python dictionary representation |
|
|
184
186
|
|
|
185
187
|
HTML export can also generate a `<script>` block for basic client-side validation.
|
|
186
188
|
|
|
189
|
+
### JSON Schema Export
|
|
190
|
+
|
|
191
|
+
Generate a standard [JSON Schema (draft-07)](http://json-schema.org/draft-07/schema#) from any form. The resulting schema is compatible with tools like [React JSON Schema Form](https://github.com/rjsf-team/react-jsonschema-form), [Angular Formly](https://formly.dev/), and any JSON Schema validator.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import json
|
|
195
|
+
from codeforms import (
|
|
196
|
+
Form, TextField, EmailField, NumberField, SelectField, SelectOption,
|
|
197
|
+
CheckboxField, form_to_json_schema,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
form = Form(
|
|
201
|
+
name="registration",
|
|
202
|
+
fields=[
|
|
203
|
+
TextField(name="name", label="Full Name", required=True, minlength=2, maxlength=100),
|
|
204
|
+
EmailField(name="email", label="Email", required=True),
|
|
205
|
+
NumberField(name="age", label="Age", min_value=18, max_value=120),
|
|
206
|
+
SelectField(
|
|
207
|
+
name="country",
|
|
208
|
+
label="Country",
|
|
209
|
+
required=True,
|
|
210
|
+
options=[
|
|
211
|
+
SelectOption(value="us", label="United States"),
|
|
212
|
+
SelectOption(value="uk", label="United Kingdom"),
|
|
213
|
+
],
|
|
214
|
+
),
|
|
215
|
+
CheckboxField(name="terms", label="Accept Terms", required=True),
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Option 1: Direct function call
|
|
220
|
+
schema = form_to_json_schema(form)
|
|
221
|
+
print(json.dumps(schema, indent=2))
|
|
222
|
+
|
|
223
|
+
# Option 2: Via form.export()
|
|
224
|
+
result = form.export("json_schema")
|
|
225
|
+
schema = result["output"]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Output:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
233
|
+
"type": "object",
|
|
234
|
+
"title": "registration",
|
|
235
|
+
"properties": {
|
|
236
|
+
"name": {
|
|
237
|
+
"type": "string",
|
|
238
|
+
"minLength": 2,
|
|
239
|
+
"maxLength": 100,
|
|
240
|
+
"title": "Full Name"
|
|
241
|
+
},
|
|
242
|
+
"email": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"format": "email",
|
|
245
|
+
"title": "Email"
|
|
246
|
+
},
|
|
247
|
+
"age": {
|
|
248
|
+
"type": "number",
|
|
249
|
+
"minimum": 18,
|
|
250
|
+
"maximum": 120,
|
|
251
|
+
"title": "Age"
|
|
252
|
+
},
|
|
253
|
+
"country": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"enum": ["us", "uk"],
|
|
256
|
+
"title": "Country"
|
|
257
|
+
},
|
|
258
|
+
"terms": {
|
|
259
|
+
"type": "boolean",
|
|
260
|
+
"title": "Accept Terms"
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"required": ["name", "email", "country", "terms"],
|
|
264
|
+
"additionalProperties": false
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### Field Type Mapping
|
|
269
|
+
|
|
270
|
+
| codeforms Field | JSON Schema Type | Extra Keywords |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `TextField` | `string` | `minLength`, `maxLength`, `pattern` |
|
|
273
|
+
| `EmailField` | `string` (`format: "email"`) | — |
|
|
274
|
+
| `NumberField` | `number` | `minimum`, `maximum`, `multipleOf` |
|
|
275
|
+
| `DateField` | `string` (`format: "date"`) | — |
|
|
276
|
+
| `SelectField` | `string` + `enum` | — |
|
|
277
|
+
| `SelectField` (`multiple=True`) | `array` of `enum` strings | `minItems`, `maxItems`, `uniqueItems` |
|
|
278
|
+
| `RadioField` | `string` + `enum` | — |
|
|
279
|
+
| `CheckboxField` | `boolean` | — |
|
|
280
|
+
| `CheckboxGroupField` | `array` of `enum` strings | `uniqueItems` |
|
|
281
|
+
| `FileField` | `string` (`contentEncoding: "base64"`) | — |
|
|
282
|
+
| `FileField` (`multiple=True`) | `array` of base64 strings | — |
|
|
283
|
+
| `HiddenField` | `string` | — |
|
|
284
|
+
| `UrlField` | `string` (`format: "uri"`) | `minLength`, `maxLength` |
|
|
285
|
+
| `TextareaField` | `string` | `minLength`, `maxLength` |
|
|
286
|
+
| `ListField` | `array` | `minItems`, `maxItems` |
|
|
287
|
+
|
|
288
|
+
Field annotations like `label`, `help_text`, `default_value`, and `readonly` map to the JSON Schema keywords `title`, `description`, `default`, and `readOnly` respectively.
|
|
289
|
+
|
|
290
|
+
Fields inside `FieldGroup` and `FormStep` containers are flattened into the top-level `properties` automatically.
|
|
291
|
+
|
|
187
292
|
## Internationalization (i18n)
|
|
188
293
|
|
|
189
294
|
All validation and export messages are locale-aware. **English** (`en`) and **Spanish** (`es`) are included out of the box, and you can register any additional language at runtime via `register_locale()`.
|
|
@@ -258,6 +363,159 @@ print(result["errors"][0]["message"]) # "El campo name es requerido"
|
|
|
258
363
|
|
|
259
364
|
See [`examples/i18n_usage.py`](examples/i18n_usage.py) for a full working example.
|
|
260
365
|
|
|
366
|
+
## Dynamic Forms
|
|
367
|
+
|
|
368
|
+
### Conditional Visibility
|
|
369
|
+
|
|
370
|
+
Fields can be shown or hidden based on the value of other fields using `visible_when`. This is metadata that your frontend can use for dynamic UI, and the backend can respect during validation.
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from codeforms import Form, TextField, SelectField, SelectOption, VisibilityRule
|
|
374
|
+
|
|
375
|
+
form = Form(
|
|
376
|
+
name="address",
|
|
377
|
+
fields=[
|
|
378
|
+
SelectField(
|
|
379
|
+
name="country",
|
|
380
|
+
label="Country",
|
|
381
|
+
required=True,
|
|
382
|
+
options=[
|
|
383
|
+
SelectOption(value="US", label="United States"),
|
|
384
|
+
SelectOption(value="AR", label="Argentina"),
|
|
385
|
+
],
|
|
386
|
+
),
|
|
387
|
+
TextField(
|
|
388
|
+
name="state",
|
|
389
|
+
label="State",
|
|
390
|
+
required=True,
|
|
391
|
+
visible_when=[
|
|
392
|
+
VisibilityRule(field="country", operator="equals", value="US"),
|
|
393
|
+
],
|
|
394
|
+
),
|
|
395
|
+
TextField(
|
|
396
|
+
name="province",
|
|
397
|
+
label="Province",
|
|
398
|
+
required=True,
|
|
399
|
+
visible_when=[
|
|
400
|
+
VisibilityRule(field="country", operator="equals", value="AR"),
|
|
401
|
+
],
|
|
402
|
+
),
|
|
403
|
+
],
|
|
404
|
+
)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Supported operators: `equals`, `not_equals`, `in`, `not_in`, `gt`, `lt`, `is_empty`, `is_not_empty`.
|
|
408
|
+
|
|
409
|
+
#### Dynamic Validation
|
|
410
|
+
|
|
411
|
+
Use `validate_form_data_dynamic()` to validate only the fields that are currently visible:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
from codeforms import validate_form_data_dynamic
|
|
415
|
+
|
|
416
|
+
result = validate_form_data_dynamic(
|
|
417
|
+
form,
|
|
418
|
+
{"country": "US", "state": "California"},
|
|
419
|
+
respect_visibility=True,
|
|
420
|
+
)
|
|
421
|
+
print(result["success"]) # True — "province" is hidden, so not required
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The legacy `validate_form_data()` function is unchanged and always validates all fields regardless of visibility.
|
|
425
|
+
|
|
426
|
+
#### Checking Visible Fields
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
visible = form.get_visible_fields({"country": "US"})
|
|
430
|
+
print([f.name for f in visible]) # ["country", "state"]
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
See [`examples/conditional_visibility.py`](examples/conditional_visibility.py) for a full working example.
|
|
434
|
+
|
|
435
|
+
### Dependent Options
|
|
436
|
+
|
|
437
|
+
Use `DependentOptionsConfig` to define option sets that change based on another field's value:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
from codeforms import SelectField, SelectOption, DependentOptionsConfig
|
|
441
|
+
|
|
442
|
+
city_field = SelectField(
|
|
443
|
+
name="city",
|
|
444
|
+
label="City",
|
|
445
|
+
options=[ # all possible options (for static HTML rendering)
|
|
446
|
+
SelectOption(value="nyc", label="New York City"),
|
|
447
|
+
SelectOption(value="bsas", label="Buenos Aires"),
|
|
448
|
+
],
|
|
449
|
+
dependent_options=DependentOptionsConfig(
|
|
450
|
+
depends_on="country",
|
|
451
|
+
options_map={
|
|
452
|
+
"US": [SelectOption(value="nyc", label="New York City")],
|
|
453
|
+
"AR": [SelectOption(value="bsas", label="Buenos Aires")],
|
|
454
|
+
},
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The `dependent_options` metadata serializes to JSON for your frontend to consume. See [`examples/dependent_options.py`](examples/dependent_options.py).
|
|
460
|
+
|
|
461
|
+
## Multi-Step Wizard Forms
|
|
462
|
+
|
|
463
|
+
Use `FormStep` to split a form into multiple steps. Each step contains its own fields and can be validated independently.
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
from codeforms import Form, FormStep, TextField, EmailField, CheckboxField
|
|
467
|
+
|
|
468
|
+
form = Form(
|
|
469
|
+
name="registration",
|
|
470
|
+
content=[
|
|
471
|
+
FormStep(
|
|
472
|
+
title="Personal Information",
|
|
473
|
+
description="Tell us about yourself",
|
|
474
|
+
content=[
|
|
475
|
+
TextField(name="name", label="Name", required=True),
|
|
476
|
+
EmailField(name="email", label="Email", required=True),
|
|
477
|
+
],
|
|
478
|
+
),
|
|
479
|
+
FormStep(
|
|
480
|
+
title="Confirmation",
|
|
481
|
+
content=[
|
|
482
|
+
CheckboxField(name="terms", label="I accept the terms", required=True),
|
|
483
|
+
],
|
|
484
|
+
validation_mode="on_submit",
|
|
485
|
+
),
|
|
486
|
+
],
|
|
487
|
+
)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Step Validation
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
# Validate a single step
|
|
494
|
+
result = form.validate_step(0, {"name": "John", "email": "john@example.com"})
|
|
495
|
+
print(result["success"]) # True
|
|
496
|
+
|
|
497
|
+
# Validate all steps at once
|
|
498
|
+
result = form.validate_all_steps({
|
|
499
|
+
"name": "John",
|
|
500
|
+
"email": "john@example.com",
|
|
501
|
+
"terms": True,
|
|
502
|
+
})
|
|
503
|
+
print(result["success"]) # True
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Wizard Helpers
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
steps = form.get_steps() # List[FormStep]
|
|
510
|
+
fields = form.fields # Flat list of all fields across all steps
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### HTML Export
|
|
514
|
+
|
|
515
|
+
Wizard forms export with `data-wizard="true"` on the `<form>` tag. Each step renders as a `<section data-step="true">` (not `<fieldset>`), so you can wire up your own step navigation in JavaScript.
|
|
516
|
+
|
|
517
|
+
See [`examples/wizard_form.py`](examples/wizard_form.py) for a full working example.
|
|
518
|
+
|
|
261
519
|
## Custom Field Types
|
|
262
520
|
|
|
263
521
|
You can create your own field types by subclassing `FormFieldBase` and registering them with `register_field_type()`. Custom fields integrate seamlessly with forms, JSON serialization, validation, and HTML export.
|
|
@@ -167,11 +167,115 @@ print(dict_output)
|
|
|
167
167
|
| `html` | Semantic HTML |
|
|
168
168
|
| `html_bootstrap4` | HTML with Bootstrap 4 classes |
|
|
169
169
|
| `html_bootstrap5` | HTML with Bootstrap 5 classes |
|
|
170
|
+
| `json_schema` | [JSON Schema](http://json-schema.org/draft-07/schema#) (draft-07) |
|
|
170
171
|
| `json` | JSON representation of the form |
|
|
171
172
|
| `dict` | Python dictionary representation |
|
|
172
173
|
|
|
173
174
|
HTML export can also generate a `<script>` block for basic client-side validation.
|
|
174
175
|
|
|
176
|
+
### JSON Schema Export
|
|
177
|
+
|
|
178
|
+
Generate a standard [JSON Schema (draft-07)](http://json-schema.org/draft-07/schema#) from any form. The resulting schema is compatible with tools like [React JSON Schema Form](https://github.com/rjsf-team/react-jsonschema-form), [Angular Formly](https://formly.dev/), and any JSON Schema validator.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import json
|
|
182
|
+
from codeforms import (
|
|
183
|
+
Form, TextField, EmailField, NumberField, SelectField, SelectOption,
|
|
184
|
+
CheckboxField, form_to_json_schema,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
form = Form(
|
|
188
|
+
name="registration",
|
|
189
|
+
fields=[
|
|
190
|
+
TextField(name="name", label="Full Name", required=True, minlength=2, maxlength=100),
|
|
191
|
+
EmailField(name="email", label="Email", required=True),
|
|
192
|
+
NumberField(name="age", label="Age", min_value=18, max_value=120),
|
|
193
|
+
SelectField(
|
|
194
|
+
name="country",
|
|
195
|
+
label="Country",
|
|
196
|
+
required=True,
|
|
197
|
+
options=[
|
|
198
|
+
SelectOption(value="us", label="United States"),
|
|
199
|
+
SelectOption(value="uk", label="United Kingdom"),
|
|
200
|
+
],
|
|
201
|
+
),
|
|
202
|
+
CheckboxField(name="terms", label="Accept Terms", required=True),
|
|
203
|
+
],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Option 1: Direct function call
|
|
207
|
+
schema = form_to_json_schema(form)
|
|
208
|
+
print(json.dumps(schema, indent=2))
|
|
209
|
+
|
|
210
|
+
# Option 2: Via form.export()
|
|
211
|
+
result = form.export("json_schema")
|
|
212
|
+
schema = result["output"]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Output:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
220
|
+
"type": "object",
|
|
221
|
+
"title": "registration",
|
|
222
|
+
"properties": {
|
|
223
|
+
"name": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"minLength": 2,
|
|
226
|
+
"maxLength": 100,
|
|
227
|
+
"title": "Full Name"
|
|
228
|
+
},
|
|
229
|
+
"email": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"format": "email",
|
|
232
|
+
"title": "Email"
|
|
233
|
+
},
|
|
234
|
+
"age": {
|
|
235
|
+
"type": "number",
|
|
236
|
+
"minimum": 18,
|
|
237
|
+
"maximum": 120,
|
|
238
|
+
"title": "Age"
|
|
239
|
+
},
|
|
240
|
+
"country": {
|
|
241
|
+
"type": "string",
|
|
242
|
+
"enum": ["us", "uk"],
|
|
243
|
+
"title": "Country"
|
|
244
|
+
},
|
|
245
|
+
"terms": {
|
|
246
|
+
"type": "boolean",
|
|
247
|
+
"title": "Accept Terms"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"required": ["name", "email", "country", "terms"],
|
|
251
|
+
"additionalProperties": false
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Field Type Mapping
|
|
256
|
+
|
|
257
|
+
| codeforms Field | JSON Schema Type | Extra Keywords |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `TextField` | `string` | `minLength`, `maxLength`, `pattern` |
|
|
260
|
+
| `EmailField` | `string` (`format: "email"`) | — |
|
|
261
|
+
| `NumberField` | `number` | `minimum`, `maximum`, `multipleOf` |
|
|
262
|
+
| `DateField` | `string` (`format: "date"`) | — |
|
|
263
|
+
| `SelectField` | `string` + `enum` | — |
|
|
264
|
+
| `SelectField` (`multiple=True`) | `array` of `enum` strings | `minItems`, `maxItems`, `uniqueItems` |
|
|
265
|
+
| `RadioField` | `string` + `enum` | — |
|
|
266
|
+
| `CheckboxField` | `boolean` | — |
|
|
267
|
+
| `CheckboxGroupField` | `array` of `enum` strings | `uniqueItems` |
|
|
268
|
+
| `FileField` | `string` (`contentEncoding: "base64"`) | — |
|
|
269
|
+
| `FileField` (`multiple=True`) | `array` of base64 strings | — |
|
|
270
|
+
| `HiddenField` | `string` | — |
|
|
271
|
+
| `UrlField` | `string` (`format: "uri"`) | `minLength`, `maxLength` |
|
|
272
|
+
| `TextareaField` | `string` | `minLength`, `maxLength` |
|
|
273
|
+
| `ListField` | `array` | `minItems`, `maxItems` |
|
|
274
|
+
|
|
275
|
+
Field annotations like `label`, `help_text`, `default_value`, and `readonly` map to the JSON Schema keywords `title`, `description`, `default`, and `readOnly` respectively.
|
|
276
|
+
|
|
277
|
+
Fields inside `FieldGroup` and `FormStep` containers are flattened into the top-level `properties` automatically.
|
|
278
|
+
|
|
175
279
|
## Internationalization (i18n)
|
|
176
280
|
|
|
177
281
|
All validation and export messages are locale-aware. **English** (`en`) and **Spanish** (`es`) are included out of the box, and you can register any additional language at runtime via `register_locale()`.
|
|
@@ -246,6 +350,159 @@ print(result["errors"][0]["message"]) # "El campo name es requerido"
|
|
|
246
350
|
|
|
247
351
|
See [`examples/i18n_usage.py`](examples/i18n_usage.py) for a full working example.
|
|
248
352
|
|
|
353
|
+
## Dynamic Forms
|
|
354
|
+
|
|
355
|
+
### Conditional Visibility
|
|
356
|
+
|
|
357
|
+
Fields can be shown or hidden based on the value of other fields using `visible_when`. This is metadata that your frontend can use for dynamic UI, and the backend can respect during validation.
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from codeforms import Form, TextField, SelectField, SelectOption, VisibilityRule
|
|
361
|
+
|
|
362
|
+
form = Form(
|
|
363
|
+
name="address",
|
|
364
|
+
fields=[
|
|
365
|
+
SelectField(
|
|
366
|
+
name="country",
|
|
367
|
+
label="Country",
|
|
368
|
+
required=True,
|
|
369
|
+
options=[
|
|
370
|
+
SelectOption(value="US", label="United States"),
|
|
371
|
+
SelectOption(value="AR", label="Argentina"),
|
|
372
|
+
],
|
|
373
|
+
),
|
|
374
|
+
TextField(
|
|
375
|
+
name="state",
|
|
376
|
+
label="State",
|
|
377
|
+
required=True,
|
|
378
|
+
visible_when=[
|
|
379
|
+
VisibilityRule(field="country", operator="equals", value="US"),
|
|
380
|
+
],
|
|
381
|
+
),
|
|
382
|
+
TextField(
|
|
383
|
+
name="province",
|
|
384
|
+
label="Province",
|
|
385
|
+
required=True,
|
|
386
|
+
visible_when=[
|
|
387
|
+
VisibilityRule(field="country", operator="equals", value="AR"),
|
|
388
|
+
],
|
|
389
|
+
),
|
|
390
|
+
],
|
|
391
|
+
)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Supported operators: `equals`, `not_equals`, `in`, `not_in`, `gt`, `lt`, `is_empty`, `is_not_empty`.
|
|
395
|
+
|
|
396
|
+
#### Dynamic Validation
|
|
397
|
+
|
|
398
|
+
Use `validate_form_data_dynamic()` to validate only the fields that are currently visible:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
from codeforms import validate_form_data_dynamic
|
|
402
|
+
|
|
403
|
+
result = validate_form_data_dynamic(
|
|
404
|
+
form,
|
|
405
|
+
{"country": "US", "state": "California"},
|
|
406
|
+
respect_visibility=True,
|
|
407
|
+
)
|
|
408
|
+
print(result["success"]) # True — "province" is hidden, so not required
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The legacy `validate_form_data()` function is unchanged and always validates all fields regardless of visibility.
|
|
412
|
+
|
|
413
|
+
#### Checking Visible Fields
|
|
414
|
+
|
|
415
|
+
```python
|
|
416
|
+
visible = form.get_visible_fields({"country": "US"})
|
|
417
|
+
print([f.name for f in visible]) # ["country", "state"]
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
See [`examples/conditional_visibility.py`](examples/conditional_visibility.py) for a full working example.
|
|
421
|
+
|
|
422
|
+
### Dependent Options
|
|
423
|
+
|
|
424
|
+
Use `DependentOptionsConfig` to define option sets that change based on another field's value:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from codeforms import SelectField, SelectOption, DependentOptionsConfig
|
|
428
|
+
|
|
429
|
+
city_field = SelectField(
|
|
430
|
+
name="city",
|
|
431
|
+
label="City",
|
|
432
|
+
options=[ # all possible options (for static HTML rendering)
|
|
433
|
+
SelectOption(value="nyc", label="New York City"),
|
|
434
|
+
SelectOption(value="bsas", label="Buenos Aires"),
|
|
435
|
+
],
|
|
436
|
+
dependent_options=DependentOptionsConfig(
|
|
437
|
+
depends_on="country",
|
|
438
|
+
options_map={
|
|
439
|
+
"US": [SelectOption(value="nyc", label="New York City")],
|
|
440
|
+
"AR": [SelectOption(value="bsas", label="Buenos Aires")],
|
|
441
|
+
},
|
|
442
|
+
),
|
|
443
|
+
)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The `dependent_options` metadata serializes to JSON for your frontend to consume. See [`examples/dependent_options.py`](examples/dependent_options.py).
|
|
447
|
+
|
|
448
|
+
## Multi-Step Wizard Forms
|
|
449
|
+
|
|
450
|
+
Use `FormStep` to split a form into multiple steps. Each step contains its own fields and can be validated independently.
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
from codeforms import Form, FormStep, TextField, EmailField, CheckboxField
|
|
454
|
+
|
|
455
|
+
form = Form(
|
|
456
|
+
name="registration",
|
|
457
|
+
content=[
|
|
458
|
+
FormStep(
|
|
459
|
+
title="Personal Information",
|
|
460
|
+
description="Tell us about yourself",
|
|
461
|
+
content=[
|
|
462
|
+
TextField(name="name", label="Name", required=True),
|
|
463
|
+
EmailField(name="email", label="Email", required=True),
|
|
464
|
+
],
|
|
465
|
+
),
|
|
466
|
+
FormStep(
|
|
467
|
+
title="Confirmation",
|
|
468
|
+
content=[
|
|
469
|
+
CheckboxField(name="terms", label="I accept the terms", required=True),
|
|
470
|
+
],
|
|
471
|
+
validation_mode="on_submit",
|
|
472
|
+
),
|
|
473
|
+
],
|
|
474
|
+
)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Step Validation
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
# Validate a single step
|
|
481
|
+
result = form.validate_step(0, {"name": "John", "email": "john@example.com"})
|
|
482
|
+
print(result["success"]) # True
|
|
483
|
+
|
|
484
|
+
# Validate all steps at once
|
|
485
|
+
result = form.validate_all_steps({
|
|
486
|
+
"name": "John",
|
|
487
|
+
"email": "john@example.com",
|
|
488
|
+
"terms": True,
|
|
489
|
+
})
|
|
490
|
+
print(result["success"]) # True
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Wizard Helpers
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
steps = form.get_steps() # List[FormStep]
|
|
497
|
+
fields = form.fields # Flat list of all fields across all steps
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### HTML Export
|
|
501
|
+
|
|
502
|
+
Wizard forms export with `data-wizard="true"` on the `<form>` tag. Each step renders as a `<section data-step="true">` (not `<fieldset>`), so you can wire up your own step navigation in JavaScript.
|
|
503
|
+
|
|
504
|
+
See [`examples/wizard_form.py`](examples/wizard_form.py) for a full working example.
|
|
505
|
+
|
|
249
506
|
## Custom Field Types
|
|
250
507
|
|
|
251
508
|
You can create your own field types by subclassing `FormFieldBase` and registering them with `register_field_type()`. Custom fields integrate seamlessly with forms, JSON serialization, validation, and HTML export.
|
|
@@ -6,14 +6,14 @@ and HTML export.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from codeforms import (
|
|
9
|
-
Form,
|
|
10
|
-
TextField,
|
|
11
|
-
EmailField,
|
|
12
|
-
SelectField,
|
|
13
9
|
CheckboxField,
|
|
14
10
|
CheckboxGroupField,
|
|
11
|
+
EmailField,
|
|
12
|
+
Form,
|
|
15
13
|
RadioField,
|
|
14
|
+
SelectField,
|
|
16
15
|
SelectOption,
|
|
16
|
+
TextField,
|
|
17
17
|
ValidationRule,
|
|
18
18
|
validate_form_data,
|
|
19
19
|
)
|
|
@@ -205,7 +205,9 @@ if __name__ == "__main__":
|
|
|
205
205
|
],
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
-
contact_form.set_default_values(
|
|
208
|
+
contact_form.set_default_values(
|
|
209
|
+
data={"name": "John Doe", "email": "john@example.com"}
|
|
210
|
+
)
|
|
209
211
|
|
|
210
212
|
# Export as Bootstrap 4 HTML
|
|
211
213
|
print(contact_form.export(output_format="html_bootstrap4", id="my_form"))
|