codeforms 0.1.0__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 +30 -0
- codeforms/export.py +331 -0
- codeforms/fields.py +298 -0
- codeforms/forms.py +530 -0
- codeforms/i18n.py +264 -0
- codeforms-0.1.0.dist-info/METADATA +263 -0
- codeforms-0.1.0.dist-info/RECORD +9 -0
- codeforms-0.1.0.dist-info/WHEEL +4 -0
- codeforms-0.1.0.dist-info/licenses/LICENSE +21 -0
codeforms/i18n.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internationalization (i18n) module for codeforms.
|
|
3
|
+
|
|
4
|
+
Provides a configurable message system with locale-based message loading.
|
|
5
|
+
Default locale is English ('en'). Spanish ('es') is also included.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from codeforms.i18n import set_locale, get_locale, t, register_locale
|
|
9
|
+
|
|
10
|
+
# Change locale globally
|
|
11
|
+
set_locale('es')
|
|
12
|
+
|
|
13
|
+
# Get a translated message
|
|
14
|
+
msg = t('field.required') # "Este campo es requerido"
|
|
15
|
+
|
|
16
|
+
# With interpolation
|
|
17
|
+
msg = t('field.required_named', name='email') # "El campo email es requerido"
|
|
18
|
+
|
|
19
|
+
# Register a custom locale
|
|
20
|
+
register_locale('fr', {
|
|
21
|
+
'field.required': 'Ce champ est obligatoire',
|
|
22
|
+
...
|
|
23
|
+
})
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import copy
|
|
29
|
+
from typing import Any, Dict, Optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# --- Message catalogs per locale ---
|
|
33
|
+
|
|
34
|
+
_MESSAGES_EN: Dict[str, str] = {
|
|
35
|
+
# Field-level validation
|
|
36
|
+
"field.required": "This field is required",
|
|
37
|
+
"field.required_named": "The field {name} is required",
|
|
38
|
+
|
|
39
|
+
# TextField
|
|
40
|
+
"text.minlength": "Minimum length is {min}",
|
|
41
|
+
"text.maxlength": "Maximum length is {max}",
|
|
42
|
+
"text.pattern_mismatch": "Value does not match the required pattern",
|
|
43
|
+
"text.invalid_regex": "Invalid regex pattern",
|
|
44
|
+
|
|
45
|
+
# EmailField
|
|
46
|
+
"email.invalid": "Invalid email",
|
|
47
|
+
|
|
48
|
+
# NumberField
|
|
49
|
+
"number.min_value": "Value must be greater than or equal to {min}",
|
|
50
|
+
"number.max_value": "Value must be less than or equal to {max}",
|
|
51
|
+
"number.invalid": "Must be a valid number",
|
|
52
|
+
|
|
53
|
+
# DateField
|
|
54
|
+
"date.min_date": "Date must be after {min}",
|
|
55
|
+
"date.max_date": "Date must be before {max}",
|
|
56
|
+
"date.invalid_format": "Must be a valid date in YYYY-MM-DD format",
|
|
57
|
+
|
|
58
|
+
# SelectField
|
|
59
|
+
"select.invalid_option": "Invalid option selected",
|
|
60
|
+
"select.invalid_options": "Invalid options selected",
|
|
61
|
+
"select.invalid_option_value": "Invalid option: {value}. Must be one of: {valid}",
|
|
62
|
+
"select.invalid_values": "Invalid values: {values}",
|
|
63
|
+
"select.min_selected": "Must select at least {min} options",
|
|
64
|
+
"select.max_selected": "Can select at most {max} options",
|
|
65
|
+
"select.value_must_be_list": "Value must be a list",
|
|
66
|
+
"select.min_selected_negative": "min_selected cannot be negative",
|
|
67
|
+
"select.min_selected_requires_multiple": "min_selected can only be used with multiple=True",
|
|
68
|
+
"select.max_selected_min_value": "max_selected must be greater than 0",
|
|
69
|
+
"select.max_selected_requires_multiple": "max_selected can only be used with multiple=True",
|
|
70
|
+
"select.max_less_than_min": "max_selected must be greater than or equal to min_selected",
|
|
71
|
+
"select.invalid_value_must_be_one_of": "Invalid value. Must be one of: {valid}",
|
|
72
|
+
|
|
73
|
+
# RadioField
|
|
74
|
+
"radio.invalid_option": "Invalid option selected",
|
|
75
|
+
"radio.default_must_be_string": "Default value must be a string",
|
|
76
|
+
|
|
77
|
+
# CheckboxField
|
|
78
|
+
"checkbox.must_be_boolean": "Must be a boolean value",
|
|
79
|
+
"checkbox.default_must_be_boolean": "Default value must be a boolean",
|
|
80
|
+
|
|
81
|
+
# CheckboxGroupField
|
|
82
|
+
"checkbox_group.default_must_be_list": "Default value must be a list of values",
|
|
83
|
+
"checkbox_group.invalid_options": "Invalid options selected",
|
|
84
|
+
|
|
85
|
+
# UrlField
|
|
86
|
+
"url.invalid_scheme": "URL must start with http:// or https://",
|
|
87
|
+
|
|
88
|
+
# Form-level
|
|
89
|
+
"form.unique_field_names": "Field names must be unique in the form",
|
|
90
|
+
"form.unique_field_names_in_group": "Field names must be unique within group '{title}'",
|
|
91
|
+
"form.validation_success": "Data validated successfully",
|
|
92
|
+
"form.validation_error": "Validation error",
|
|
93
|
+
"form.data_validation_error": "Data validation error",
|
|
94
|
+
|
|
95
|
+
# Export / HTML
|
|
96
|
+
"export.fix_errors": "Please fix the following errors:",
|
|
97
|
+
"export.submit": "Submit",
|
|
98
|
+
"export.field_required": "The field {label} is required",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_MESSAGES_ES: Dict[str, str] = {
|
|
102
|
+
# Validación a nivel de campo
|
|
103
|
+
"field.required": "Este campo es requerido",
|
|
104
|
+
"field.required_named": "El campo {name} es requerido",
|
|
105
|
+
|
|
106
|
+
# TextField
|
|
107
|
+
"text.minlength": "La longitud mínima es {min}",
|
|
108
|
+
"text.maxlength": "La longitud máxima es {max}",
|
|
109
|
+
"text.pattern_mismatch": "El valor no coincide con el patrón requerido",
|
|
110
|
+
"text.invalid_regex": "Patrón regex inválido",
|
|
111
|
+
|
|
112
|
+
# EmailField
|
|
113
|
+
"email.invalid": "Email inválido",
|
|
114
|
+
|
|
115
|
+
# NumberField
|
|
116
|
+
"number.min_value": "El valor debe ser mayor o igual a {min}",
|
|
117
|
+
"number.max_value": "El valor debe ser menor o igual a {max}",
|
|
118
|
+
"number.invalid": "Debe ser un número válido",
|
|
119
|
+
|
|
120
|
+
# DateField
|
|
121
|
+
"date.min_date": "La fecha debe ser posterior a {min}",
|
|
122
|
+
"date.max_date": "La fecha debe ser anterior a {max}",
|
|
123
|
+
"date.invalid_format": "Debe ser una fecha válida en formato YYYY-MM-DD",
|
|
124
|
+
|
|
125
|
+
# SelectField
|
|
126
|
+
"select.invalid_option": "Opción inválida seleccionada",
|
|
127
|
+
"select.invalid_options": "Opciones inválidas seleccionadas",
|
|
128
|
+
"select.invalid_option_value": "Opción inválida: {value}. Debe ser una de: {valid}",
|
|
129
|
+
"select.invalid_values": "Valores inválidos: {values}",
|
|
130
|
+
"select.min_selected": "Debe seleccionar al menos {min} opciones",
|
|
131
|
+
"select.max_selected": "Puede seleccionar máximo {max} opciones",
|
|
132
|
+
"select.value_must_be_list": "El valor debe ser una lista",
|
|
133
|
+
"select.min_selected_negative": "min_selected no puede ser negativo",
|
|
134
|
+
"select.min_selected_requires_multiple": "min_selected solo puede usarse con multiple=True",
|
|
135
|
+
"select.max_selected_min_value": "max_selected debe ser mayor que 0",
|
|
136
|
+
"select.max_selected_requires_multiple": "max_selected solo puede usarse con multiple=True",
|
|
137
|
+
"select.max_less_than_min": "max_selected debe ser mayor o igual que min_selected",
|
|
138
|
+
"select.invalid_value_must_be_one_of": "Valor inválido. Debe ser uno de: {valid}",
|
|
139
|
+
|
|
140
|
+
# RadioField
|
|
141
|
+
"radio.invalid_option": "Opción inválida seleccionada",
|
|
142
|
+
"radio.default_must_be_string": "El valor por defecto debe ser una cadena",
|
|
143
|
+
|
|
144
|
+
# CheckboxField
|
|
145
|
+
"checkbox.must_be_boolean": "Debe ser un valor booleano",
|
|
146
|
+
"checkbox.default_must_be_boolean": "El valor por defecto debe ser un booleano",
|
|
147
|
+
|
|
148
|
+
# CheckboxGroupField
|
|
149
|
+
"checkbox_group.default_must_be_list": "El valor por defecto debe ser una lista de valores",
|
|
150
|
+
"checkbox_group.invalid_options": "Opciones inválidas seleccionadas",
|
|
151
|
+
|
|
152
|
+
# UrlField
|
|
153
|
+
"url.invalid_scheme": "La URL debe comenzar con http:// o https://",
|
|
154
|
+
|
|
155
|
+
# Formulario
|
|
156
|
+
"form.unique_field_names": "Los nombres de los campos deben ser únicos en el formulario",
|
|
157
|
+
"form.unique_field_names_in_group": "Los nombres de los campos deben ser únicos dentro del grupo '{title}'",
|
|
158
|
+
"form.validation_success": "Datos validados correctamente",
|
|
159
|
+
"form.validation_error": "Error en la validación",
|
|
160
|
+
"form.data_validation_error": "Error en la validación de datos",
|
|
161
|
+
|
|
162
|
+
# Exportación / HTML
|
|
163
|
+
"export.fix_errors": "Por favor corrija los siguientes errores:",
|
|
164
|
+
"export.submit": "Enviar",
|
|
165
|
+
"export.field_required": "El campo {label} es requerido",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# --- Registry ---
|
|
170
|
+
|
|
171
|
+
_locales: Dict[str, Dict[str, str]] = {
|
|
172
|
+
"en": _MESSAGES_EN,
|
|
173
|
+
"es": _MESSAGES_ES,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_current_locale: str = "en"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# --- Public API ---
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_locale() -> str:
|
|
183
|
+
"""Return the current locale code (e.g. 'en', 'es')."""
|
|
184
|
+
return _current_locale
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def set_locale(locale: str) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Set the current locale.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
locale: A locale code that has been registered (e.g. 'en', 'es').
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
ValueError: If the locale has not been registered.
|
|
196
|
+
"""
|
|
197
|
+
global _current_locale
|
|
198
|
+
if locale not in _locales:
|
|
199
|
+
available = ", ".join(sorted(_locales.keys()))
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Unknown locale '{locale}'. Available locales: {available}"
|
|
202
|
+
)
|
|
203
|
+
_current_locale = locale
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_available_locales() -> list[str]:
|
|
207
|
+
"""Return a sorted list of registered locale codes."""
|
|
208
|
+
return sorted(_locales.keys())
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def register_locale(locale: str, messages: Dict[str, str]) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Register a new locale or update an existing one.
|
|
214
|
+
|
|
215
|
+
If the locale already exists, its messages are *merged* (new keys are
|
|
216
|
+
added, existing keys are overwritten).
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
locale: Locale code (e.g. 'fr', 'pt').
|
|
220
|
+
messages: Dict mapping message keys to translated strings.
|
|
221
|
+
"""
|
|
222
|
+
if locale in _locales:
|
|
223
|
+
_locales[locale].update(messages)
|
|
224
|
+
else:
|
|
225
|
+
_locales[locale] = dict(messages)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_messages(locale: Optional[str] = None) -> Dict[str, str]:
|
|
229
|
+
"""
|
|
230
|
+
Return a *copy* of the message catalog for the given locale.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
locale: Locale code. Defaults to the current locale.
|
|
234
|
+
"""
|
|
235
|
+
loc = locale or _current_locale
|
|
236
|
+
return copy.copy(_locales.get(loc, _locales["en"]))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def t(key: str, **kwargs: Any) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Translate a message key using the current locale.
|
|
242
|
+
|
|
243
|
+
Supports Python ``str.format()`` interpolation via keyword arguments.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
key: Message key (e.g. ``'field.required_named'``).
|
|
247
|
+
**kwargs: Interpolation values (e.g. ``name='email'``).
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The translated (and optionally interpolated) string.
|
|
251
|
+
If the key is not found in the current locale, falls back to the
|
|
252
|
+
English catalog. If still not found, returns the key itself.
|
|
253
|
+
"""
|
|
254
|
+
messages = _locales.get(_current_locale, _locales["en"])
|
|
255
|
+
template = messages.get(key)
|
|
256
|
+
if template is None:
|
|
257
|
+
# Fallback to English
|
|
258
|
+
template = _MESSAGES_EN.get(key, key)
|
|
259
|
+
if kwargs:
|
|
260
|
+
try:
|
|
261
|
+
return template.format(**kwargs)
|
|
262
|
+
except (KeyError, IndexError):
|
|
263
|
+
return template
|
|
264
|
+
return template
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codeforms
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library for creating, validating, and rendering web forms using Pydantic
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: pydantic[email]>=2.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# codeforms
|
|
14
|
+
|
|
15
|
+
A Python library for dynamically creating, validating, and rendering web forms using [Pydantic](https://docs.pydantic.dev/).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install codeforms
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add codeforms
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python 3.12+.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Creating a Form
|
|
34
|
+
|
|
35
|
+
Everything starts with the `Form` class. A form is defined with a name and a list of fields.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from codeforms import Form, TextField, EmailField, NumberField
|
|
39
|
+
|
|
40
|
+
form = Form(
|
|
41
|
+
name="UserRegistration",
|
|
42
|
+
fields=[
|
|
43
|
+
TextField(name="full_name", label="Full Name", required=True),
|
|
44
|
+
EmailField(name="email", label="Email", required=True),
|
|
45
|
+
NumberField(name="age", label="Age"),
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### The `Form` Class
|
|
51
|
+
|
|
52
|
+
The `Form` class is the main container for your form structure.
|
|
53
|
+
|
|
54
|
+
- `id` — Auto-generated UUID.
|
|
55
|
+
- `name` — Form name (used in HTML export and validation).
|
|
56
|
+
- `fields` — A list of field objects (e.g. `TextField`, `EmailField`).
|
|
57
|
+
- `css_classes` — Optional CSS classes for the `<form>` tag.
|
|
58
|
+
- `version` — Form version number.
|
|
59
|
+
- `attributes` — Dictionary of additional HTML attributes for the `<form>` tag.
|
|
60
|
+
|
|
61
|
+
## Field Types
|
|
62
|
+
|
|
63
|
+
All fields inherit from `FormFieldBase` and share these common attributes:
|
|
64
|
+
|
|
65
|
+
- `name` — Field name (maps to `name` in HTML).
|
|
66
|
+
- `label` — User-visible label.
|
|
67
|
+
- `field_type` — Field type (`FieldType` enum).
|
|
68
|
+
- `required` — Whether the field is mandatory.
|
|
69
|
+
- `placeholder` — Placeholder text inside the field.
|
|
70
|
+
- `default_value` — Default value.
|
|
71
|
+
- `help_text` — Help text displayed below the field.
|
|
72
|
+
- `css_classes` — CSS classes for the field element.
|
|
73
|
+
- `readonly` — Whether the field is read-only.
|
|
74
|
+
- `attributes` — Additional HTML attributes for the `<input>` tag.
|
|
75
|
+
|
|
76
|
+
### Available Fields
|
|
77
|
+
|
|
78
|
+
- **`TextField`** — Generic text input (`<input type="text">`).
|
|
79
|
+
- `minlength`, `maxlength`: Min/max text length.
|
|
80
|
+
- `pattern`: Regex pattern for validation.
|
|
81
|
+
- **`EmailField`** — Email address (`<input type="email">`).
|
|
82
|
+
- **`NumberField`** — Numeric value (`<input type="number">`).
|
|
83
|
+
- `min_value`, `max_value`: Allowed value range.
|
|
84
|
+
- `step`: Increment step.
|
|
85
|
+
- **`DateField`** — Date picker (`<input type="date">`).
|
|
86
|
+
- `min_date`, `max_date`: Allowed date range.
|
|
87
|
+
- **`SelectField`** — Dropdown select (`<select>`).
|
|
88
|
+
- `options`: List of `SelectOption(value="...", label="...")`.
|
|
89
|
+
- `multiple`: Enables multi-select.
|
|
90
|
+
- `min_selected`, `max_selected`: Selection count limits (multi-select only).
|
|
91
|
+
- **`RadioField`** — Radio buttons (`<input type="radio">`).
|
|
92
|
+
- `options`: List of `SelectOption`.
|
|
93
|
+
- `inline`: Display options inline.
|
|
94
|
+
- **`CheckboxField`** — Single checkbox (`<input type="checkbox">`).
|
|
95
|
+
- **`CheckboxGroupField`** — Group of checkboxes.
|
|
96
|
+
- `options`: List of `SelectOption`.
|
|
97
|
+
- `inline`: Display options inline.
|
|
98
|
+
- **`FileField`** — File upload (`<input type="file">`).
|
|
99
|
+
- `accept`: Accepted file types (e.g. `"image/*,.pdf"`).
|
|
100
|
+
- `multiple`: Allow multiple file uploads.
|
|
101
|
+
- **`HiddenField`** — Hidden field (`<input type="hidden">`).
|
|
102
|
+
|
|
103
|
+
## Data Validation
|
|
104
|
+
|
|
105
|
+
codeforms offers multiple ways to validate user-submitted data, leveraging Pydantic's validation engine.
|
|
106
|
+
|
|
107
|
+
### Recommended: `FormDataValidator`
|
|
108
|
+
|
|
109
|
+
The most robust approach is `FormDataValidator.create_model`, which dynamically generates a Pydantic model from your form definition. This gives you powerful validations and detailed error messages automatically.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from codeforms import Form, FormDataValidator, TextField, SelectField, SelectOption
|
|
113
|
+
from pydantic import ValidationError
|
|
114
|
+
|
|
115
|
+
# 1. Define your form
|
|
116
|
+
form = Form(
|
|
117
|
+
name="MyForm",
|
|
118
|
+
fields=[
|
|
119
|
+
TextField(name="name", label="Name", required=True),
|
|
120
|
+
SelectField(
|
|
121
|
+
name="country",
|
|
122
|
+
label="Country",
|
|
123
|
+
options=[
|
|
124
|
+
SelectOption(value="us", label="United States"),
|
|
125
|
+
SelectOption(value="uk", label="United Kingdom"),
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# 2. Create the validation model
|
|
132
|
+
ValidationModel = FormDataValidator.create_model(form)
|
|
133
|
+
|
|
134
|
+
# 3. Validate incoming data
|
|
135
|
+
user_data = {"name": "John", "country": "us"}
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
validated = ValidationModel.model_validate(user_data)
|
|
139
|
+
print("Valid!", validated)
|
|
140
|
+
except ValidationError as e:
|
|
141
|
+
print("Validation errors:", e.errors())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This approach integrates seamlessly with API backends like FastAPI or Flask, since it produces standard Pydantic models.
|
|
145
|
+
|
|
146
|
+
### Other Validation Methods
|
|
147
|
+
|
|
148
|
+
Two simpler alternatives exist, though `FormDataValidator` is preferred:
|
|
149
|
+
|
|
150
|
+
1. `form.validate_data(data)` — Built-in method on the `Form` class. Less flexible; doesn't produce Pydantic models.
|
|
151
|
+
2. `validate_form_data(form, data)` — Standalone function with basic validation logic.
|
|
152
|
+
|
|
153
|
+
## Exporting Forms
|
|
154
|
+
|
|
155
|
+
Once your form is defined, you can export it to different formats.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Export to plain HTML
|
|
159
|
+
html_output = form.export('html', submit=True)
|
|
160
|
+
print(html_output['output'])
|
|
161
|
+
|
|
162
|
+
# Export to HTML with Bootstrap 5 classes
|
|
163
|
+
bootstrap_output = form.export('html_bootstrap5', submit=True)
|
|
164
|
+
print(bootstrap_output['output'])
|
|
165
|
+
|
|
166
|
+
# Export to JSON
|
|
167
|
+
json_output = form.to_json()
|
|
168
|
+
print(json_output)
|
|
169
|
+
|
|
170
|
+
# Export to a Python dictionary
|
|
171
|
+
dict_output = form.to_dict()
|
|
172
|
+
print(dict_output)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Supported Formats
|
|
176
|
+
|
|
177
|
+
| Format | Description |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `html` | Semantic HTML |
|
|
180
|
+
| `html_bootstrap4` | HTML with Bootstrap 4 classes |
|
|
181
|
+
| `html_bootstrap5` | HTML with Bootstrap 5 classes |
|
|
182
|
+
| `json` | JSON representation of the form |
|
|
183
|
+
| `dict` | Python dictionary representation |
|
|
184
|
+
|
|
185
|
+
HTML export can also generate a `<script>` block for basic client-side validation.
|
|
186
|
+
|
|
187
|
+
## Internationalization (i18n)
|
|
188
|
+
|
|
189
|
+
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()`.
|
|
190
|
+
|
|
191
|
+
### Switching Locales
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from codeforms import set_locale, get_locale, get_available_locales
|
|
195
|
+
|
|
196
|
+
print(get_locale()) # "en"
|
|
197
|
+
print(get_available_locales()) # ["en", "es"]
|
|
198
|
+
|
|
199
|
+
set_locale("es")
|
|
200
|
+
# All validation messages will now be in Spanish
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Registering a Custom Locale
|
|
204
|
+
|
|
205
|
+
You can add any locale at runtime. Missing keys automatically fall back to English.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from codeforms import register_locale, set_locale
|
|
209
|
+
|
|
210
|
+
register_locale("pt", {
|
|
211
|
+
"field.required": "Este campo é obrigatório",
|
|
212
|
+
"field.required_named": "O campo {name} é obrigatório",
|
|
213
|
+
"email.invalid": "E-mail inválido",
|
|
214
|
+
"number.min_value": "O valor deve ser maior ou igual a {min}",
|
|
215
|
+
"form.validation_success": "Dados validados com sucesso",
|
|
216
|
+
"form.data_validation_error": "Erro na validação dos dados",
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
set_locale("pt")
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Using the Translation Function
|
|
223
|
+
|
|
224
|
+
The `t()` function translates a message key, with optional interpolation:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from codeforms import t, set_locale
|
|
228
|
+
|
|
229
|
+
set_locale("en")
|
|
230
|
+
print(t("field.required")) # "This field is required"
|
|
231
|
+
print(t("field.required_named", name="email")) # "The field email is required"
|
|
232
|
+
|
|
233
|
+
set_locale("es")
|
|
234
|
+
print(t("field.required")) # "Este campo es requerido"
|
|
235
|
+
print(t("text.minlength", min=3)) # "La longitud mínima es 3"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Locale-Aware Validation
|
|
239
|
+
|
|
240
|
+
All validation functions respect the active locale:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from codeforms import Form, TextField, validate_form_data, set_locale
|
|
244
|
+
|
|
245
|
+
form = Form(
|
|
246
|
+
name="example",
|
|
247
|
+
fields=[TextField(name="name", label="Name", required=True)]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
set_locale("en")
|
|
251
|
+
result = validate_form_data(form, {})
|
|
252
|
+
print(result["errors"][0]["message"]) # "The field name is required"
|
|
253
|
+
|
|
254
|
+
set_locale("es")
|
|
255
|
+
result = validate_form_data(form, {})
|
|
256
|
+
print(result["errors"][0]["message"]) # "El campo name es requerido"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
See [`examples/i18n_usage.py`](examples/i18n_usage.py) for a full working example.
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
codeforms/__init__.py,sha256=sk4cFfqYAd5LzAgkL35Wm-0uHtYAksMICX6ak5Y0DI8,590
|
|
2
|
+
codeforms/export.py,sha256=CCJJm6XxoUoPh88PGJBiO7KePD8tXvhq2mqJWSuy-_c,12472
|
|
3
|
+
codeforms/fields.py,sha256=gijcydSB4dbABN76cUxpi8KhtUnTrW15YGzDVYjqduE,9343
|
|
4
|
+
codeforms/forms.py,sha256=lzsKXQdbun-gc7y6D5DjjNf2OxMFnUd4H_0JsPW8KA0,23383
|
|
5
|
+
codeforms/i18n.py,sha256=_wuPwelKgLFac_vW9AeYa-MR2AAoLIlNbNTXV80CYwM,9510
|
|
6
|
+
codeforms-0.1.0.dist-info/METADATA,sha256=Q2cYBCMbe7ack6GcKx2HAxNkKFFJWzfZrr6ysjKTdKY,8158
|
|
7
|
+
codeforms-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
codeforms-0.1.0.dist-info/licenses/LICENSE,sha256=U9D6cQ9DZJQrT03MMDJcq-aOuoB_Bn8HJlzcTTN7PfM,1074
|
|
9
|
+
codeforms-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Pablo Manson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|