11from django import forms
22from django .utils .translation import gettext as _
33
4- from dcim .models import DeviceType , ModuleType , Manufacturer , Device
4+ from dcim .models import DeviceType , ModuleType , Manufacturer , Device , Module
55from netbox .forms import NetBoxModelForm
66from netbox_lifecycle .models import (
77 HardwareLifecycle ,
@@ -101,19 +101,40 @@ class SupportContractAssignmentForm(NetBoxModelForm):
101101 selector = True ,
102102 label = _ ('Device' ),
103103 )
104+ module = DynamicModelChoiceField (
105+ queryset = Module .objects .all (),
106+ required = False ,
107+ selector = True ,
108+ label = _ ('Module' ),
109+ query_params = {'device_id' : '$device' },
110+ )
104111 license = DynamicModelChoiceField (
105112 queryset = LicenseAssignment .objects .all (),
106113 required = False ,
107114 selector = True ,
108115 label = _ ('License Assignment' ),
109116 )
110117
118+ fieldsets = (
119+ FieldSet ('contract' , 'sku' , name = _ ('Contract' )),
120+ FieldSet (
121+ TabbedGroups (
122+ FieldSet ('device' , 'module' , name = _ ('Hardware' )),
123+ FieldSet ('license' , name = _ ('License' )),
124+ ),
125+ name = _ ('Assignment' ),
126+ ),
127+ FieldSet ('end' , name = _ ('Dates' )),
128+ FieldSet ('description' , 'comments' , 'tags' , name = _ ('Other' )),
129+ )
130+
111131 class Meta :
112132 model = SupportContractAssignment
113133 fields = (
114134 'contract' ,
115135 'sku' ,
116136 'device' ,
137+ 'module' ,
117138 'license' ,
118139 'end' ,
119140 'description' ,
@@ -124,37 +145,50 @@ class Meta:
124145 'end' : DatePicker (),
125146 }
126147
127- def __init__ (self , * args , ** kwargs ):
128- super ().__init__ (* args , ** kwargs )
129-
130148 def clean (self ):
131149 super ().clean ()
132150
133- # Handle object assignment
134- selected_objects = [
135- field for field in ( 'device' , 'license' ) if self . cleaned_data [ field ]
136- ]
151+ has_hardware = self . cleaned_data . get ( 'device' ) or self . cleaned_data . get (
152+ 'module'
153+ )
154+ has_license = self . cleaned_data . get ( 'license' )
137155
138- if len (selected_objects ) == 0 :
156+ # Must select at least one assignment target
157+ if not has_hardware and not has_license :
139158 raise forms .ValidationError (
140- {
141- 'device' : "You must select at least a device or license" ,
142- 'license' : "You must select at least a device or license" ,
143- }
159+ _ ('Select a device, module, or license assignment' )
144160 )
145161
162+ # Auto-populate device from module if module selected without device
163+ if self .cleaned_data .get ('module' ) and not self .cleaned_data .get ('device' ):
164+ self .cleaned_data ['device' ] = self .cleaned_data ['module' ].device
165+
166+ # Validate device matches module.device
167+ if (
168+ self .cleaned_data .get ('device' )
169+ and self .cleaned_data .get ('module' )
170+ and self .cleaned_data ['device' ] != self .cleaned_data ['module' ].device
171+ ):
172+ raise forms .ValidationError (
173+ {'module' : _ ('Module must belong to the selected device' )}
174+ )
175+
176+ # Auto-populate device from license if license selected without device
146177 if self .cleaned_data .get ('license' ) and not self .cleaned_data .get ('device' ):
147- self .cleaned_data ['device' ] = self .cleaned_data .get ('license' ).device
148-
149- if self .cleaned_data .get ('license' ) and self .cleaned_data .get ('device' ):
150- if self .cleaned_data .get ('license' ).device != self .cleaned_data .get (
151- 'device'
152- ):
153- raise forms .ValidationError (
154- {
155- 'device' : 'Device assigned to license must match device assignment'
156- }
157- )
178+ self .cleaned_data ['device' ] = self .cleaned_data ['license' ].device
179+
180+ # Validate device matches license.device if both are set
181+ if (
182+ self .cleaned_data .get ('license' )
183+ and self .cleaned_data .get ('device' )
184+ and self .cleaned_data ['license' ].device
185+ and self .cleaned_data ['device' ] != self .cleaned_data ['license' ].device
186+ ):
187+ raise forms .ValidationError (
188+ {'device' : _ ('Device must match the device assigned to the license' )}
189+ )
190+
191+ return self .cleaned_data
158192
159193
160194class LicenseForm (NetBoxModelForm ):
0 commit comments