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/forms.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
from codeforms.fields import *
|
|
2
|
+
from codeforms.i18n import t
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
class Form(BaseModel):
|
|
8
|
+
id: UUID = Field(default_factory=uuid4)
|
|
9
|
+
name: str
|
|
10
|
+
content: List[Union[
|
|
11
|
+
TextField,
|
|
12
|
+
EmailField,
|
|
13
|
+
NumberField,
|
|
14
|
+
DateField,
|
|
15
|
+
SelectField,
|
|
16
|
+
RadioField,
|
|
17
|
+
CheckboxField,
|
|
18
|
+
CheckboxGroupField,
|
|
19
|
+
FileField,
|
|
20
|
+
HiddenField,
|
|
21
|
+
'FieldGroup'
|
|
22
|
+
]]
|
|
23
|
+
css_classes: Optional[str] = None
|
|
24
|
+
version: int = 1
|
|
25
|
+
attributes: Dict[str, str] = Field(default_factory=dict)
|
|
26
|
+
action: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
@model_validator(mode='before')
|
|
29
|
+
@classmethod
|
|
30
|
+
def convert_fields_to_content(cls, data: Any) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Validador para mantener retrocompatibilidad.
|
|
33
|
+
Convierte automáticamente 'fields' a 'content' si está presente.
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(data, dict):
|
|
36
|
+
# Si tiene 'fields' pero no 'content', convertir
|
|
37
|
+
if 'fields' in data and 'content' not in data:
|
|
38
|
+
data = data.copy()
|
|
39
|
+
data['content'] = data.pop('fields')
|
|
40
|
+
# Si tiene ambos, 'content' tiene prioridad
|
|
41
|
+
elif 'fields' in data and 'content' in data:
|
|
42
|
+
data = data.copy()
|
|
43
|
+
data.pop('fields') # Remover 'fields' redundante
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def fields(self) -> List[Union[TextField, EmailField, NumberField, DateField, SelectField, RadioField, CheckboxField, CheckboxGroupField, FileField, HiddenField]]:
|
|
48
|
+
"""
|
|
49
|
+
Devuelve una lista plana de todos los campos del formulario,
|
|
50
|
+
independientemente de si están en un grupo o no.
|
|
51
|
+
Mantiene retrocompatibilidad con el código existente.
|
|
52
|
+
"""
|
|
53
|
+
all_fields = []
|
|
54
|
+
for item in self.content:
|
|
55
|
+
if hasattr(item, 'fields') and hasattr(item, 'title'): # Es un FieldGroup
|
|
56
|
+
all_fields.extend(item.fields)
|
|
57
|
+
else: # Es un campo individual
|
|
58
|
+
all_fields.append(item)
|
|
59
|
+
return all_fields
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def loads(form: Union[str, dict, bytearray]):
|
|
63
|
+
if isinstance(form, (str, bytearray)):
|
|
64
|
+
return Form.model_validate_json(form)
|
|
65
|
+
else:
|
|
66
|
+
return Form.model_validate(form)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def create_from_fields(cls, name: str, fields: List, **kwargs) -> 'Form':
|
|
70
|
+
"""
|
|
71
|
+
Método de conveniencia para crear un formulario usando la estructura anterior
|
|
72
|
+
donde se pasaba directamente una lista de campos.
|
|
73
|
+
Mantiene retrocompatibilidad.
|
|
74
|
+
"""
|
|
75
|
+
return cls(name=name, content=fields, **kwargs)
|
|
76
|
+
|
|
77
|
+
def to_dict(self, exclude_none: bool=True) -> Dict[str, Any]:
|
|
78
|
+
return json.loads(self.model_dump_json(exclude_none=exclude_none))
|
|
79
|
+
|
|
80
|
+
@model_validator(mode='after')
|
|
81
|
+
def validate_field_names(self) -> 'Form':
|
|
82
|
+
"""Valida que todos los nombres de campos sean únicos en todo el formulario"""
|
|
83
|
+
names = [field.name for field in self.fields]
|
|
84
|
+
if len(names) != len(set(names)):
|
|
85
|
+
raise ValueError(t("form.unique_field_names"))
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def validate_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
89
|
+
"""Valida todos los campos del formulario y retorna el resultado"""
|
|
90
|
+
errors = []
|
|
91
|
+
validated_data = {}
|
|
92
|
+
|
|
93
|
+
for field in self.fields:
|
|
94
|
+
field_value = data.get(field.name)
|
|
95
|
+
|
|
96
|
+
# Validar campo requerido
|
|
97
|
+
if field.required and field_value is None:
|
|
98
|
+
errors.append({
|
|
99
|
+
"field": field.name,
|
|
100
|
+
"message": t("field.required_named", name=field.name)
|
|
101
|
+
})
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Validar según el tipo de campo
|
|
105
|
+
if isinstance(field, TextField):
|
|
106
|
+
is_valid, error_msg = field.validate_value(field_value)
|
|
107
|
+
if not is_valid:
|
|
108
|
+
errors.append({"field": field.name, "message": error_msg})
|
|
109
|
+
elif isinstance(field, EmailField):
|
|
110
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", field_value):
|
|
111
|
+
errors.append({"field": field.name, "message": t("email.invalid")})
|
|
112
|
+
elif isinstance(field, NumberField):
|
|
113
|
+
try:
|
|
114
|
+
num_value = float(field_value)
|
|
115
|
+
if field.min_value is not None and num_value < field.min_value:
|
|
116
|
+
errors.append({"field": field.name, "message": t("number.min_value", min=field.min_value)})
|
|
117
|
+
if field.max_value is not None and num_value > field.max_value:
|
|
118
|
+
errors.append({"field": field.name, "message": t("number.max_value", max=field.max_value)})
|
|
119
|
+
except (ValueError, TypeError):
|
|
120
|
+
errors.append({"field": field.name, "message": t("number.invalid")})
|
|
121
|
+
elif isinstance(field, DateField):
|
|
122
|
+
try:
|
|
123
|
+
date_value = date.fromisoformat(field_value)
|
|
124
|
+
if field.min_date is not None and date_value < field.min_date:
|
|
125
|
+
errors.append({"field": field.name, "message": t("date.min_date", min=field.min_date)})
|
|
126
|
+
if field.max_date is not None and date_value > field.max_date:
|
|
127
|
+
errors.append({"field": field.name, "message": t("date.max_date", max=field.max_date)})
|
|
128
|
+
except (ValueError, TypeError):
|
|
129
|
+
errors.append({"field": field.name, "message": t("date.invalid_format")})
|
|
130
|
+
elif isinstance(field, SelectField):
|
|
131
|
+
valid_options = [opt.value for opt in field.options]
|
|
132
|
+
if field.multiple:
|
|
133
|
+
if not isinstance(field_value, list) or not all(v in valid_options for v in field_value):
|
|
134
|
+
errors.append({"field": field.name, "message": t("select.invalid_options")})
|
|
135
|
+
elif field_value not in valid_options:
|
|
136
|
+
errors.append({"field": field.name, "message": t("select.invalid_option")})
|
|
137
|
+
elif isinstance(field, RadioField):
|
|
138
|
+
if field_value not in [opt.value for opt in field.options]:
|
|
139
|
+
errors.append({"field": field.name, "message": t("radio.invalid_option")})
|
|
140
|
+
elif isinstance(field, CheckboxField):
|
|
141
|
+
if not isinstance(field_value, bool):
|
|
142
|
+
errors.append({"field": field.name, "message": t("checkbox.must_be_boolean")})
|
|
143
|
+
elif isinstance(field, CheckboxGroupField):
|
|
144
|
+
if not isinstance(field_value, list) or not all(v in [opt.value for opt in field.options] for v in field_value):
|
|
145
|
+
errors.append({"field": field.name, "message": t("checkbox_group.invalid_options")})
|
|
146
|
+
|
|
147
|
+
if not errors:
|
|
148
|
+
validated_data[field.name] = field_value
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"success": len(errors) == 0,
|
|
152
|
+
"data": validated_data if not errors else None,
|
|
153
|
+
"errors": errors,
|
|
154
|
+
"message": t("form.validation_success") if not errors else t("form.validation_error")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def export(self, output_format: str = 'html', **kwargs) -> dict:
|
|
158
|
+
from codeforms.export import ExportFormat
|
|
159
|
+
export_result = {'format': output_format}
|
|
160
|
+
if output_format in [format.value for format in ExportFormat]:
|
|
161
|
+
from codeforms.export import exporter
|
|
162
|
+
export_result = exporter(self, output_format=output_format, **kwargs)
|
|
163
|
+
|
|
164
|
+
elif output_format == 'dict':
|
|
165
|
+
export_result['output'] = self.to_dict()
|
|
166
|
+
|
|
167
|
+
elif output_format == 'json':
|
|
168
|
+
export_result['output'] = self.model_dump_json()
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError(f"Unsupported export format: {output_format}")
|
|
172
|
+
|
|
173
|
+
return export_result
|
|
174
|
+
|
|
175
|
+
def to_json(self) -> str:
|
|
176
|
+
return self.export(output_format='json').get('output')
|
|
177
|
+
|
|
178
|
+
def set_default_values(self, data: Dict[str, Any]) -> 'Form':
|
|
179
|
+
"""Establece los valores por defecto para los campos del formulario"""
|
|
180
|
+
for field in self.fields:
|
|
181
|
+
field.default_value = data.get(field.name)
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Modelo para los datos recibidos
|
|
186
|
+
class FormDataModel(BaseModel):
|
|
187
|
+
def __init__(self, form: Form):
|
|
188
|
+
"""
|
|
189
|
+
Crea dinámicamente un modelo basado en la estructura del formulario
|
|
190
|
+
"""
|
|
191
|
+
fields = {}
|
|
192
|
+
for field in form.fields:
|
|
193
|
+
# Definir el tipo y validaciones basadas en el campo del formulario
|
|
194
|
+
if isinstance(field, EmailField):
|
|
195
|
+
fields[field.name] = (EmailStr, ... if field.required else None)
|
|
196
|
+
elif isinstance(field, CheckboxGroupField):
|
|
197
|
+
fields[field.name] = (List[str], [] if not field.required else ...)
|
|
198
|
+
elif isinstance(field, CheckboxField):
|
|
199
|
+
fields[field.name] = (bool, ... if field.required else False)
|
|
200
|
+
elif isinstance(field, NumberField):
|
|
201
|
+
fields[field.name] = (float, ... if field.required else None)
|
|
202
|
+
elif isinstance(field, DateField):
|
|
203
|
+
fields[field.name] = (date, ... if field.required else None)
|
|
204
|
+
else:
|
|
205
|
+
fields[field.name] = (str, ... if field.required else None)
|
|
206
|
+
|
|
207
|
+
# Crear el modelo dinámicamente
|
|
208
|
+
self.__class__ = type(
|
|
209
|
+
'DynamicFormData',
|
|
210
|
+
(BaseModel,),
|
|
211
|
+
{
|
|
212
|
+
'__annotations__': fields,
|
|
213
|
+
'model_config': {
|
|
214
|
+
'extra': 'forbid' # No permitir campos extra
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
super().__init__()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FormDataValidator:
|
|
222
|
+
@staticmethod
|
|
223
|
+
def create_model(form: Form) -> Type[BaseModel]:
|
|
224
|
+
fields = {}
|
|
225
|
+
annotations = {}
|
|
226
|
+
validations = {}
|
|
227
|
+
|
|
228
|
+
for field in form.fields:
|
|
229
|
+
# Configurar el tipo y las validaciones según el tipo de campo
|
|
230
|
+
if isinstance(field, SelectField):
|
|
231
|
+
valid_values = field.get_valid_values()
|
|
232
|
+
if field.multiple:
|
|
233
|
+
field_type = List[str]
|
|
234
|
+
|
|
235
|
+
# Crear validador para la lista de valores
|
|
236
|
+
def create_validator(valid_values=valid_values,
|
|
237
|
+
min_selected=field.min_selected,
|
|
238
|
+
max_selected=field.max_selected):
|
|
239
|
+
def validate_select_values(v: List[str]) -> List[str]:
|
|
240
|
+
if not v and field.required:
|
|
241
|
+
raise ValueError(t("field.required"))
|
|
242
|
+
|
|
243
|
+
# Validar que todos los valores sean válidos
|
|
244
|
+
invalid_values = set(v) - valid_values
|
|
245
|
+
if invalid_values:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
t("select.invalid_values", values=', '.join(invalid_values))
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Validar cantidad mínima de selecciones
|
|
251
|
+
if min_selected is not None and len(v) < min_selected:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
t("select.min_selected", min=min_selected)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Validar cantidad máxima de selecciones
|
|
257
|
+
if max_selected is not None and len(v) > max_selected:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
t("select.max_selected", max=max_selected)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return v
|
|
263
|
+
|
|
264
|
+
return validate_select_values
|
|
265
|
+
|
|
266
|
+
validations[field.name] = field_validator(field.name)(
|
|
267
|
+
create_validator()
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
field_type = str
|
|
271
|
+
|
|
272
|
+
# Crear validador para valor único
|
|
273
|
+
def create_validator(valid_values=valid_values):
|
|
274
|
+
def validate_select_value(v: str) -> str:
|
|
275
|
+
if not v and field.required:
|
|
276
|
+
raise ValueError(t("field.required"))
|
|
277
|
+
if v not in valid_values:
|
|
278
|
+
raise ValueError(
|
|
279
|
+
t("select.invalid_value_must_be_one_of", valid=', '.join(valid_values))
|
|
280
|
+
)
|
|
281
|
+
return v
|
|
282
|
+
|
|
283
|
+
return validate_select_value
|
|
284
|
+
|
|
285
|
+
validations[field.name] = field_validator(field.name)(
|
|
286
|
+
create_validator()
|
|
287
|
+
)
|
|
288
|
+
elif isinstance(field, EmailField):
|
|
289
|
+
field_type = EmailStr
|
|
290
|
+
elif isinstance(field, CheckboxGroupField):
|
|
291
|
+
field_type = List[str]
|
|
292
|
+
elif isinstance(field, CheckboxField):
|
|
293
|
+
field_type = bool
|
|
294
|
+
elif isinstance(field, NumberField):
|
|
295
|
+
field_type = float
|
|
296
|
+
elif isinstance(field, DateField):
|
|
297
|
+
field_type = date
|
|
298
|
+
else:
|
|
299
|
+
field_type = str
|
|
300
|
+
|
|
301
|
+
# Definir el campo con sus validaciones
|
|
302
|
+
if field.required:
|
|
303
|
+
fields[field.name] = Field(..., description=field.help_text)
|
|
304
|
+
else:
|
|
305
|
+
default_value = None
|
|
306
|
+
if field_type == List[str]:
|
|
307
|
+
default_value = []
|
|
308
|
+
fields[field.name] = Field(default=default_value, description=field.help_text)
|
|
309
|
+
|
|
310
|
+
annotations[field.name] = field_type
|
|
311
|
+
|
|
312
|
+
# Crear el modelo dinámicamente
|
|
313
|
+
model_name = f"Dynamic{form.name.title()}Data"
|
|
314
|
+
model = type(
|
|
315
|
+
model_name,
|
|
316
|
+
(BaseModel,),
|
|
317
|
+
{
|
|
318
|
+
'__annotations__': annotations,
|
|
319
|
+
**fields,
|
|
320
|
+
**validations,
|
|
321
|
+
'model_config': ConfigDict(
|
|
322
|
+
arbitrary_types_allowed=True,
|
|
323
|
+
extra='forbid'
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return model
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def validate_form_data(form: Form, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
332
|
+
try:
|
|
333
|
+
validated_data = {}
|
|
334
|
+
for field in form.fields:
|
|
335
|
+
field_value = data.get(field.name)
|
|
336
|
+
|
|
337
|
+
# Si no hay valor, usar el valor por defecto
|
|
338
|
+
if field_value is None:
|
|
339
|
+
field_value = field.default_value
|
|
340
|
+
|
|
341
|
+
# Validar campo requerido después de considerar el valor por defecto
|
|
342
|
+
if field.required and field_value is None:
|
|
343
|
+
return {
|
|
344
|
+
"success": False,
|
|
345
|
+
"errors": [{
|
|
346
|
+
"field": field.name,
|
|
347
|
+
"message": t("field.required_named", name=field.name)
|
|
348
|
+
}],
|
|
349
|
+
"message": t("form.data_validation_error")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# Procesar valores específicos por tipo
|
|
353
|
+
if field_value is not None:
|
|
354
|
+
# Select validation (debe ir antes de otros tipos)
|
|
355
|
+
if field.field_type == FieldType.SELECT:
|
|
356
|
+
valid_options = [opt.value for opt in field.options]
|
|
357
|
+
if field.multiple:
|
|
358
|
+
# Asegurar que field_value sea una lista
|
|
359
|
+
if isinstance(field_value, str):
|
|
360
|
+
field_value = [field_value]
|
|
361
|
+
if not isinstance(field_value, list):
|
|
362
|
+
return {
|
|
363
|
+
"success": False,
|
|
364
|
+
"errors": [{
|
|
365
|
+
"field": field.name,
|
|
366
|
+
"message": t("select.value_must_be_list")
|
|
367
|
+
}],
|
|
368
|
+
"message": t("form.data_validation_error")
|
|
369
|
+
}
|
|
370
|
+
# Validar cada valor en la lista
|
|
371
|
+
invalid_values = [v for v in field_value if v not in valid_options]
|
|
372
|
+
if invalid_values:
|
|
373
|
+
return {
|
|
374
|
+
"success": False,
|
|
375
|
+
"errors": [{
|
|
376
|
+
"field": field.name,
|
|
377
|
+
"message": t("select.invalid_values", values=str(invalid_values))
|
|
378
|
+
}],
|
|
379
|
+
"message": t("form.data_validation_error")
|
|
380
|
+
}
|
|
381
|
+
else:
|
|
382
|
+
# Validar valor único
|
|
383
|
+
if field_value not in valid_options:
|
|
384
|
+
return {
|
|
385
|
+
"success": False,
|
|
386
|
+
"errors": [{
|
|
387
|
+
"field": field.name,
|
|
388
|
+
"message": t("select.invalid_option_value", value=field_value, valid=str(valid_options))
|
|
389
|
+
}],
|
|
390
|
+
"message": t("form.data_validation_error")
|
|
391
|
+
}
|
|
392
|
+
validated_data[field.name] = field_value
|
|
393
|
+
|
|
394
|
+
# Email validation
|
|
395
|
+
elif field.field_type == FieldType.EMAIL:
|
|
396
|
+
try:
|
|
397
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", field_value):
|
|
398
|
+
raise ValueError("Invalid email format")
|
|
399
|
+
validated_data[field.name] = field_value
|
|
400
|
+
except ValueError:
|
|
401
|
+
return {
|
|
402
|
+
"success": False,
|
|
403
|
+
"errors": [{
|
|
404
|
+
"field": field.name,
|
|
405
|
+
"message": t("email.invalid")
|
|
406
|
+
}],
|
|
407
|
+
"message": t("form.data_validation_error")
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# Checkbox group validation
|
|
411
|
+
elif field.field_type == FieldType.CHECKBOX and hasattr(field, 'options'):
|
|
412
|
+
valid_options = [opt.value for opt in field.options]
|
|
413
|
+
|
|
414
|
+
# Ensure field_value is a list
|
|
415
|
+
if isinstance(field_value, str):
|
|
416
|
+
field_value = [field_value]
|
|
417
|
+
|
|
418
|
+
if not isinstance(field_value, list):
|
|
419
|
+
return {
|
|
420
|
+
"success": False,
|
|
421
|
+
"errors": [{
|
|
422
|
+
"field": field.name,
|
|
423
|
+
"message": t("select.value_must_be_list")
|
|
424
|
+
}],
|
|
425
|
+
"message": t("form.data_validation_error")
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Check if all values are valid options
|
|
429
|
+
invalid_values = [v for v in field_value if v not in valid_options]
|
|
430
|
+
if invalid_values:
|
|
431
|
+
return {
|
|
432
|
+
"success": False,
|
|
433
|
+
"errors": [{
|
|
434
|
+
"field": field.name,
|
|
435
|
+
"message": t("select.invalid_values", values=str(invalid_values))
|
|
436
|
+
}],
|
|
437
|
+
"message": t("form.data_validation_error")
|
|
438
|
+
}
|
|
439
|
+
validated_data[field.name] = field_value
|
|
440
|
+
|
|
441
|
+
# Single checkbox validation
|
|
442
|
+
elif field.field_type == FieldType.CHECKBOX and not hasattr(field, 'options'):
|
|
443
|
+
# Convertir a booleano si es necesario
|
|
444
|
+
validated_data[field.name] = bool(field_value)
|
|
445
|
+
|
|
446
|
+
# Radio validation
|
|
447
|
+
elif field.field_type == FieldType.RADIO:
|
|
448
|
+
valid_options = [opt.value for opt in field.options]
|
|
449
|
+
if field_value not in valid_options:
|
|
450
|
+
return {
|
|
451
|
+
"success": False,
|
|
452
|
+
"errors": [{
|
|
453
|
+
"field": field.name,
|
|
454
|
+
"message": t("radio.invalid_option")
|
|
455
|
+
}],
|
|
456
|
+
"message": t("form.data_validation_error")
|
|
457
|
+
}
|
|
458
|
+
validated_data[field.name] = field_value
|
|
459
|
+
|
|
460
|
+
# Number validation
|
|
461
|
+
elif field.field_type == FieldType.NUMBER:
|
|
462
|
+
try:
|
|
463
|
+
num_value = float(field_value)
|
|
464
|
+
if hasattr(field, 'min_value') and field.min_value is not None:
|
|
465
|
+
if num_value < field.min_value:
|
|
466
|
+
raise ValueError(t("number.min_value", min=field.min_value))
|
|
467
|
+
if hasattr(field, 'max_value') and field.max_value is not None:
|
|
468
|
+
if num_value > field.max_value:
|
|
469
|
+
raise ValueError(t("number.max_value", max=field.max_value))
|
|
470
|
+
validated_data[field.name] = num_value
|
|
471
|
+
except ValueError as e:
|
|
472
|
+
return {
|
|
473
|
+
"success": False,
|
|
474
|
+
"errors": [{
|
|
475
|
+
"field": field.name,
|
|
476
|
+
"message": str(e)
|
|
477
|
+
}],
|
|
478
|
+
"message": t("form.data_validation_error")
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# Text validation
|
|
482
|
+
elif field.field_type == FieldType.TEXT:
|
|
483
|
+
if not isinstance(field_value, str):
|
|
484
|
+
field_value = str(field_value)
|
|
485
|
+
if hasattr(field, 'minlength') and field.minlength is not None:
|
|
486
|
+
if len(field_value) < field.minlength:
|
|
487
|
+
return {
|
|
488
|
+
"success": False,
|
|
489
|
+
"errors": [{
|
|
490
|
+
"field": field.name,
|
|
491
|
+
"message": t("text.minlength", min=field.minlength)
|
|
492
|
+
}],
|
|
493
|
+
"message": t("form.data_validation_error")
|
|
494
|
+
}
|
|
495
|
+
if hasattr(field, 'maxlength') and field.maxlength is not None:
|
|
496
|
+
if len(field_value) > field.maxlength:
|
|
497
|
+
return {
|
|
498
|
+
"success": False,
|
|
499
|
+
"errors": [{
|
|
500
|
+
"field": field.name,
|
|
501
|
+
"message": t("text.maxlength", max=field.maxlength)
|
|
502
|
+
}],
|
|
503
|
+
"message": t("form.data_validation_error")
|
|
504
|
+
}
|
|
505
|
+
validated_data[field.name] = field_value
|
|
506
|
+
|
|
507
|
+
# Default validation for other types
|
|
508
|
+
else:
|
|
509
|
+
validated_data[field.name] = field_value
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
"success": True,
|
|
513
|
+
"data": validated_data,
|
|
514
|
+
"message": t("form.validation_success")
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
except Exception as e:
|
|
518
|
+
return {
|
|
519
|
+
"success": False,
|
|
520
|
+
"errors": [{
|
|
521
|
+
"field": "unknown",
|
|
522
|
+
"message": str(e)
|
|
523
|
+
}],
|
|
524
|
+
"message": t("form.data_validation_error")
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|