@@ -53,20 +53,34 @@ def __init__(self, form_definition, user=None, initial_data=None, *args, **kwarg
5353 HTML (f'<h3 class="mt-4 mb-3">{ field .field_label } </h3>' )
5454 )
5555 else :
56+ # Wrap field in a div with field-wrapper class for multi-step support
57+ field_wrapper_class = f"field-wrapper field-{ field .field_name } "
58+
5659 if field .width == "half" :
5760 layout_fields .append (
58- Row (
59- Column (Field (field .field_name ), css_class = "col-md-6" ),
61+ Div (
62+ Row (
63+ Column (Field (field .field_name ), css_class = "col-md-6" ),
64+ ),
65+ css_class = field_wrapper_class
6066 )
6167 )
6268 elif field .width == "third" :
6369 layout_fields .append (
64- Row (
65- Column (Field (field .field_name ), css_class = "col-md-4" ),
70+ Div (
71+ Row (
72+ Column (Field (field .field_name ), css_class = "col-md-4" ),
73+ ),
74+ css_class = field_wrapper_class
6675 )
6776 )
6877 else :
69- layout_fields .append (Field (field .field_name ))
78+ layout_fields .append (
79+ Div (
80+ Field (field .field_name ),
81+ css_class = field_wrapper_class
82+ )
83+ )
7084
7185 # Add submit buttons
7286 buttons = [Submit ("submit" , "Submit" , css_class = "btn btn-primary" )]
@@ -79,6 +93,13 @@ def __init__(self, form_definition, user=None, initial_data=None, *args, **kwarg
7993
8094 self .helper .layout = Layout (* layout_fields )
8195
96+ # Add form ID for JavaScript targeting
97+ self .helper .form_id = f"form_{ form_definition .slug } "
98+ self .helper .attrs = {
99+ 'data-form-enhancements' : 'true' ,
100+ 'data-form-slug' : form_definition .slug ,
101+ }
102+
82103 def _parse_choices (self , choices ):
83104 """
84105 Parse choices from either JSON format or comma-separated string.
@@ -352,3 +373,142 @@ def _get_prefill_value(self, prefill_source, prefill_config=None):
352373 logger .error (f"Error getting prefill value for { prefill_source } : { e } " )
353374
354375 return ""
376+
377+ def get_enhancements_config (self ):
378+ """
379+ Generate JavaScript configuration for form enhancements.
380+ Returns a dictionary that can be serialized to JSON.
381+ """
382+ import json
383+
384+ config = {
385+ 'autoSaveEnabled' : getattr (self .form_definition , 'enable_auto_save' , True ),
386+ 'autoSaveInterval' : getattr (self .form_definition , 'auto_save_interval' , 30 ) * 1000 , # Convert to ms
387+ 'autoSaveEndpoint' : f'/forms/{ self .form_definition .slug } /auto-save/' ,
388+ 'multiStepEnabled' : getattr (self .form_definition , 'enable_multi_step' , False ),
389+ 'steps' : getattr (self .form_definition , 'form_steps' , None ) or [],
390+ 'conditionalRules' : [],
391+ 'fieldDependencies' : [],
392+ 'validationRules' : [],
393+ }
394+
395+ # Collect conditional rules from all fields
396+ for field in self .form_definition .fields .all ():
397+ # Legacy simple conditional logic
398+ if field .show_if_field and field .show_if_value :
399+ config ['conditionalRules' ].append ({
400+ 'targetField' : field .field_name ,
401+ 'action' : 'show' ,
402+ 'operator' : 'AND' ,
403+ 'conditions' : [{
404+ 'field' : field .show_if_field ,
405+ 'operator' : 'equals' ,
406+ 'value' : field .show_if_value ,
407+ }]
408+ })
409+
410+ # Advanced conditional rules
411+ if hasattr (field , 'conditional_rules' ) and field .conditional_rules :
412+ if isinstance (field .conditional_rules , str ):
413+ try :
414+ rules = json .loads (field .conditional_rules )
415+ except json .JSONDecodeError :
416+ rules = None
417+ else :
418+ rules = field .conditional_rules
419+
420+ if rules :
421+ config ['conditionalRules' ].append ({
422+ 'targetField' : field .field_name ,
423+ ** rules
424+ })
425+
426+ # Field dependencies
427+ if hasattr (field , 'field_dependencies' ) and field .field_dependencies :
428+ if isinstance (field .field_dependencies , str ):
429+ try :
430+ deps = json .loads (field .field_dependencies )
431+ except json .JSONDecodeError :
432+ deps = []
433+ else :
434+ deps = field .field_dependencies
435+
436+ if deps :
437+ config ['fieldDependencies' ].extend (deps )
438+
439+ # Validation rules
440+ validation_rules = []
441+
442+ if field .required :
443+ validation_rules .append ({
444+ 'type' : 'required' ,
445+ 'message' : f'{ field .field_label } is required'
446+ })
447+
448+ if field .field_type == 'email' :
449+ validation_rules .append ({
450+ 'type' : 'email' ,
451+ 'message' : 'Please enter a valid email address'
452+ })
453+
454+ if field .field_type == 'url' :
455+ validation_rules .append ({
456+ 'type' : 'url' ,
457+ 'message' : 'Please enter a valid URL'
458+ })
459+
460+ if field .min_length :
461+ validation_rules .append ({
462+ 'type' : 'min' ,
463+ 'value' : field .min_length ,
464+ 'message' : f'Minimum { field .min_length } characters required'
465+ })
466+
467+ if field .max_length :
468+ validation_rules .append ({
469+ 'type' : 'max' ,
470+ 'value' : field .max_length ,
471+ 'message' : f'Maximum { field .max_length } characters allowed'
472+ })
473+
474+ if field .min_value is not None :
475+ validation_rules .append ({
476+ 'type' : 'min_value' ,
477+ 'value' : float (field .min_value ),
478+ 'message' : f'Minimum value is { field .min_value } '
479+ })
480+
481+ if field .max_value is not None :
482+ validation_rules .append ({
483+ 'type' : 'max_value' ,
484+ 'value' : float (field .max_value ),
485+ 'message' : f'Maximum value is { field .max_value } '
486+ })
487+
488+ if field .regex_validation :
489+ validation_rules .append ({
490+ 'type' : 'pattern' ,
491+ 'value' : field .regex_validation ,
492+ 'message' : field .regex_error_message or 'Invalid format'
493+ })
494+
495+ # Custom validation rules from field config
496+ if hasattr (field , 'validation_rules' ) and field .validation_rules :
497+ if isinstance (field .validation_rules , str ):
498+ try :
499+ custom_rules = json .loads (field .validation_rules )
500+ except json .JSONDecodeError :
501+ custom_rules = []
502+ else :
503+ custom_rules = field .validation_rules
504+
505+ if custom_rules :
506+ validation_rules .extend (custom_rules )
507+
508+ if validation_rules :
509+ config ['validationRules' ].append ({
510+ 'field' : field .field_name ,
511+ 'rules' : validation_rules
512+ })
513+
514+ return config
0 commit comments