mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-18 19:32:24 -06:00
Update API tests
This commit is contained in:
@@ -5,15 +5,15 @@ from rest_framework import serializers
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||
RearPort, VirtualDeviceContext,
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PortAssignment,
|
||||
PowerOutlet, PowerPort, RearPort, VirtualDeviceContext,
|
||||
)
|
||||
from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer
|
||||
from ipam.api.serializers_.vrfs import VRFSerializer
|
||||
from ipam.models import VLAN
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||
from netbox.api.gfk_fields import GFKSerializerField
|
||||
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||
from wireless.api.serializers_.nested import NestedWirelessLinkSerializer
|
||||
from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
|
||||
@@ -294,6 +294,16 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
||||
return super().validate(data)
|
||||
|
||||
|
||||
class RearPortAssignmentSerializer(serializers.ModelSerializer):
|
||||
front_port = serializers.PrimaryKeyRelatedField(
|
||||
queryset=FrontPort.objects.all(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PortAssignment
|
||||
fields = ('id', 'rear_port_position', 'front_port', 'front_port_position')
|
||||
|
||||
|
||||
class RearPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module = ModuleSerializer(
|
||||
@@ -303,25 +313,52 @@ class RearPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
front_ports = RearPortAssignmentSerializer(
|
||||
source='assignments',
|
||||
many=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
|
||||
'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'front_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
def create(self, validated_data):
|
||||
assignments = validated_data.pop('assignments', [])
|
||||
instance = super().create(validated_data)
|
||||
|
||||
class FrontPortRearPortSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
|
||||
"""
|
||||
# Create FrontPort assignments
|
||||
for assignment_data in assignments:
|
||||
PortAssignment.objects.create(rear_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
assignments = validated_data.pop('assignments', None)
|
||||
instance = super().update(instance, validated_data)
|
||||
|
||||
if assignments is not None:
|
||||
# Update FrontPort assignments
|
||||
PortAssignment.objects.filter(rear_port=instance).delete()
|
||||
for assignment_data in assignments:
|
||||
PortAssignment.objects.create(rear_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class FrontPortAssignmentSerializer(serializers.ModelSerializer):
|
||||
rear_port = serializers.PrimaryKeyRelatedField(
|
||||
queryset=RearPort.objects.all(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = ['id', 'url', 'display_url', 'display', 'name', 'label', 'description']
|
||||
model = PortAssignment
|
||||
fields = ('id', 'front_port_position', 'rear_port', 'rear_port_position')
|
||||
|
||||
|
||||
class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
@@ -333,7 +370,11 @@ class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
rear_ports = FrontPortRearPortSerializer(many=True)
|
||||
rear_ports = FrontPortAssignmentSerializer(
|
||||
source='assignments',
|
||||
many=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
@@ -344,6 +385,28 @@ class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
def create(self, validated_data):
|
||||
assignments = validated_data.pop('assignments', [])
|
||||
instance = super().create(validated_data)
|
||||
|
||||
# Create RearPort assignments
|
||||
for assignment_data in assignments:
|
||||
PortAssignment.objects.create(front_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
assignments = validated_data.pop('assignments', None)
|
||||
instance = super().update(instance, validated_data)
|
||||
|
||||
if assignments is not None:
|
||||
# Update RearPort assignments
|
||||
PortAssignment.objects.filter(front_port=instance).delete()
|
||||
for assignment_data in assignments:
|
||||
PortAssignment.objects.create(front_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class ModuleBaySerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
|
||||
@@ -385,7 +385,7 @@ class DeviceTypeType(PrimaryObjectType):
|
||||
)
|
||||
class FrontPortType(ModularComponentType, CabledObjectMixin):
|
||||
color: str
|
||||
rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
|
||||
# rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -396,7 +396,7 @@ class FrontPortType(ModularComponentType, CabledObjectMixin):
|
||||
)
|
||||
class FrontPortTemplateType(ModularComponentTemplateType):
|
||||
color: str
|
||||
rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
|
||||
# rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -768,7 +768,7 @@ class RackRoleType(OrganizationalObjectType):
|
||||
class RearPortType(ModularComponentType, CabledObjectMixin):
|
||||
color: str
|
||||
|
||||
frontports: List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]
|
||||
front_ports: List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -780,7 +780,7 @@ class RearPortType(ModularComponentType, CabledObjectMixin):
|
||||
class RearPortTemplateType(ModularComponentTemplateType):
|
||||
color: str
|
||||
|
||||
frontport_templates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||
front_ports: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
|
||||
@@ -87,11 +87,19 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
(
|
||||
'front_port',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.frontporttemplate')
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='dcim.frontporttemplate',
|
||||
related_name='assignments'
|
||||
)
|
||||
),
|
||||
(
|
||||
'rear_port',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.rearporttemplate')
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='dcim.rearporttemplate',
|
||||
related_name='assignments'
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -146,8 +154,22 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
),
|
||||
),
|
||||
('front_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.frontport')),
|
||||
('rear_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.rearport')),
|
||||
(
|
||||
'front_port',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='dcim.frontport',
|
||||
related_name='assignments'
|
||||
)
|
||||
),
|
||||
(
|
||||
'rear_port',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='dcim.rearport',
|
||||
related_name='assignments'
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
|
||||
@@ -527,10 +527,12 @@ class PortAssignmentTemplate(PortAssignmentBase):
|
||||
front_port = models.ForeignKey(
|
||||
to='dcim.FrontPortTemplate',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='assignments',
|
||||
)
|
||||
rear_port = models.ForeignKey(
|
||||
to='dcim.RearPortTemplate',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='assignments',
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
|
||||
@@ -1078,10 +1078,12 @@ class PortAssignment(PortAssignmentBase):
|
||||
front_port = models.ForeignKey(
|
||||
to='dcim.FrontPort',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='assignments',
|
||||
)
|
||||
rear_port = models.ForeignKey(
|
||||
to='dcim.RearPort',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='assignments',
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
@@ -1168,7 +1170,7 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||
|
||||
# Check that positions count is greater than or equal to the number of associated FrontPorts
|
||||
if not self._state.adding:
|
||||
frontport_count = self.frontports.count()
|
||||
frontport_count = self.front_ports.count()
|
||||
if self.positions < frontport_count:
|
||||
raise ValidationError({
|
||||
"positions": _(
|
||||
|
||||
@@ -981,32 +981,18 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
RearPortTemplate.objects.bulk_create(rear_port_templates)
|
||||
|
||||
front_port_templates = (
|
||||
FrontPortTemplate(
|
||||
device_type=devicetype,
|
||||
name='Front Port Template 1',
|
||||
type=PortTypeChoices.TYPE_8P8C,
|
||||
rear_port=rear_port_templates[0]
|
||||
),
|
||||
FrontPortTemplate(
|
||||
device_type=devicetype,
|
||||
name='Front Port Template 2',
|
||||
type=PortTypeChoices.TYPE_8P8C,
|
||||
rear_port=rear_port_templates[1]
|
||||
),
|
||||
FrontPortTemplate(
|
||||
module_type=moduletype,
|
||||
name='Front Port Template 5',
|
||||
type=PortTypeChoices.TYPE_8P8C,
|
||||
rear_port=rear_port_templates[4]
|
||||
),
|
||||
FrontPortTemplate(
|
||||
module_type=moduletype,
|
||||
name='Front Port Template 6',
|
||||
type=PortTypeChoices.TYPE_8P8C,
|
||||
rear_port=rear_port_templates[5]
|
||||
),
|
||||
FrontPortTemplate(device_type=devicetype, name='Front Port Template 1', type=PortTypeChoices.TYPE_8P8C),
|
||||
FrontPortTemplate(device_type=devicetype, name='Front Port Template 2', type=PortTypeChoices.TYPE_8P8C),
|
||||
FrontPortTemplate(module_type=moduletype, name='Front Port Template 5', type=PortTypeChoices.TYPE_8P8C),
|
||||
FrontPortTemplate(module_type=moduletype, name='Front Port Template 6', type=PortTypeChoices.TYPE_8P8C),
|
||||
)
|
||||
FrontPortTemplate.objects.bulk_create(front_port_templates)
|
||||
PortAssignmentTemplate.objects.bulk_create([
|
||||
PortAssignmentTemplate(front_port=front_port_templates[0], rear_port=rear_port_templates[0]),
|
||||
PortAssignmentTemplate(front_port=front_port_templates[1], rear_port=rear_port_templates[1]),
|
||||
PortAssignmentTemplate(front_port=front_port_templates[2], rear_port=rear_port_templates[4]),
|
||||
PortAssignmentTemplate(front_port=front_port_templates[3], rear_port=rear_port_templates[5]),
|
||||
])
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
@@ -2017,49 +2003,63 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
|
||||
RearPort.objects.bulk_create(rear_ports)
|
||||
|
||||
front_ports = (
|
||||
FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
|
||||
FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
|
||||
FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[2]),
|
||||
FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
|
||||
FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
|
||||
FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C),
|
||||
)
|
||||
FrontPort.objects.bulk_create(front_ports)
|
||||
PortAssignment.objects.bulk_create([
|
||||
PortAssignment(front_port=front_ports[0], rear_port=rear_ports[0]),
|
||||
PortAssignment(front_port=front_ports[1], rear_port=rear_ports[1]),
|
||||
PortAssignment(front_port=front_ports[2], rear_port=rear_ports[2]),
|
||||
])
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Front Port 4',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_ports[3].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_ports[3].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Front Port 5',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_ports[4].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_ports[4].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'device': device.pk,
|
||||
'name': 'Front Port 6',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_ports[5].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_ports[5].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@tag('regression') # Issue #18991
|
||||
def test_front_port_paths(self):
|
||||
device = Device.objects.first()
|
||||
rear_port = RearPort.objects.create(
|
||||
device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C
|
||||
)
|
||||
interface1 = Interface.objects.create(device=device, name='Interface 1')
|
||||
front_port = FrontPort.objects.create(
|
||||
device=device,
|
||||
name='Rear Port 10',
|
||||
type=PortTypeChoices.TYPE_8P8C,
|
||||
rear_port=rear_port,
|
||||
)
|
||||
rear_port = RearPort.objects.create(device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C)
|
||||
front_port = FrontPort.objects.create(device=device, name='Front Port 10', type=PortTypeChoices.TYPE_8P8C)
|
||||
PortAssignment.objects.create(front_port=front_port, rear_port=rear_port)
|
||||
Cable.objects.create(a_terminations=[interface1], b_terminations=[front_port])
|
||||
|
||||
self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
|
||||
|
||||
Reference in New Issue
Block a user