Fix missing NestedSerializer.

This commit is contained in:
Daniel Sheppard 2022-10-28 14:52:05 -05:00
parent 04af1e12b2
commit b78eebed31
9 changed files with 56 additions and 25 deletions

View File

@ -45,6 +45,7 @@ __all__ = [
'NestedSiteSerializer', 'NestedSiteSerializer',
'NestedSiteGroupSerializer', 'NestedSiteGroupSerializer',
'NestedVirtualChassisSerializer', 'NestedVirtualChassisSerializer',
'NestedVirtualDeviceContextSerializer',
] ]
@ -466,3 +467,12 @@ class NestedPowerFeedSerializer(WritableNestedSerializer):
class Meta: class Meta:
model = models.PowerFeed model = models.PowerFeed
fields = ['id', 'url', 'display', 'name', 'cable', '_occupied'] fields = ['id', 'url', 'display', 'name', 'cable', '_occupied']
class NestedVirtualDeviceContextSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
device = NestedDeviceSerializer()
class Meta:
model = models.VirtualDeviceContext
fields = ['id', 'url', 'display', 'name', 'identifier', 'device', 'vdc_type']

View File

@ -317,6 +317,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
) )
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False)
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False) airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False)
vdc_type = ChoiceField(choices=VirtualDeviceContextTypeChoices, allow_blank=True, required=False)
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
@ -324,8 +325,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
model = DeviceType model = DeviceType
fields = [ fields = [
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags', 'subdevice_role', 'vdc_type', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments',
'custom_fields', 'created', 'last_updated', 'device_count', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
] ]
@ -840,6 +841,11 @@ class PowerPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer): class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
vdcs = NestedVirtualDeviceContextSerializer(
required=False,
allow_null=True,
many=True
)
module = ComponentNestedModuleSerializer( module = ComponentNestedModuleSerializer(
required=False, required=False,
allow_null=True allow_null=True
@ -876,13 +882,13 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
class Meta: class Meta:
model = Interface model = Interface
fields = [ fields = [
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'id', 'url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge',
'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'lag', 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role',
'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'rf_channel', 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
'tagged_vlans', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers', 'link_peers_type', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints', 'connected_endpoints_type', 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
'count_fhrp_groups', '_occupied', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
] ]
def validate(self, data): def validate(self, data):

View File

@ -397,7 +397,7 @@ class DeviceTypeForm(NetBoxModelForm):
model = DeviceType model = DeviceType
fields = [ fields = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
'vdc_type', 'vdc_type', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags', 'vdc_type', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags',
] ]
widgets = { widgets = {
'airflow': StaticSelect(), 'airflow': StaticSelect(),
@ -1375,7 +1375,7 @@ class PowerOutletForm(ModularDeviceComponentForm):
class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm): class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
vdc = DynamicModelMultipleChoiceField( vdcs = DynamicModelMultipleChoiceField(
queryset=VirtualDeviceContext.objects.all(), queryset=VirtualDeviceContext.objects.all(),
required=False, required=False,
label='Virtual Device Contexts', label='Virtual Device Contexts',
@ -1457,7 +1457,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
) )
fieldsets = ( fieldsets = (
('Interface', ('device', 'module', 'vdc', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')), ('Interface', ('device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
('Addressing', ('vrf', 'mac_address', 'wwn')), ('Addressing', ('vrf', 'mac_address', 'wwn')),
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
('Related Interfaces', ('parent', 'bridge', 'lag')), ('Related Interfaces', ('parent', 'bridge', 'lag')),
@ -1471,7 +1471,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
class Meta: class Meta:
model = Interface model = Interface
fields = [ fields = [
'device', 'module', 'vdc', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag', 'device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag',
'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
@ -1498,9 +1498,9 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
def clean_vdc(self): def clean_vdc(self):
device = self.cleaned_data.get('device') device = self.cleaned_data.get('device')
if device.device_type.vdc_type not in [VirtualDeviceContextTypeChoices.CISCO_ASA_CONTEXT, VirtualDeviceContextTypeChoices.CISCO_FTD_INSTANCE]\ if device.device_type.vdc_type not in [VirtualDeviceContextTypeChoices.CISCO_ASA_CONTEXT, VirtualDeviceContextTypeChoices.CISCO_FTD_INSTANCE]\
and len(self.cleaned_data.get('vdc')) > 1: and len(self.cleaned_data.get('vdcs')) > 1:
raise forms.ValidationError(f"You cannot assign more then 1 VDC for {device.device_type}") raise forms.ValidationError(f"You cannot assign more then 1 VDC for {device.device_type}")
return self.cleaned_data.get('vdc') return self.cleaned_data.get('vdcs')
class FrontPortForm(ModularDeviceComponentForm): class FrontPortForm(ModularDeviceComponentForm):

View File

@ -45,7 +45,7 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='interface', model_name='interface',
name='vdc', name='vdcs',
field=models.ManyToManyField(related_name='interfaces', to='dcim.virtualdevicecontext'), field=models.ManyToManyField(related_name='interfaces', to='dcim.virtualdevicecontext'),
), ),
migrations.AddConstraint( migrations.AddConstraint(

View File

@ -531,7 +531,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
max_length=100, max_length=100,
blank=True blank=True
) )
vdc = models.ManyToManyField( vdcs = models.ManyToManyField(
to='dcim.VirtualDeviceContext', to='dcim.VirtualDeviceContext',
related_name='interfaces' related_name='interfaces'
) )

View File

@ -124,17 +124,17 @@ class DeviceType(NetBoxModel, WeightMixin):
help_text='Parent devices house child devices in device bays. Leave blank ' help_text='Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.' 'if this device type is neither a parent nor a child.'
) )
airflow = models.CharField(
max_length=50,
choices=DeviceAirflowChoices,
blank=True
)
vdc_type = models.CharField( vdc_type = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
choices=VirtualDeviceContextTypeChoices, choices=VirtualDeviceContextTypeChoices,
verbose_name='VDC Type' verbose_name='VDC Type'
) )
airflow = models.CharField(
max_length=50,
choices=DeviceAirflowChoices,
blank=True
)
front_image = models.ImageField( front_image = models.ImageField(
upload_to='devicetype-images', upload_to='devicetype-images',
blank=True blank=True
@ -1217,3 +1217,7 @@ class VirtualDeviceContext(NetBoxModel):
return self.primary_ip4 return self.primary_ip4
else: else:
return None return None
@property
def vdc_type(self):
return self.device.device_type.vdc_type

View File

@ -127,13 +127,13 @@ def nullify_connected_endpoints(instance, **kwargs):
cablepath.retrace() cablepath.retrace()
@receiver(m2m_changed, sender=Interface.vdc.through) @receiver(m2m_changed, sender=Interface.vdcs.through)
def enforce_vdc_type_restrictions(instance, **kwargs): def enforce_vdc_type_restrictions(instance, **kwargs):
if 'action' == 'post_add': if 'action' == 'post_add':
device = instance.device device = instance.device
if device.device_type.vdc_type not in [VirtualDeviceContextTypeChoices.CISCO_ASA_CONTEXT, VirtualDeviceContextTypeChoices.CISCO_FTD_INSTANCE] \ if device.device_type.vdc_type not in [VirtualDeviceContextTypeChoices.CISCO_ASA_CONTEXT, VirtualDeviceContextTypeChoices.CISCO_FTD_INSTANCE] \
and len(instance.vdc) > 1: and len(instance.vdcs) > 1:
print('Error') print('Error')
raise forms.ValidationError({ raise forms.ValidationError({
'vdc': f"You cannot assign more then 1 VDC for {device.device_type}" 'vdcs': f"You cannot assign more then 1 VDC for {device.device_type}"
}) })

View File

@ -57,6 +57,12 @@
{{ object.get_airflow_display|placeholder }} {{ object.get_airflow_display|placeholder }}
</td> </td>
</tr> </tr>
<tr>
<td>VDC Type</td>
<td>
{{ object.get_vdc_type_display|placeholder }}
</td>
</tr>
<tr> <tr>
<td>Front Image</td> <td>Front Image</td>
<td> <td>

View File

@ -36,6 +36,11 @@
<th scope="row">Identifier</th> <th scope="row">Identifier</th>
<td>{{ object.identifier|placeholder }}</td> <td>{{ object.identifier|placeholder }}</td>
</tr> </tr>
</tr>
<tr>
<th scope="row">VDC Type</th>
<td>{{ object.device.device_type.get_vdc_type_display |placeholder }}</td>
</tr>
<tr> <tr>
<th scope="row">Primary IPv4</th> <th scope="row">Primary IPv4</th>
<td> <td>