mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-20 04:12:25 -06:00
Update FrontPort model form
This commit is contained in:
@@ -333,14 +333,14 @@ class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
|||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
type = ChoiceField(choices=PortTypeChoices)
|
type = ChoiceField(choices=PortTypeChoices)
|
||||||
rear_port = FrontPortRearPortSerializer()
|
rear_ports = FrontPortRearPortSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
|
'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
|
||||||
'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'tags',
|
'rear_ports', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||||
'custom_fields', 'created', 'last_updated', '_occupied',
|
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||||
|
|
||||||
|
|||||||
@@ -1578,16 +1578,15 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||||||
|
|
||||||
|
|
||||||
class FrontPortForm(ModularDeviceComponentForm):
|
class FrontPortForm(ModularDeviceComponentForm):
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_ports = forms.MultipleChoiceField(
|
||||||
queryset=RearPort.objects.all(),
|
choices=[],
|
||||||
query_params={
|
label=_('Rear ports'),
|
||||||
'device_id': '$device',
|
widget=forms.SelectMultiple(attrs={'size': 6})
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'rear_ports', 'mark_connected',
|
||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1599,6 +1598,59 @@ class FrontPortForm(ModularDeviceComponentForm):
|
|||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if device_id := (self.data.get('device') or self.initial.get('device')):
|
||||||
|
device = Device.objects.get(pk=device_id)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Populate rear port choices
|
||||||
|
choices = []
|
||||||
|
for rear_port in RearPort.objects.filter(device=device):
|
||||||
|
for i in range(1, rear_port.positions + 1):
|
||||||
|
choices.append(
|
||||||
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
||||||
|
)
|
||||||
|
self.fields['rear_ports'].choices = choices
|
||||||
|
|
||||||
|
# Set initial rear port assignments
|
||||||
|
if self.instance.pk:
|
||||||
|
self.initial['rear_ports'] = [
|
||||||
|
f'{assignment.rear_port_id}:{assignment.rear_port_position}'
|
||||||
|
for assignment in PortAssignment.objects.filter(front_port_id=self.instance.pk)
|
||||||
|
]
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Count of selected rear port & position pairs much match the assigned number of positions
|
||||||
|
if len(self.cleaned_data['rear_ports']) != self.cleaned_data['positions']:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
_("The number of rear port/position pairs selected must match the number of positions assigned.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def _save_m2m(self):
|
||||||
|
super()._save_m2m()
|
||||||
|
|
||||||
|
# TODO: Can this be made more efficient?
|
||||||
|
# Delete existing rear port assignments
|
||||||
|
PortAssignment.objects.filter(front_port_id=self.instance.pk).delete()
|
||||||
|
|
||||||
|
# Create new rear port assignments
|
||||||
|
assignments = []
|
||||||
|
for i, rp_position in enumerate(self.cleaned_data['rear_ports'], start=1):
|
||||||
|
rear_port_id, rear_port_position = rp_position.split(':')
|
||||||
|
assignments.append(
|
||||||
|
PortAssignment(
|
||||||
|
front_port_id=self.instance.pk,
|
||||||
|
front_port_position=i,
|
||||||
|
rear_port_id=rear_port_id,
|
||||||
|
rear_port_position=rear_port_position,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
PortAssignment.objects.bulk_create(assignments)
|
||||||
|
|
||||||
|
|
||||||
class RearPortForm(ModularDeviceComponentForm):
|
class RearPortForm(ModularDeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='frontport',
|
||||||
|
name='dcim_frontport_unique_rear_port_position',
|
||||||
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='frontport',
|
model_name='frontport',
|
||||||
name='rear_port',
|
name='rear_port',
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ __all__ = (
|
|||||||
'InventoryItemRole',
|
'InventoryItemRole',
|
||||||
'ModuleBay',
|
'ModuleBay',
|
||||||
'PathEndpoint',
|
'PathEndpoint',
|
||||||
|
'PortAssignment',
|
||||||
'PowerOutlet',
|
'PowerOutlet',
|
||||||
'PowerPort',
|
'PowerPort',
|
||||||
'RearPort',
|
'RearPort',
|
||||||
@@ -1106,6 +1107,29 @@ class PortAssignment(models.Model):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Validate rear port assignment
|
||||||
|
if self.front_port.device_id != self.rear_port.device_id:
|
||||||
|
raise ValidationError({
|
||||||
|
"rear_port": _("Rear port ({rear_port}) must belong to the same device").format(
|
||||||
|
rear_port=self.rear_port
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Validate rear port position assignment
|
||||||
|
if self.rear_port_position > self.rear_port.positions:
|
||||||
|
raise ValidationError({
|
||||||
|
"rear_port_position": _(
|
||||||
|
"Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} "
|
||||||
|
"positions."
|
||||||
|
).format(
|
||||||
|
rear_port_position=self.rear_port_position,
|
||||||
|
name=self.rear_port.name,
|
||||||
|
positions=self.rear_port.positions
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||||
"""
|
"""
|
||||||
@@ -1142,40 +1166,10 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
|||||||
fields=('device', 'name'),
|
fields=('device', 'name'),
|
||||||
name='%(app_label)s_%(class)s_unique_device_name'
|
name='%(app_label)s_%(class)s_unique_device_name'
|
||||||
),
|
),
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=('rear_port', 'rear_port_position'),
|
|
||||||
name='%(app_label)s_%(class)s_unique_rear_port_position'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
verbose_name = _('front port')
|
verbose_name = _('front port')
|
||||||
verbose_name_plural = _('front ports')
|
verbose_name_plural = _('front ports')
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
if hasattr(self, 'rear_port'):
|
|
||||||
|
|
||||||
# Validate rear port assignment
|
|
||||||
if self.rear_port.device != self.device:
|
|
||||||
raise ValidationError({
|
|
||||||
"rear_port": _(
|
|
||||||
"Rear port ({rear_port}) must belong to the same device"
|
|
||||||
).format(rear_port=self.rear_port)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Validate rear port position assignment
|
|
||||||
if self.rear_port_position > self.rear_port.positions:
|
|
||||||
raise ValidationError({
|
|
||||||
"rear_port_position": _(
|
|
||||||
"Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} "
|
|
||||||
"positions."
|
|
||||||
).format(
|
|
||||||
rear_port_position=self.rear_port_position,
|
|
||||||
name=self.rear_port.name,
|
|
||||||
positions=self.rear_port.positions
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from wireless.models import WirelessLAN
|
|||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DeviceFaceChoices, InterfaceModeChoices
|
from .choices import DeviceFaceChoices, InterfaceModeChoices
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .models.device_components import PortAssignment
|
||||||
from .object_actions import BulkAddComponents, BulkDisconnect
|
from .object_actions import BulkAddComponents, BulkDisconnect
|
||||||
|
|
||||||
CABLE_TERMINATION_TYPES = {
|
CABLE_TERMINATION_TYPES = {
|
||||||
@@ -3242,6 +3243,11 @@ class FrontPortListView(generic.ObjectListView):
|
|||||||
class FrontPortView(generic.ObjectView):
|
class FrontPortView(generic.ObjectView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'rear_port_assignments': PortAssignment.objects.filter(front_port=instance).prefetch_related('rear_port'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(FrontPort, 'add', detail=False)
|
@register_model_view(FrontPort, 'add', detail=False)
|
||||||
class FrontPortCreateView(generic.ComponentCreateView):
|
class FrontPortCreateView(generic.ComponentCreateView):
|
||||||
@@ -3313,6 +3319,11 @@ class RearPortListView(generic.ObjectListView):
|
|||||||
class RearPortView(generic.ObjectView):
|
class RearPortView(generic.ObjectView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'front_port_assignments': PortAssignment.objects.filter(rear_port=instance).prefetch_related('front_port'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RearPort, 'add', detail=False)
|
@register_model_view(RearPort, 'add', detail=False)
|
||||||
class RearPortCreateView(generic.ComponentCreateView):
|
class RearPortCreateView(generic.ComponentCreateView):
|
||||||
|
|||||||
@@ -61,6 +61,18 @@
|
|||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12 col-md-6">
|
<div class="col col-12 col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Rear Ports" %}</h2>
|
||||||
|
<table class="table table-hover">
|
||||||
|
{% for assignment in rear_port_assignments %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ assignment.front_port_position }}</td>
|
||||||
|
<td>{{ assignment.rear_port|linkify }}</td>
|
||||||
|
<td>{{ assignment.rear_port_position }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||||
{% if object.mark_connected %}
|
{% if object.mark_connected %}
|
||||||
|
|||||||
@@ -61,6 +61,18 @@
|
|||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12 col-md-6">
|
<div class="col col-12 col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Rear Ports" %}</h2>
|
||||||
|
<table class="table table-hover">
|
||||||
|
{% for assignment in front_port_assignments %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ assignment.rear_port_position }}</td>
|
||||||
|
<td>{{ assignment.front_port|linkify }}</td>
|
||||||
|
<td>{{ assignment.front_port_position }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||||
{% if object.mark_connected %}
|
{% if object.mark_connected %}
|
||||||
|
|||||||
Reference in New Issue
Block a user