mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-19 03:42:25 -06:00
Update FrontPort model form
This commit is contained in:
@@ -333,14 +333,14 @@ class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
allow_null=True
|
||||
)
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
rear_port = FrontPortRearPortSerializer()
|
||||
rear_ports = FrontPortRearPortSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
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',
|
||||
'rear_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')
|
||||
|
||||
|
||||
@@ -1578,16 +1578,15 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||
|
||||
|
||||
class FrontPortForm(ModularDeviceComponentForm):
|
||||
rear_port = DynamicModelChoiceField(
|
||||
queryset=RearPort.objects.all(),
|
||||
query_params={
|
||||
'device_id': '$device',
|
||||
}
|
||||
rear_ports = forms.MultipleChoiceField(
|
||||
choices=[],
|
||||
label=_('Rear ports'),
|
||||
widget=forms.SelectMultiple(attrs={'size': 6})
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
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',
|
||||
),
|
||||
)
|
||||
@@ -1599,6 +1598,59 @@ class FrontPortForm(ModularDeviceComponentForm):
|
||||
'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):
|
||||
fieldsets = (
|
||||
|
||||
@@ -9,6 +9,10 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name='frontport',
|
||||
name='dcim_frontport_unique_rear_port_position',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='frontport',
|
||||
name='rear_port',
|
||||
|
||||
@@ -35,6 +35,7 @@ __all__ = (
|
||||
'InventoryItemRole',
|
||||
'ModuleBay',
|
||||
'PathEndpoint',
|
||||
'PortAssignment',
|
||||
'PowerOutlet',
|
||||
'PowerPort',
|
||||
'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):
|
||||
"""
|
||||
@@ -1142,40 +1166,10 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
||||
fields=('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_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):
|
||||
"""
|
||||
|
||||
@@ -42,6 +42,7 @@ from wireless.models import WirelessLAN
|
||||
from . import filtersets, forms, tables
|
||||
from .choices import DeviceFaceChoices, InterfaceModeChoices
|
||||
from .models import *
|
||||
from .models.device_components import PortAssignment
|
||||
from .object_actions import BulkAddComponents, BulkDisconnect
|
||||
|
||||
CABLE_TERMINATION_TYPES = {
|
||||
@@ -3242,6 +3243,11 @@ class FrontPortListView(generic.ObjectListView):
|
||||
class FrontPortView(generic.ObjectView):
|
||||
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)
|
||||
class FrontPortCreateView(generic.ComponentCreateView):
|
||||
@@ -3313,6 +3319,11 @@ class RearPortListView(generic.ObjectListView):
|
||||
class RearPortView(generic.ObjectView):
|
||||
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)
|
||||
class RearPortCreateView(generic.ComponentCreateView):
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<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">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<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">
|
||||
<h2 class="card-header">{% trans "Connection" %}</h2>
|
||||
{% if object.mark_connected %}
|
||||
|
||||
Reference in New Issue
Block a user