Spaces:
Sleeping
Sleeping
| """ | |
| Field Validation Defaults - Smart defaults for activation requirement types | |
| Provides sensible validation rules, placeholders, and help text based on field type. | |
| Managers only specify the type, system fills in the rest automatically. | |
| """ | |
| from typing import Dict, Any, Optional | |
| # Smart defaults by field type | |
| FIELD_TYPE_DEFAULTS = { | |
| "phone": { | |
| "min_length": 10, | |
| "max_length": 15, | |
| "pattern": r"^\+?[0-9]{10,15}$", | |
| "placeholder": "+254XXXXXXXXX", | |
| "help_text": "Enter phone number with country code" | |
| }, | |
| "email": { | |
| "min_length": 5, | |
| "max_length": 100, | |
| "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", | |
| "placeholder": "user@example.com", | |
| "help_text": "Enter valid email address" | |
| }, | |
| "mac_address": { | |
| "min_length": 17, | |
| "max_length": 17, | |
| "pattern": r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", | |
| "placeholder": "AA:BB:CC:DD:EE:FF", | |
| "help_text": "Enter MAC address (format: AA:BB:CC:DD:EE:FF)" | |
| }, | |
| "serial_number": { | |
| "min_length": 6, | |
| "max_length": 30, | |
| "pattern": r"^[A-Z0-9\-]+$", | |
| "placeholder": "ABC123XYZ", | |
| "help_text": "Enter equipment serial number" | |
| }, | |
| "ip_address": { | |
| "min_length": 7, | |
| "max_length": 15, | |
| "pattern": r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", | |
| "placeholder": "192.168.1.1", | |
| "help_text": "Enter IPv4 address" | |
| }, | |
| "url": { | |
| "min_length": 10, | |
| "max_length": 500, | |
| "pattern": r"^https?://[^\s/$.?#].[^\s]*$", | |
| "placeholder": "https://example.com", | |
| "help_text": "Enter valid URL starting with http:// or https://" | |
| }, | |
| "text": { | |
| "min_length": 1, | |
| "max_length": 255, | |
| "pattern": None, | |
| "placeholder": None, # Will use field label | |
| "help_text": None | |
| }, | |
| "number": { | |
| "min_length": None, | |
| "max_length": None, | |
| "pattern": r"^-?[0-9]+(\.[0-9]+)?$", | |
| "placeholder": "0", | |
| "help_text": "Enter numeric value" | |
| }, | |
| "date": { | |
| "min_length": None, | |
| "max_length": None, | |
| "pattern": r"^\d{4}-\d{2}-\d{2}$", | |
| "placeholder": "YYYY-MM-DD", | |
| "help_text": "Select date" | |
| }, | |
| "boolean": { | |
| "min_length": None, | |
| "max_length": None, | |
| "pattern": None, | |
| "placeholder": None, | |
| "help_text": None | |
| }, | |
| "select": { | |
| "min_length": None, | |
| "max_length": None, | |
| "pattern": None, | |
| "placeholder": "Select an option", | |
| "help_text": None | |
| } | |
| } | |
| def apply_field_defaults(field_config: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Apply smart defaults to field configuration based on type | |
| Args: | |
| field_config: Field configuration dict (from ActivationRequirement) | |
| Returns: | |
| Enhanced field config with defaults applied (non-destructive) | |
| """ | |
| field_type = field_config.get("type", "text") | |
| defaults = FIELD_TYPE_DEFAULTS.get(field_type, FIELD_TYPE_DEFAULTS["text"]) | |
| # Create enhanced config (don't modify original) | |
| enhanced = field_config.copy() | |
| # Apply defaults only if not already specified | |
| if enhanced.get("min_length") is None and defaults["min_length"] is not None: | |
| enhanced["min_length"] = defaults["min_length"] | |
| if enhanced.get("max_length") is None and defaults["max_length"] is not None: | |
| enhanced["max_length"] = defaults["max_length"] | |
| # Handle pattern (check both 'pattern' and deprecated 'validation_regex') | |
| if not enhanced.get("pattern") and not enhanced.get("validation_regex"): | |
| if defaults["pattern"]: | |
| enhanced["pattern"] = defaults["pattern"] | |
| elif enhanced.get("validation_regex") and not enhanced.get("pattern"): | |
| # Migrate deprecated field | |
| enhanced["pattern"] = enhanced["validation_regex"] | |
| if enhanced.get("placeholder") is None: | |
| if defaults["placeholder"]: | |
| enhanced["placeholder"] = defaults["placeholder"] | |
| elif field_type == "text": | |
| # For text fields, use label as placeholder | |
| enhanced["placeholder"] = f"Enter {enhanced.get('label', 'value').lower()}" | |
| if enhanced.get("help_text") is None and defaults["help_text"]: | |
| enhanced["help_text"] = defaults["help_text"] | |
| return enhanced | |
| def get_validation_rules(field_config: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Get complete validation rules for frontend | |
| Args: | |
| field_config: Field configuration with defaults applied | |
| Returns: | |
| Validation rules dict ready for frontend consumption | |
| """ | |
| return { | |
| "min_length": field_config.get("min_length"), | |
| "max_length": field_config.get("max_length"), | |
| "pattern": field_config.get("pattern"), | |
| "placeholder": field_config.get("placeholder"), | |
| "help_text": field_config.get("help_text"), | |
| "required": field_config.get("required", True) | |
| } | |
| def validate_field_value(value: Any, field_config: Dict[str, Any]) -> tuple[bool, Optional[str]]: | |
| """ | |
| Validate a field value against its configuration | |
| Args: | |
| value: Value to validate | |
| field_config: Field configuration with validation rules | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| import re | |
| field_type = field_config.get("type", "text") | |
| required = field_config.get("required", True) | |
| # Check required | |
| if required and (value is None or value == ""): | |
| return False, f"{field_config.get('label', 'Field')} is required" | |
| # If not required and empty, it's valid | |
| if not required and (value is None or value == ""): | |
| return True, None | |
| # Convert value to string for validation | |
| str_value = str(value) | |
| # Check min_length | |
| min_length = field_config.get("min_length") | |
| if min_length is not None and len(str_value) < min_length: | |
| return False, f"{field_config.get('label', 'Field')} must be at least {min_length} characters" | |
| # Check max_length | |
| max_length = field_config.get("max_length") | |
| if max_length is not None and len(str_value) > max_length: | |
| return False, f"{field_config.get('label', 'Field')} must not exceed {max_length} characters" | |
| # Check pattern | |
| pattern = field_config.get("pattern") | |
| if pattern: | |
| try: | |
| if not re.match(pattern, str_value): | |
| help_text = field_config.get("help_text", "Invalid format") | |
| return False, f"{field_config.get('label', 'Field')}: {help_text}" | |
| except re.error: | |
| # Invalid regex pattern - log but don't fail validation | |
| return True, None | |
| # Type-specific validation | |
| if field_type == "number": | |
| try: | |
| float(str_value) | |
| except ValueError: | |
| return False, f"{field_config.get('label', 'Field')} must be a valid number" | |
| elif field_type == "boolean": | |
| if str_value.lower() not in ["true", "false", "1", "0", "yes", "no"]: | |
| return False, f"{field_config.get('label', 'Field')} must be true or false" | |
| elif field_type == "select": | |
| options = field_config.get("options", []) | |
| if options and str_value not in options: | |
| return False, f"{field_config.get('label', 'Field')} must be one of: {', '.join(options)}" | |
| return True, None | |