diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index a3c218fc9..e0259b84b 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -29,6 +29,7 @@ __all__ = ( 'IPAddressBulkAddForm', 'IPAddressForm', 'IPRangeForm', + 'IPAddressFunctionAssignmentsForm', 'L2VPNForm', 'L2VPNTerminationForm', 'PrefixForm', @@ -422,6 +423,51 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): ) +class IPAddressFunctionAssignmentsForm(NetBoxModelForm): + device_object = DynamicModelChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True + ) + vm_object = DynamicModelChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + selector=True + ) + + class Meta: + model = IPAddressFunctionAssignments + fields = [ + 'assigned_ip', 'function', 'tags', + ] + + def __init__(self, *args, **kwargs): + instance = kwargs.get('instance') + initial = kwargs.get('initial', {}).copy() + + if instance: + if type(instance.assigned_object) is Device: + initial['device_object'] = instance.assigned_object + elif type(instance.assigned_object) is VirtualMachine: + initial['vm_object'] = instance.assigned_object + kwargs['initial'] = initial + + super().__init__(*args, **kwargs) + + def clean(self): + super().clean() + + device_object = self.cleaned_data.get('device_object') + vm_object = self.cleaned_data.get('vm_object') + + if not (device_object or vm_object): + raise ValidationError('An ip address function assignment must specify an device or virtualmachine.') + if len([x for x in (device_object, vm_object) if x]) > 1: + raise ValidationError('An ip address function assignment can only have one terminating object (a device or virtualmachine).') + + self.instance.assigned_object = device_object or vm_object + + class FHRPGroupForm(NetBoxModelForm): # Optionally create a new IPAddress along with the FHRPGroup diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 7df1c349c..075600bbc 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -708,11 +708,11 @@ class IPAddressFunctionAssignments(NetBoxModel): def __str__(self): if self.pk is not None: - return f'{self.assigned_object_type} - {self.function} - {self.assigned_ip}' + return f'{self.assigned_object} - {self.function} - {self.assigned_ip}' return super().__str__() def get_absolute_url(self): - return reverse('ipam:ipaddressfunction', args=[self.pk]) + return reverse('ipam:ipaddressfunctionassignments', args=[self.pk]) class IPAddress(PrimaryModel): diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index aff090f3a..9904c3146 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -11,6 +11,7 @@ __all__ = ( 'AssignedIPAddressesTable', 'IPAddressAssignTable', 'IPAddressTable', + 'IPAddressFunctionAssignmentsTable', 'IPRangeTable', 'PrefixTable', 'RIRTable', @@ -424,3 +425,22 @@ class AssignedIPAddressesTable(NetBoxTable): model = IPAddress fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description') exclude = ('id', ) + + +# +# IPAddresses +# + +class IPAddressFunctionAssignmentsTable(TenancyColumnsMixin, NetBoxTable): + + assigned_object = tables.Column(linkify=True) + assigned_ip = tables.Column(linkify=True) + + class Meta(NetBoxTable.Meta): + model = IPAddressFunctionAssignments + fields = ( + 'pk', 'id', 'function', 'assigned_ip', 'assigned_object', 'created', 'last_updated', + ) + default_columns = ( + 'pk', 'assigned_object', 'function', 'assigned_ip', + ) diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 3bfe34b7b..c37b2aa17 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -88,6 +88,13 @@ urlpatterns = [ path('ip-addresses/assign/', views.IPAddressAssignView.as_view(), name='ipaddress_assign'), path('ip-addresses//', include(get_model_urls('ipam', 'ipaddress'))), + # IP address Function Assignments + path('ip-addresses-function-assignments/', views.IPAddressFunctionAssignmentsListView.as_view(), name='ipaddressfunctionassignments_list'), + path('ip-addresses-function-assignments/add/', views.IPAddressFunctionAssignmentsEditView.as_view(), name='ipaddressfunctionassignments_add'), + path('ip-addresses-function-assignments/edit/', views.IPAddressFunctionAssignmentsEditView.as_view(), name='IPAddressFunctionAssignments_edit'), + path('ip-addresses-function-assignments/delete/', views.IPAddressFunctionAssignmentsDeleteView.as_view(), name='IPAddressFunctionAssignments_delete'), + path('ip-addresses-function-assignments//', include(get_model_urls('ipam', 'ipaddressfunctionassignments'))), + # FHRP groups path('fhrp-groups/', views.FHRPGroupListView.as_view(), name='fhrpgroup_list'), path('fhrp-groups/add/', views.FHRPGroupEditView.as_view(), name='fhrpgroup_add'), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 32badd2d5..e0e89cfa2 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -878,10 +878,61 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView): return parent.get_related_ips().restrict(request.user, 'view') +# +# IP addresses +# + + +class IPAddressFunctionAssignmentsListView(generic.ObjectListView): + queryset = IPAddressFunctionAssignments.objects.all() + # filterset = filtersets.IPAddressFunctionAssignmentsFilterSet + # filterset_form = forms.IPAddressFunctionAssignmentsFilterForm + table = tables.IPAddressFunctionAssignmentsTable + + +@register_model_view(IPAddressFunctionAssignments) +class IPAddressFunctionAssignmentsView(generic.ObjectView): + queryset = IPAddressFunctionAssignments.objects.all() + + +@register_model_view(IPAddressFunctionAssignments, 'edit') +class IPAddressFunctionAssignmentsEditView(generic.ObjectEditView): + queryset = IPAddressFunctionAssignments.objects.all() + form = forms.IPAddressFunctionAssignmentsForm + # template_name = 'ipam/ipaddress_edit.html' + + # def alter_object(self, obj, request, url_args, url_kwargs): + + # if 'interface' in request.GET: + # try: + # obj.assigned_object = Interface.objects.get(pk=request.GET['interface']) + # except (ValueError, Interface.DoesNotExist): + # pass + + # elif 'vminterface' in request.GET: + # try: + # obj.assigned_object = VMInterface.objects.get(pk=request.GET['vminterface']) + # except (ValueError, VMInterface.DoesNotExist): + # pass + + # elif 'fhrpgroup' in request.GET: + # try: + # obj.assigned_object = FHRPGroup.objects.get(pk=request.GET['fhrpgroup']) + # except (ValueError, FHRPGroup.DoesNotExist): + # pass + + # return obj + + +@register_model_view(IPAddressFunctionAssignments, 'delete') +class IPAddressFunctionAssignmentsDeleteView(generic.ObjectDeleteView): + queryset = IPAddressFunctionAssignments.objects.all() + # # VLAN groups # + class VLANGroupListView(generic.ObjectListView): queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet diff --git a/netbox/templates/ipam/ipaddressfunctionassignments.html b/netbox/templates/ipam/ipaddressfunctionassignments.html new file mode 100644 index 000000000..8e71342d2 --- /dev/null +++ b/netbox/templates/ipam/ipaddressfunctionassignments.html @@ -0,0 +1,50 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} +{% load render_table from django_tables2 %} + +{% block content %} +
+
+
+
+ IP Address Function Assignment +
+
+ + + + + + + + + + + + + +
Function{{ object.function }}
Assigned IP{{ object.assigned_ip|linkify }}
Assigned Object + {% if object.assigned_object %} + {{ object.assigned_object|linkify }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
+
+
+ {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/comments.html' %} + {% plugin_left_page object %} + + + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %}