diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 1f5d50c4d..95000bb24 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1692,10 +1692,67 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): # class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): - interface_a = forms.ChoiceField( - choices=[], + site_a = forms.ModelChoiceField( + queryset=Site.objects.all(), + label='Site', + required=False, + widget=forms.Select( + attrs={'filter-for': 'rack_a'} + ) + ) + rack_a = ChainedModelChoiceField( + queryset=Rack.objects.all(), + chains=( + ('site', 'site_a'), + ), + label='Rack', + required=False, + widget=APISelect( + api_url='/api/dcim/racks/?site_id={{site_a}}', + attrs={'filter-for': 'device_a', 'nullable': 'true'} + ) + ) + device_a = ChainedModelChoiceField( + queryset=Device.objects.all(), + chains=( + ('site', 'site_a'), + ('rack', 'rack_a'), + ), + label='Device', + required=False, + widget=APISelect( + api_url='/api/dcim/devices/?site_id={{site_a}}&rack_id={{rack_a}}', + display_field='display_name', + attrs={'filter-for': 'interface_a'} + ) + ) + livesearch_a = forms.CharField( + required=False, + label='Device', + widget=Livesearch( + query_key='q', + query_url='dcim-api:device-list', + field_to_update='device_a' + ) + ) + #interface_a = forms.ChoiceField( + # choices=[], + # widget=SelectWithDisabled, + # label='Interface' + #) + interface_a = ChainedModelChoiceField( + queryset=Interface.objects.connectable().select_related( + 'circuit_termination', 'connected_as_a', 'connected_as_b' + ), + chains=( + ('device', 'device_a'), + ), + label='Interface', widget=SelectWithDisabled, - label='Interface' + #widget=APISelect( + # api_url='/api/dcim/interfaces/?device_id={{device_a}}&type=physical', + # disabled_indicator='is_connected' + #) ) site_b = forms.ModelChoiceField( queryset=Site.objects.all(), @@ -1756,19 +1813,33 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor class Meta: model = InterfaceConnection - fields = ['interface_a', 'site_b', 'rack_b', 'device_b', 'interface_b', 'livesearch', 'connection_status'] + fields = ['site_a','rack_a','device_a','interface_a', 'livesearch_a','site_b', 'rack_b', 'device_b', 'interface_b', 'livesearch', 'connection_status','connection_name'] - def __init__(self, device_a, *args, **kwargs): + def __init__(self, *args, **kwargs): super(InterfaceConnectionForm, self).__init__(*args, **kwargs) - # Initialize interface A choices - device_a_interfaces = Interface.objects.connectable().order_naturally().filter(device=device_a).select_related( - 'circuit_termination', 'connected_as_a', 'connected_as_b' - ) - self.fields['interface_a'].choices = [ - (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces - ] + #if self.instance.pk: + + + #else: + + if self.initial.get('device_a'): + + # Initialize interface A choices + device_a_interfaces = Interface.objects.connectable().order_naturally().filter(device=self.initial['device_a']).select_related( + 'circuit_termination', 'connected_as_a', 'connected_as_b' + ) + + self.fields['interface_a'].choices = [ + (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces + ] + + # Mark connected interfaces as disabled + if self.data.get('device_a'): + self.fields['interface_a'].choices = [ + (iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in self.fields['interface_a'].queryset + ] # Mark connected interfaces as disabled if self.data.get('device_b'): @@ -1800,10 +1871,14 @@ class InterfaceConnectionCSVForm(forms.ModelForm): choices=CONNECTION_STATUS_CHOICES, help_text='Connection status' ) + connection_name = forms.CharField( + help_text='Name of this connection (e.g. cable-number)', + required = False + ) class Meta: model = InterfaceConnection - fields = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'] + fields = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status','connection_name'] def clean_interface_a(self): diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index d50b35487..fbfec61ae 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1370,8 +1370,9 @@ class InterfaceConnection(models.Model): interface_b = models.OneToOneField('Interface', related_name='connected_as_b', on_delete=models.CASCADE) connection_status = models.BooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED, verbose_name='Status') + connection_name = models.CharField(max_length = 64,blank = True) - csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'] + csv_headers = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status','connection_name'] def clean(self): try: @@ -1390,6 +1391,7 @@ class InterfaceConnection(models.Model): self.interface_b.device.identifier, self.interface_b.name, self.get_connection_status_display(), + self.connection_name, ]) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index e95277704..7170ac89c 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -519,7 +519,8 @@ class InterfaceConnectionTable(BaseTable): device_b = tables.LinkColumn('dcim:device', accessor=Accessor('interface_b.device'), args=[Accessor('interface_b.device.pk')], verbose_name='Device B') interface_b = tables.Column(verbose_name='Interface B') + connection_name = tables.Column(verbose_name='Connection Name') class Meta(BaseTable.Meta): model = Interface - fields = ('device_a', 'interface_a', 'device_b', 'interface_b') + fields = ('device_a', 'interface_a', 'device_b', 'interface_b','connection_name') diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index a15774569..42c5df5b4 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -182,6 +182,7 @@ urlpatterns = [ url(r'^devices/(?P\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), url(r'^devices/(?P\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'), url(r'^interface-connections/(?P\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'), + url(r'^interface-connections/(?P\d+)/edit/$', views.InterfaceConnectionEditView.as_view(), name='interfaceconnection_edit'), url(r'^interfaces/(?P\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'), url(r'^interfaces/(?P\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 9b681e4a7..87199245c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1682,7 +1682,7 @@ def interfaceconnection_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': - form = forms.InterfaceConnectionForm(device, request.POST) + form = forms.InterfaceConnectionForm( request.POST) if form.is_valid(): interfaceconnection = form.save() @@ -1709,12 +1709,14 @@ def interfaceconnection_add(request, pk): return redirect('dcim:device', pk=device.pk) else: - form = forms.InterfaceConnectionForm(device, initial={ + form = forms.InterfaceConnectionForm(initial={ + 'device_a': pk, 'interface_a': request.GET.get('interface_a'), 'site_b': request.GET.get('site_b'), 'rack_b': request.GET.get('rack_b'), 'device_b': request.GET.get('device_b'), 'interface_b': request.GET.get('interface_b'), + 'connection_name': request.GET.get('connection_name'), }) return render(request, 'dcim/interfaceconnection_edit.html', { @@ -1768,6 +1770,21 @@ def interfaceconnection_delete(request, pk): }) +class InterfaceConnectionEditView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.edit_interfaceconnection' + model = InterfaceConnection + #parent_field = 'id' + model_form = forms.InterfaceConnectionForm + template_name = 'dcim/interfaceconnection_edit.html' + + #template_name = 'dcim/device_edit.html' + #default_return_url = 'dcim:device_list' + + #def alter_obj(self, obj, request, url_args, url_kwargs): + # if 'device' in url_kwargs: + # obj.device = get_object_or_404(InterfaceConnection, pk=url_kwargs['device']) + # return obj + class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.change_interface' model_form = forms.InterfaceConnectionCSVForm diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index b0c35a0e9..8228849a5 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -48,6 +48,7 @@ {{ connected_iface }} + {% if iface.connection.connection_name %}
(via {{ iface.connection.connection_name }}){% endif %} {% endwith %} {% elif iface.circuit_termination %} @@ -98,6 +99,9 @@ {% endif %} + diff --git a/netbox/templates/dcim/interfaceconnection_edit.html b/netbox/templates/dcim/interfaceconnection_edit.html index 488c11472..a83b7e67b 100644 --- a/netbox/templates/dcim/interfaceconnection_edit.html +++ b/netbox/templates/dcim/interfaceconnection_edit.html @@ -82,6 +82,12 @@ {% render_field form.connection_status %} +
+ +
+ {% render_field form.connection_name %} +
+