codeforms 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
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/__init__.py +25 -22
- codeforms/export.py +354 -101
- codeforms/fields.py +86 -38
- codeforms/forms.py +343 -318
- codeforms/i18n.py +1 -30
- codeforms/registry.py +43 -18
- codeforms-0.2.2.dist-info/METADATA +675 -0
- codeforms-0.2.2.dist-info/RECORD +10 -0
- {codeforms-0.2.0.dist-info → codeforms-0.2.2.dist-info}/WHEEL +1 -1
- codeforms-0.2.0.dist-info/METADATA +0 -325
- codeforms-0.2.0.dist-info/RECORD +0 -10
- {codeforms-0.2.0.dist-info → codeforms-0.2.2.dist-info}/licenses/LICENSE +0 -0
codeforms/i18n.py
CHANGED
|
@@ -28,33 +28,27 @@ from __future__ import annotations
|
|
|
28
28
|
import copy
|
|
29
29
|
from typing import Any, Dict, Optional
|
|
30
30
|
|
|
31
|
-
|
|
32
31
|
# --- Message catalogs per locale ---
|
|
33
32
|
|
|
34
33
|
_MESSAGES_EN: Dict[str, str] = {
|
|
35
34
|
# Field-level validation
|
|
36
35
|
"field.required": "This field is required",
|
|
37
36
|
"field.required_named": "The field {name} is required",
|
|
38
|
-
|
|
39
37
|
# TextField
|
|
40
38
|
"text.minlength": "Minimum length is {min}",
|
|
41
39
|
"text.maxlength": "Maximum length is {max}",
|
|
42
40
|
"text.pattern_mismatch": "Value does not match the required pattern",
|
|
43
41
|
"text.invalid_regex": "Invalid regex pattern",
|
|
44
|
-
|
|
45
42
|
# EmailField
|
|
46
43
|
"email.invalid": "Invalid email",
|
|
47
|
-
|
|
48
44
|
# NumberField
|
|
49
45
|
"number.min_value": "Value must be greater than or equal to {min}",
|
|
50
46
|
"number.max_value": "Value must be less than or equal to {max}",
|
|
51
47
|
"number.invalid": "Must be a valid number",
|
|
52
|
-
|
|
53
48
|
# DateField
|
|
54
49
|
"date.min_date": "Date must be after {min}",
|
|
55
50
|
"date.max_date": "Date must be before {max}",
|
|
56
51
|
"date.invalid_format": "Must be a valid date in YYYY-MM-DD format",
|
|
57
|
-
|
|
58
52
|
# SelectField
|
|
59
53
|
"select.invalid_option": "Invalid option selected",
|
|
60
54
|
"select.invalid_options": "Invalid options selected",
|
|
@@ -69,40 +63,32 @@ _MESSAGES_EN: Dict[str, str] = {
|
|
|
69
63
|
"select.max_selected_requires_multiple": "max_selected can only be used with multiple=True",
|
|
70
64
|
"select.max_less_than_min": "max_selected must be greater than or equal to min_selected",
|
|
71
65
|
"select.invalid_value_must_be_one_of": "Invalid value. Must be one of: {valid}",
|
|
72
|
-
|
|
73
66
|
# RadioField
|
|
74
67
|
"radio.invalid_option": "Invalid option selected",
|
|
75
68
|
"radio.default_must_be_string": "Default value must be a string",
|
|
76
|
-
|
|
77
69
|
# CheckboxField
|
|
78
70
|
"checkbox.must_be_boolean": "Must be a boolean value",
|
|
79
71
|
"checkbox.default_must_be_boolean": "Default value must be a boolean",
|
|
80
|
-
|
|
81
72
|
# CheckboxGroupField
|
|
82
73
|
"checkbox_group.default_must_be_list": "Default value must be a list of values",
|
|
83
74
|
"checkbox_group.invalid_options": "Invalid options selected",
|
|
84
|
-
|
|
85
75
|
# UrlField
|
|
86
76
|
"url.invalid_scheme": "URL must start with http:// or https://",
|
|
87
|
-
|
|
88
77
|
# Form-level
|
|
89
78
|
"form.unique_field_names": "Field names must be unique in the form",
|
|
90
79
|
"form.unique_field_names_in_group": "Field names must be unique within group '{title}'",
|
|
91
80
|
"form.validation_success": "Data validated successfully",
|
|
92
81
|
"form.validation_error": "Validation error",
|
|
93
82
|
"form.data_validation_error": "Data validation error",
|
|
94
|
-
|
|
95
83
|
# Export / HTML
|
|
96
84
|
"export.fix_errors": "Please fix the following errors:",
|
|
97
85
|
"export.submit": "Submit",
|
|
98
86
|
"export.field_required": "The field {label} is required",
|
|
99
|
-
|
|
100
87
|
# Wizard / Multi-step
|
|
101
88
|
"wizard.not_a_wizard_form": "This form is not configured as a wizard (no steps found)",
|
|
102
89
|
"wizard.invalid_step_index": "Invalid step index {index}, must be between 0 and {max}",
|
|
103
90
|
"wizard.step_validation_failed": "Validation failed for step {step}",
|
|
104
91
|
"wizard.validation_failed": "Wizard validation failed",
|
|
105
|
-
|
|
106
92
|
# Visibility
|
|
107
93
|
"visibility.unknown_operator": "Unknown visibility operator: {operator}",
|
|
108
94
|
}
|
|
@@ -111,26 +97,21 @@ _MESSAGES_ES: Dict[str, str] = {
|
|
|
111
97
|
# Validación a nivel de campo
|
|
112
98
|
"field.required": "Este campo es requerido",
|
|
113
99
|
"field.required_named": "El campo {name} es requerido",
|
|
114
|
-
|
|
115
100
|
# TextField
|
|
116
101
|
"text.minlength": "La longitud mínima es {min}",
|
|
117
102
|
"text.maxlength": "La longitud máxima es {max}",
|
|
118
103
|
"text.pattern_mismatch": "El valor no coincide con el patrón requerido",
|
|
119
104
|
"text.invalid_regex": "Patrón regex inválido",
|
|
120
|
-
|
|
121
105
|
# EmailField
|
|
122
106
|
"email.invalid": "Email inválido",
|
|
123
|
-
|
|
124
107
|
# NumberField
|
|
125
108
|
"number.min_value": "El valor debe ser mayor o igual a {min}",
|
|
126
109
|
"number.max_value": "El valor debe ser menor o igual a {max}",
|
|
127
110
|
"number.invalid": "Debe ser un número válido",
|
|
128
|
-
|
|
129
111
|
# DateField
|
|
130
112
|
"date.min_date": "La fecha debe ser posterior a {min}",
|
|
131
113
|
"date.max_date": "La fecha debe ser anterior a {max}",
|
|
132
114
|
"date.invalid_format": "Debe ser una fecha válida en formato YYYY-MM-DD",
|
|
133
|
-
|
|
134
115
|
# SelectField
|
|
135
116
|
"select.invalid_option": "Opción inválida seleccionada",
|
|
136
117
|
"select.invalid_options": "Opciones inválidas seleccionadas",
|
|
@@ -145,40 +126,32 @@ _MESSAGES_ES: Dict[str, str] = {
|
|
|
145
126
|
"select.max_selected_requires_multiple": "max_selected solo puede usarse con multiple=True",
|
|
146
127
|
"select.max_less_than_min": "max_selected debe ser mayor o igual que min_selected",
|
|
147
128
|
"select.invalid_value_must_be_one_of": "Valor inválido. Debe ser uno de: {valid}",
|
|
148
|
-
|
|
149
129
|
# RadioField
|
|
150
130
|
"radio.invalid_option": "Opción inválida seleccionada",
|
|
151
131
|
"radio.default_must_be_string": "El valor por defecto debe ser una cadena",
|
|
152
|
-
|
|
153
132
|
# CheckboxField
|
|
154
133
|
"checkbox.must_be_boolean": "Debe ser un valor booleano",
|
|
155
134
|
"checkbox.default_must_be_boolean": "El valor por defecto debe ser un booleano",
|
|
156
|
-
|
|
157
135
|
# CheckboxGroupField
|
|
158
136
|
"checkbox_group.default_must_be_list": "El valor por defecto debe ser una lista de valores",
|
|
159
137
|
"checkbox_group.invalid_options": "Opciones inválidas seleccionadas",
|
|
160
|
-
|
|
161
138
|
# UrlField
|
|
162
139
|
"url.invalid_scheme": "La URL debe comenzar con http:// o https://",
|
|
163
|
-
|
|
164
140
|
# Formulario
|
|
165
141
|
"form.unique_field_names": "Los nombres de los campos deben ser únicos en el formulario",
|
|
166
142
|
"form.unique_field_names_in_group": "Los nombres de los campos deben ser únicos dentro del grupo '{title}'",
|
|
167
143
|
"form.validation_success": "Datos validados correctamente",
|
|
168
144
|
"form.validation_error": "Error en la validación",
|
|
169
145
|
"form.data_validation_error": "Error en la validación de datos",
|
|
170
|
-
|
|
171
146
|
# Exportación / HTML
|
|
172
147
|
"export.fix_errors": "Por favor corrija los siguientes errores:",
|
|
173
148
|
"export.submit": "Enviar",
|
|
174
149
|
"export.field_required": "El campo {label} es requerido",
|
|
175
|
-
|
|
176
150
|
# Wizard / Multi-paso
|
|
177
151
|
"wizard.not_a_wizard_form": "Este formulario no está configurado como wizard (no se encontraron pasos)",
|
|
178
152
|
"wizard.invalid_step_index": "Índice de paso inválido {index}, debe estar entre 0 y {max}",
|
|
179
153
|
"wizard.step_validation_failed": "La validación falló para el paso {step}",
|
|
180
154
|
"wizard.validation_failed": "La validación del wizard falló",
|
|
181
|
-
|
|
182
155
|
# Visibilidad
|
|
183
156
|
"visibility.unknown_operator": "Operador de visibilidad desconocido: {operator}",
|
|
184
157
|
}
|
|
@@ -215,9 +188,7 @@ def set_locale(locale: str) -> None:
|
|
|
215
188
|
global _current_locale
|
|
216
189
|
if locale not in _locales:
|
|
217
190
|
available = ", ".join(sorted(_locales.keys()))
|
|
218
|
-
raise ValueError(
|
|
219
|
-
f"Unknown locale '{locale}'. Available locales: {available}"
|
|
220
|
-
)
|
|
191
|
+
raise ValueError(f"Unknown locale '{locale}'. Available locales: {available}")
|
|
221
192
|
_current_locale = locale
|
|
222
193
|
|
|
223
194
|
|
codeforms/registry.py
CHANGED
|
@@ -17,7 +17,7 @@ Usage:
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import Any, Dict, List, Type
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Type
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from codeforms.fields import FormFieldBase
|
|
@@ -33,15 +33,16 @@ _registry_initialized: bool = False
|
|
|
33
33
|
# Internal helpers
|
|
34
34
|
# ---------------------------------------------------------------------------
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
def _get_field_type_key(cls: Type[FormFieldBase]) -> str:
|
|
37
38
|
"""Extract the field_type default value as a plain string."""
|
|
38
|
-
field_info = cls.model_fields.get(
|
|
39
|
+
field_info = cls.model_fields.get("field_type")
|
|
39
40
|
if field_info is None:
|
|
40
41
|
raise ValueError(f"{cls.__name__} has no 'field_type' field")
|
|
41
42
|
default = field_info.default
|
|
42
43
|
if default is None:
|
|
43
44
|
raise ValueError(f"{cls.__name__} must define a default field_type value")
|
|
44
|
-
return default.value if hasattr(default,
|
|
45
|
+
return default.value if hasattr(default, "value") else str(default)
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def _register_class(cls: Type[FormFieldBase]) -> None:
|
|
@@ -61,15 +62,37 @@ def _init_builtin_types() -> None:
|
|
|
61
62
|
_registry_initialized = True
|
|
62
63
|
|
|
63
64
|
from codeforms.fields import (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
CheckboxField,
|
|
66
|
+
CheckboxGroupField,
|
|
67
|
+
DateField,
|
|
68
|
+
EmailField,
|
|
69
|
+
FileField,
|
|
70
|
+
HiddenField,
|
|
71
|
+
ListField,
|
|
72
|
+
ObjectListField,
|
|
73
|
+
NumberField,
|
|
74
|
+
RadioField,
|
|
75
|
+
SelectField,
|
|
76
|
+
TextareaField,
|
|
77
|
+
TextField,
|
|
78
|
+
UrlField,
|
|
67
79
|
)
|
|
68
80
|
|
|
69
81
|
for cls in [
|
|
70
|
-
TextField,
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
TextField,
|
|
83
|
+
EmailField,
|
|
84
|
+
NumberField,
|
|
85
|
+
DateField,
|
|
86
|
+
SelectField,
|
|
87
|
+
RadioField,
|
|
88
|
+
CheckboxField,
|
|
89
|
+
CheckboxGroupField,
|
|
90
|
+
FileField,
|
|
91
|
+
HiddenField,
|
|
92
|
+
UrlField,
|
|
93
|
+
TextareaField,
|
|
94
|
+
ListField,
|
|
95
|
+
ObjectListField,
|
|
73
96
|
]:
|
|
74
97
|
_register_class(cls)
|
|
75
98
|
|
|
@@ -78,6 +101,7 @@ def _init_builtin_types() -> None:
|
|
|
78
101
|
# Public API
|
|
79
102
|
# ---------------------------------------------------------------------------
|
|
80
103
|
|
|
104
|
+
|
|
81
105
|
def register_field_type(field_class: Type[FormFieldBase]) -> None:
|
|
82
106
|
"""
|
|
83
107
|
Register a custom field type so it can be used in Form and FieldGroup.
|
|
@@ -104,10 +128,9 @@ def register_field_type(field_class: Type[FormFieldBase]) -> None:
|
|
|
104
128
|
"""
|
|
105
129
|
_init_builtin_types()
|
|
106
130
|
from codeforms.fields import FormFieldBase as _Base
|
|
131
|
+
|
|
107
132
|
if not issubclass(field_class, _Base):
|
|
108
|
-
raise TypeError(
|
|
109
|
-
f"{field_class.__name__} must be a subclass of FormFieldBase"
|
|
110
|
-
)
|
|
133
|
+
raise TypeError(f"{field_class.__name__} must be a subclass of FormFieldBase")
|
|
111
134
|
_register_class(field_class)
|
|
112
135
|
|
|
113
136
|
|
|
@@ -153,25 +176,27 @@ def resolve_content_item(item: Any) -> Any:
|
|
|
153
176
|
return item # already an instance
|
|
154
177
|
|
|
155
178
|
# (1) Explicit container type discriminator
|
|
156
|
-
if
|
|
157
|
-
container_type = item[
|
|
158
|
-
if container_type ==
|
|
179
|
+
if "type" in item:
|
|
180
|
+
container_type = item["type"]
|
|
181
|
+
if container_type == "step":
|
|
159
182
|
from codeforms.fields import FormStep
|
|
183
|
+
|
|
160
184
|
return FormStep.model_validate(item)
|
|
161
185
|
# Unknown type values fall through to existing heuristics
|
|
162
186
|
|
|
163
187
|
# (2) Legacy FieldGroup heuristic (has 'title', no 'field_type')
|
|
164
|
-
if
|
|
188
|
+
if "title" in item and "field_type" not in item:
|
|
165
189
|
from codeforms.fields import FieldGroup
|
|
190
|
+
|
|
166
191
|
return FieldGroup.model_validate(item)
|
|
167
192
|
|
|
168
193
|
# (3) Field type registry lookup
|
|
169
|
-
field_type = item.get(
|
|
194
|
+
field_type = item.get("field_type")
|
|
170
195
|
if field_type is None:
|
|
171
196
|
return item
|
|
172
197
|
|
|
173
198
|
# Normalise enum → string
|
|
174
|
-
if hasattr(field_type,
|
|
199
|
+
if hasattr(field_type, "value"):
|
|
175
200
|
field_type = field_type.value
|
|
176
201
|
|
|
177
202
|
candidates = _field_type_registry.get(field_type)
|