from flask_wtf import FlaskForm from wtforms import IntegerField, FloatField, BooleanField, StringField, validators from flask import current_app class DynamicFormBase(FlaskForm): def __init__(self, formdata=None, *args, **kwargs): super(DynamicFormBase, self).__init__(*args, **kwargs) # Maps collection names to lists of field names self.dynamic_fields = {} # Store formdata for later use self.formdata = formdata def add_dynamic_fields(self, collection_name, config, initial_data=None): """Add dynamic fields to the form based on the configuration.""" self.dynamic_fields[collection_name] = [] for field_name, field_def in config.items(): current_app.logger.debug(f"{field_name}: {field_def}") # Prefix the field name with the collection name full_field_name = f"{collection_name}_{field_name}" field_type = field_def.get('type') description = field_def.get('description', '') required = field_def.get('required', False) default = field_def.get('default') # Determine validators field_validators = [validators.InputRequired()] if required else [validators.Optional()] # Map the field type to WTForms field classes field_class = { 'int': IntegerField, 'float': FloatField, 'boolean': BooleanField, 'string': StringField, }.get(field_type, StringField) # Create the field instance unbound_field = field_class( label=description, validators=field_validators, default=default ) # Bind the field to the form bound_field = unbound_field.bind(form=self, name=full_field_name) # Process the field with formdata if self.formdata and full_field_name in self.formdata: # If formdata is available and contains the field bound_field.process(self.formdata) elif initial_data and field_name in initial_data: # Use initial data if provided bound_field.process(formdata=None, data=initial_data[field_name]) else: # Use default value bound_field.process(formdata=None, data=default) # Set collection name attribute for identification # bound_field.collection_name = collection_name # Add the field to the form setattr(self, full_field_name, bound_field) self._fields[full_field_name] = bound_field self.dynamic_fields[collection_name].append(full_field_name) def get_static_fields(self): """Return a list of static field instances.""" # Get names of dynamic fields dynamic_field_names = set() for field_list in self.dynamic_fields.values(): dynamic_field_names.update(field_list) # Return all fields that are not dynamic return [field for name, field in self._fields.items() if name not in dynamic_field_names] def get_dynamic_fields(self): """Return a dictionary of dynamic fields per collection.""" result = {} for collection_name, field_names in self.dynamic_fields.items(): result[collection_name] = [getattr(self, name) for name in field_names] return result def get_dynamic_data(self, collection_name): """Retrieve the data from dynamic fields of a specific collection.""" data = {} if collection_name not in self.dynamic_fields: return data prefix_length = len(collection_name) + 1 # +1 for the underscore for full_field_name in self.dynamic_fields[collection_name]: original_field_name = full_field_name[prefix_length:] field = getattr(self, full_field_name) data[original_field_name] = field.data return data