@@ -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