mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-18 19:32:24 -06:00
Remove rear_ports M2M fields from FrontPort & FrontPortTemplate
This commit is contained in:
@@ -5,7 +5,8 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import (
|
||||
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, FrontPortTemplate, InterfaceTemplate,
|
||||
InventoryItemTemplate, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||
InventoryItemTemplate, ModuleBayTemplate, PortAssignmentTemplate, PowerOutletTemplate, PowerPortTemplate,
|
||||
RearPortTemplate,
|
||||
)
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||
from netbox.api.gfk_fields import GFKSerializerField
|
||||
@@ -205,6 +206,16 @@ class InterfaceTemplateSerializer(ComponentTemplateSerializer):
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
class RearPortTemplateAssignmentSerializer(serializers.ModelSerializer):
|
||||
front_port = serializers.PrimaryKeyRelatedField(
|
||||
queryset=FrontPortTemplate.objects.all(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PortAssignmentTemplate
|
||||
fields = ('id', 'rear_port_position', 'front_port', 'front_port_position')
|
||||
|
||||
|
||||
class RearPortTemplateSerializer(ComponentTemplateSerializer):
|
||||
device_type = DeviceTypeSerializer(
|
||||
required=False,
|
||||
@@ -219,15 +230,52 @@ class RearPortTemplateSerializer(ComponentTemplateSerializer):
|
||||
default=None
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
front_ports = RearPortTemplateAssignmentSerializer(
|
||||
source='assignments',
|
||||
many=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'color',
|
||||
'positions', 'description', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions',
|
||||
'front_ports', 'description', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
def create(self, validated_data):
|
||||
assignments = validated_data.pop('assignments', [])
|
||||
instance = super().create(validated_data)
|
||||
|
||||
# Create FrontPort assignments
|
||||
for assignment_data in assignments:
|
||||
PortAssignmentTemplate.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
|
||||
PortAssignmentTemplate.objects.filter(rear_port=instance).delete()
|
||||
for assignment_data in assignments:
|
||||
PortAssignmentTemplate.objects.create(rear_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class FrontPortTemplateAssignmentSerializer(serializers.ModelSerializer):
|
||||
rear_port = serializers.PrimaryKeyRelatedField(
|
||||
queryset=RearPortTemplate.objects.all(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PortAssignmentTemplate
|
||||
fields = ('id', 'front_port_position', 'rear_port', 'rear_port_position')
|
||||
|
||||
|
||||
class FrontPortTemplateSerializer(ComponentTemplateSerializer):
|
||||
device_type = DeviceTypeSerializer(
|
||||
@@ -243,7 +291,11 @@ class FrontPortTemplateSerializer(ComponentTemplateSerializer):
|
||||
default=None
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
rear_ports = RearPortTemplateSerializer(nested=True, many=True)
|
||||
rear_ports = FrontPortTemplateAssignmentSerializer(
|
||||
source='assignments',
|
||||
many=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
@@ -253,6 +305,28 @@ class FrontPortTemplateSerializer(ComponentTemplateSerializer):
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
def create(self, validated_data):
|
||||
assignments = validated_data.pop('assignments', [])
|
||||
instance = super().create(validated_data)
|
||||
|
||||
# Create RearPort assignments
|
||||
for assignment_data in assignments:
|
||||
PortAssignmentTemplate.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
|
||||
PortAssignmentTemplate.objects.filter(front_port=instance).delete()
|
||||
for assignment_data in assignments:
|
||||
PortAssignmentTemplate.objects.create(front_port=instance, **assignment_data)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class ModuleBayTemplateSerializer(ComponentTemplateSerializer):
|
||||
device_type = DeviceTypeSerializer(
|
||||
|
||||
@@ -884,10 +884,11 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
)
|
||||
rear_port_template_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='rear_ports',
|
||||
queryset=FrontPortTemplate.objects.all(),
|
||||
label=_('Rear port template (ID)'),
|
||||
rear_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='assignments__rear_port',
|
||||
queryset=RearPort.objects.all(),
|
||||
to_field_name='rear_port',
|
||||
label=_('Rear port (ID)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -900,10 +901,11 @@ class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCom
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
)
|
||||
front_port_template_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='front_ports',
|
||||
queryset=FrontPortTemplate.objects.all(),
|
||||
label=_('Front port template (ID)'),
|
||||
front_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='assignments__front_port',
|
||||
queryset=FrontPort.objects.all(),
|
||||
to_field_name='front_port',
|
||||
label=_('Front port (ID)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -2109,8 +2111,9 @@ class FrontPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet)
|
||||
null_value=None
|
||||
)
|
||||
rear_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='rear_ports',
|
||||
field_name='assignments__rear_port',
|
||||
queryset=RearPort.objects.all(),
|
||||
to_field_name='rear_port',
|
||||
label=_('Rear port (ID)'),
|
||||
)
|
||||
|
||||
@@ -2128,8 +2131,9 @@ class RearPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet):
|
||||
null_value=None
|
||||
)
|
||||
front_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='front_ports',
|
||||
field_name='assignments__front_port',
|
||||
queryset=FrontPort.objects.all(),
|
||||
to_field_name='front_port',
|
||||
label=_('Front port (ID)'),
|
||||
)
|
||||
|
||||
|
||||
@@ -133,8 +133,6 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
|
||||
|
||||
# Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate
|
||||
# positions
|
||||
print(f"name: {self.cleaned_data['name']}")
|
||||
print(f"rear_ports: {self.cleaned_data['rear_ports']}")
|
||||
frontport_count = len(self.cleaned_data['name'])
|
||||
rearport_count = len(self.cleaned_data['rear_ports'])
|
||||
if frontport_count != rearport_count:
|
||||
|
||||
@@ -385,7 +385,8 @@ class DeviceTypeType(PrimaryObjectType):
|
||||
)
|
||||
class FrontPortType(ModularComponentType, CabledObjectMixin):
|
||||
color: str
|
||||
# rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
assignments: List[Annotated["PortAssignmentType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -396,7 +397,8 @@ class FrontPortType(ModularComponentType, CabledObjectMixin):
|
||||
)
|
||||
class FrontPortTemplateType(ModularComponentTemplateType):
|
||||
color: str
|
||||
# rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
assignments: List[Annotated["PortAssignmentTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -636,6 +638,28 @@ class PlatformType(NestedGroupObjectType):
|
||||
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.PortAssignment,
|
||||
fields='__all__',
|
||||
# filters=PortAssignmentFilter,
|
||||
pagination=True
|
||||
)
|
||||
class PortAssignmentType(ModularComponentTemplateType):
|
||||
front_port: Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]
|
||||
rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.PortAssignmentTemplate,
|
||||
fields='__all__',
|
||||
# filters=PortAssignmentTemplateFilter,
|
||||
pagination=True
|
||||
)
|
||||
class PortAssignmentTemplateType(ModularComponentTemplateType):
|
||||
front_port_template: Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]
|
||||
rear_port_template: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.PowerFeed,
|
||||
exclude=['_path'],
|
||||
@@ -768,7 +792,7 @@ class RackRoleType(OrganizationalObjectType):
|
||||
class RearPortType(ModularComponentType, CabledObjectMixin):
|
||||
color: str
|
||||
|
||||
front_ports: List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]
|
||||
assignments: List[Annotated["PortAssignmentType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -780,7 +804,7 @@ class RearPortType(ModularComponentType, CabledObjectMixin):
|
||||
class RearPortTemplateType(ModularComponentTemplateType):
|
||||
color: str
|
||||
|
||||
front_ports: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||
assignments: List[Annotated["PortAssignmentTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
|
||||
@@ -118,17 +118,6 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
|
||||
# Add rear_ports ManyToManyField on FrontPortTemplate
|
||||
migrations.AddField(
|
||||
model_name='frontporttemplate',
|
||||
name='rear_ports',
|
||||
field=models.ManyToManyField(
|
||||
related_name='front_ports',
|
||||
through='dcim.PortAssignmentTemplate',
|
||||
to='dcim.rearporttemplate'
|
||||
),
|
||||
),
|
||||
|
||||
# Create PortAssignment model (for Devices)
|
||||
migrations.CreateModel(
|
||||
name='PortAssignment',
|
||||
@@ -187,17 +176,6 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
|
||||
# Add rear_ports ManyToManyField on FrontPort
|
||||
migrations.AddField(
|
||||
model_name='frontport',
|
||||
name='rear_ports',
|
||||
field=models.ManyToManyField(
|
||||
related_name='front_ports',
|
||||
through='dcim.PortAssignment',
|
||||
to='dcim.rearport'
|
||||
),
|
||||
),
|
||||
|
||||
# Data migration
|
||||
migrations.RunPython(
|
||||
code=populate_port_template_assignments,
|
||||
|
||||
@@ -568,11 +568,6 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
||||
MaxValueValidator(PORT_POSITION_MAX)
|
||||
],
|
||||
)
|
||||
rear_ports = models.ManyToManyField(
|
||||
to='dcim.RearPortTemplate',
|
||||
through='dcim.PortAssignmentTemplate',
|
||||
related_name='front_ports',
|
||||
)
|
||||
|
||||
component_model = FrontPort
|
||||
|
||||
|
||||
@@ -1119,13 +1119,8 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||
MaxValueValidator(PORT_POSITION_MAX)
|
||||
],
|
||||
)
|
||||
rear_ports = models.ManyToManyField(
|
||||
to='dcim.RearPort',
|
||||
through='dcim.PortAssignment',
|
||||
related_name='front_ports',
|
||||
)
|
||||
|
||||
clone_fields = ('device', 'type', 'color')
|
||||
clone_fields = ('device', 'type', 'color', 'positions')
|
||||
|
||||
class Meta(ModularComponentModel.Meta):
|
||||
constraints = (
|
||||
@@ -1159,6 +1154,7 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||
MaxValueValidator(PORT_POSITION_MAX)
|
||||
],
|
||||
)
|
||||
|
||||
clone_fields = ('device', 'type', 'color', 'positions')
|
||||
|
||||
class Meta(ModularComponentModel.Meta):
|
||||
@@ -1170,13 +1166,13 @@ 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.front_ports.count()
|
||||
if self.positions < frontport_count:
|
||||
assignment_count = self.assignments.count()
|
||||
if self.positions < assignment_count:
|
||||
raise ValidationError({
|
||||
"positions": _(
|
||||
"The number of positions cannot be less than the number of mapped front ports "
|
||||
"({frontport_count})"
|
||||
).format(frontport_count=frontport_count)
|
||||
"({count})"
|
||||
).format(count=assignment_count)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -156,8 +156,8 @@ def extend_rearport_cable_paths(instance, created, raw, **kwargs):
|
||||
When a new FrontPort is created, add it to any CablePaths which end at its corresponding RearPort.
|
||||
"""
|
||||
if created and not raw:
|
||||
for rear_port in instance.rear_ports.all():
|
||||
for cablepath in CablePath.objects.filter(_nodes__contains=rear_port):
|
||||
for assignment in instance.assignments.prefetch_related('rear_port'):
|
||||
for cablepath in CablePath.objects.filter(_nodes__contains=assignment.rear_port):
|
||||
cablepath.retrace()
|
||||
|
||||
|
||||
|
||||
@@ -999,29 +999,49 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Front Port Template 3',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_port_templates[2].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_port_templates[2].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Front Port Template 4',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_port_templates[3].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_port_templates[3].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'module_type': moduletype.pk,
|
||||
'name': 'Front Port Template 7',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_port_templates[6].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_port_templates[6].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'module_type': moduletype.pk,
|
||||
'name': 'Front Port Template 8',
|
||||
'type': PortTypeChoices.TYPE_8P8C,
|
||||
'rear_port': rear_port_templates[7].pk,
|
||||
'rear_port_position': 1,
|
||||
'rear_ports': [
|
||||
{
|
||||
'front_port_position': 1,
|
||||
'rear_port': rear_port_templates[7].pk,
|
||||
'rear_port_position': 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user