- Split field creation logic into helper functions for each attribute type. - Improved readability, maintainability, and reusability of the code. - No functional changes.
110 lines
4.7 KiB
Python
110 lines
4.7 KiB
Python
from datetime import datetime
|
|
import re
|
|
from marshmallow import Schema, fields, ValidationError
|
|
from typing import Dict, Any, Optional, Type, Union
|
|
from config import item_attributes
|
|
from definitions.attributes import *
|
|
|
|
def create_text_field(attrib: textAttribute) -> fields.String:
|
|
"""Create a Marshmallow String field for textAttribute."""
|
|
def validate_allowed_chars(value: str, attrib=attrib) -> None:
|
|
if attrib.allowed_chars and not all(char in attrib.allowed_chars for char in value):
|
|
raise ValidationError(f"Invalid characters in {attrib.display_name}. Allowed characters are: {attrib.allowed_chars}")
|
|
|
|
return fields.String(
|
|
required=attrib.required,
|
|
validate=[
|
|
lambda x, attrib=attrib: len(x) <= attrib.max_length if attrib.max_length else True,
|
|
lambda x, attrib=attrib: len(x) >= attrib.min_length if attrib.min_length else True,
|
|
lambda x, attrib=attrib: re.match(attrib.regex, x) if attrib.regex else True,
|
|
validate_allowed_chars,
|
|
],
|
|
error_messages={
|
|
"required": f"{attrib.display_name} is required.",
|
|
"validator_failed": f"Invalid value for {attrib.display_name}.",
|
|
},
|
|
)
|
|
|
|
def create_numeric_field(attrib: Union[intAttribute, floatAttribute]) -> Union[fields.Integer, fields.Float]:
|
|
"""Create a Marshmallow Integer or Float field for intAttribute or floatAttribute."""
|
|
field_type = fields.Integer if isinstance(attrib, intAttribute) else fields.Float
|
|
return field_type(
|
|
required=attrib.required,
|
|
validate=[
|
|
lambda x, attrib=attrib: x >= attrib.min_val if attrib.min_val is not None else True,
|
|
lambda x, attrib=attrib: x <= attrib.max_val if attrib.max_val is not None else True,
|
|
],
|
|
error_messages={
|
|
"required": f"{attrib.display_name} is required.",
|
|
"validator_failed": f"Invalid value for {attrib.display_name}.",
|
|
},
|
|
)
|
|
|
|
def create_date_field(attrib: dateAttribute) -> fields.Date:
|
|
"""Create a Marshmallow Date field for dateAttribute."""
|
|
return fields.Date(
|
|
required=attrib.required,
|
|
format="%Y-%m-%d",
|
|
validate=[
|
|
lambda x, attrib=attrib: x >= datetime.strptime(attrib.min_val, "%Y-%m-%d").date()
|
|
if attrib.min_val
|
|
else True,
|
|
lambda x, attrib=attrib: x <= datetime.strptime(attrib.max_val, "%Y-%m-%d").date()
|
|
if attrib.max_val
|
|
else True,
|
|
],
|
|
error_messages={
|
|
"required": f"{attrib.display_name} is required.",
|
|
"validator_failed": f"Invalid value for {attrib.display_name}.",
|
|
},
|
|
)
|
|
|
|
def create_select_field(attrib: selectAttribute) -> fields.String:
|
|
"""Create a Marshmallow String field for selectAttribute."""
|
|
return fields.String(
|
|
required=attrib.required,
|
|
validate=[lambda x, attrib=attrib: x in attrib.options],
|
|
error_messages={
|
|
"required": f"{attrib.display_name} is required.",
|
|
"validator_failed": f"Invalid value for {attrib.display_name}. Must be one of: {', '.join(attrib.options)}.",
|
|
},
|
|
)
|
|
|
|
def create_dynamic_schema() -> Type[Schema]:
|
|
"""
|
|
Dynamically creates a Marshmallow Schema based on the configuration in item_attributes.
|
|
"""
|
|
schema_fields = {} # Dictionary to store dynamically created fields
|
|
|
|
for attrib in item_attributes:
|
|
if isinstance(attrib, textAttribute):
|
|
schema_fields[attrib.attrib_name] = create_text_field(attrib)
|
|
elif isinstance(attrib, (intAttribute, floatAttribute)):
|
|
schema_fields[attrib.attrib_name] = create_numeric_field(attrib)
|
|
elif isinstance(attrib, dateAttribute):
|
|
schema_fields[attrib.attrib_name] = create_date_field(attrib)
|
|
elif isinstance(attrib, selectAttribute):
|
|
schema_fields[attrib.attrib_name] = create_select_field(attrib)
|
|
|
|
# Dynamically create the schema class
|
|
DynamicSchema = type("DynamicSchema", (Schema,), schema_fields)
|
|
return DynamicSchema
|
|
|
|
def validate_values(form_data: Dict[str, Any]) -> Optional[str]:
|
|
"""
|
|
Validate form data against the configuration in item_attributes using Marshmallow.
|
|
Returns an error message if invalid, otherwise None.
|
|
"""
|
|
DynamicSchema = create_dynamic_schema()
|
|
schema = DynamicSchema()
|
|
|
|
try:
|
|
schema.load(form_data) # Validate the data
|
|
return None # No errors
|
|
except ValidationError as e:
|
|
# Format the error message for display
|
|
error_messages = []
|
|
for field, errors in e.messages.items():
|
|
for error in errors:
|
|
error_messages.append(f"{field}: {error}")
|
|
return "; ".join(error_messages) |