Jeremy Stretch 5 лет назад
Родитель
Сommit
e3820e93b7

+ 2 - 4
netbox/dcim/models/device_components.py

@@ -622,8 +622,7 @@ class BaseInterface(models.Model):
 @extras_features('graphs', 'export_templates', 'webhooks')
 class Interface(CableTermination, ComponentModel, BaseInterface):
     """
-    A network interface within a Device. A physical Interface can connect to exactly one other
-    Interface.
+    A network interface within a Device. A physical Interface can connect to exactly one other Interface.
     """
     device = models.ForeignKey(
         to='Device',
@@ -695,8 +694,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
     tags = TaggableManager(through=TaggedItem)
 
     csv_headers = [
-        'device', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only',
-        'description', 'mode',
+        'device', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode',
     ]
 
     class Meta:

+ 1 - 1
netbox/ipam/constants.py

@@ -33,7 +33,7 @@ PREFIX_LENGTH_MAX = 127  # IPv6
 
 IPADDRESS_ASSIGNMENT_MODELS = Q(
     Q(app_label='dcim', model='interface') |
-    Q(app_label='virtualization', model='interface')
+    Q(app_label='virtualization', model='vminterface')
 )
 
 IPADDRESS_MASK_LENGTH_MIN = 1

+ 1 - 0
netbox/ipam/filters.py

@@ -319,6 +319,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
         field_name='pk',
         label='Virtual machine (ID)',
     )
+    # TODO: Restore filtering by assigned interface
     # interface = django_filters.ModelMultipleChoiceFilter(
     #     field_name='interface__name',
     #     queryset=Interface.objects.unrestricted(),

+ 1 - 0
netbox/ipam/forms.py

@@ -522,6 +522,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
 #
 
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
+    # TODO: Restore ability to select assigned object when editing IPAddress
     # interface = forms.ModelChoiceField(
     #     queryset=Interface.objects.all(),
     #     required=False

+ 1 - 1
netbox/ipam/migrations/0037_ipaddress_assignment.py

@@ -31,7 +31,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='ipaddress',
             name='assigned_object_type',
-            field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'dcim'), ('model', 'interface')), models.Q(('app_label', 'virtualization'), ('model', 'interface')), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType', blank=True, null=True),
+            field=models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'dcim'), ('model', 'interface')), models.Q(('app_label', 'virtualization'), ('model', 'vminterface')), _connector='OR')), null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
             preserve_default=False,
         ),
         migrations.RunPython(

+ 7 - 3
netbox/ipam/models.py

@@ -753,6 +753,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
         super().save(*args, **kwargs)
 
     def to_objectchange(self, action):
+        # Annotate the assigned object, if any
         return ObjectChange(
             changed_object=self,
             object_repr=str(self),
@@ -764,12 +765,15 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
     def to_csv(self):
 
         # Determine if this IP is primary for a Device
+        is_primary = False
         if self.address.version == 4 and getattr(self, 'primary_ip4_for', False):
             is_primary = True
         elif self.address.version == 6 and getattr(self, 'primary_ip6_for', False):
             is_primary = True
-        else:
-            is_primary = False
+
+        obj_type = None
+        if self.assigned_object_type:
+            obj_type = f'{self.assigned_object_type.app_label}.{self.assigned_object_type.model}'
 
         return (
             self.address,
@@ -777,7 +781,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
             self.tenant.name if self.tenant else None,
             self.get_status_display(),
             self.get_role_display(),
-            '{}.{}'.format(self.assigned_object_type.app_label, self.assigned_object_type.model) if self.assigned_object_type else None,
+            obj_type,
             self.assigned_object_id,
             is_primary,
             self.dns_name,

+ 1 - 13
netbox/ipam/tables.py

@@ -92,14 +92,6 @@ IPADDRESS_ASSIGN_LINK = """
 {% endif %}
 """
 
-IPADDRESS_PARENT = """
-{% if record.interface %}
-    <a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
-{% else %}
-    &mdash;
-{% endif %}
-"""
-
 VRF_LINK = """
 {% if record.vrf %}
     <a href="{{ record.vrf.get_absolute_url }}">{{ record.vrf }}</a>
@@ -477,17 +469,13 @@ class IPAddressAssignTable(BaseTable):
     status = tables.TemplateColumn(
         template_code=STATUS_LABEL
     )
-    parent = tables.TemplateColumn(
-        template_code=IPADDRESS_PARENT,
-        orderable=False
-    )
     assigned_object = tables.Column(
         orderable=False
     )
 
     class Meta(BaseTable.Meta):
         model = IPAddress
-        fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'parent', 'assigned_object', 'description')
+        fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
         orderable = False
 
 

+ 38 - 38
netbox/ipam/views.py

@@ -607,47 +607,47 @@ class IPAddressView(ObjectView):
 
         ipaddress = get_object_or_404(self.queryset, pk=pk)
 
-        # # Parent prefixes table
-        # parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
-        #     vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
-        # ).prefetch_related(
-        #     'site', 'role'
-        # )
-        # parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
-        # parent_prefixes_table.exclude = ('vrf',)
-        #
-        # # Duplicate IPs table
-        # duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
-        #     vrf=ipaddress.vrf, address=str(ipaddress.address)
-        # ).exclude(
-        #     pk=ipaddress.pk
-        # ).prefetch_related(
-        #     'nat_inside'
-        # )
-        # # Exclude anycast IPs if this IP is anycast
-        # if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
-        #     duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
-        # duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
-        #
-        # # Related IP table
-        # related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
-        #     address=str(ipaddress.address)
-        # ).filter(
-        #     vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
-        # )
-        # related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
-        #
-        # paginate = {
-        #     'paginator_class': EnhancedPaginator,
-        #     'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
-        # }
-        # RequestConfig(request, paginate).configure(related_ips_table)
+        # Parent prefixes table
+        parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
+            vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
+        ).prefetch_related(
+            'site', 'role'
+        )
+        parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
+        parent_prefixes_table.exclude = ('vrf',)
+
+        # Duplicate IPs table
+        duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
+            vrf=ipaddress.vrf, address=str(ipaddress.address)
+        ).exclude(
+            pk=ipaddress.pk
+        ).prefetch_related(
+            'nat_inside'
+        )
+        # Exclude anycast IPs if this IP is anycast
+        if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
+            duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
+        duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
+
+        # Related IP table
+        related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
+            address=str(ipaddress.address)
+        ).filter(
+            vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
+        )
+        related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
+
+        paginate = {
+            'paginator_class': EnhancedPaginator,
+            'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
+        }
+        RequestConfig(request, paginate).configure(related_ips_table)
 
         return render(request, 'ipam/ipaddress.html', {
             'ipaddress': ipaddress,
-            # 'parent_prefixes_table': parent_prefixes_table,
-            # 'duplicate_ips_table': duplicate_ips_table,
-            # 'related_ips_table': related_ips_table,
+            'parent_prefixes_table': parent_prefixes_table,
+            'duplicate_ips_table': duplicate_ips_table,
+            'related_ips_table': related_ips_table,
         })
 
 

+ 0 - 4
netbox/utilities/filters.py

@@ -256,10 +256,6 @@ class BaseFilterSet(django_filters.FilterSet):
                 except django_filters.exceptions.FieldLookupError:
                     # The filter could not be created because the lookup expression is not supported on the field
                     continue
-                except Exception as e:
-                    print(existing_filter_name, existing_filter)
-                    print(f'field: {field}, lookup_expr: {lookup_expr}')
-                    raise e
 
                 if lookup_name.startswith('n'):
                     # This is a negation filter which requires a queryset.exclude() clause

+ 1 - 1
netbox/virtualization/api/views.py

@@ -78,4 +78,4 @@ class InterfaceViewSet(ModelViewSet):
         'virtual_machine', 'tags'
     )
     serializer_class = serializers.VMInterfaceSerializer
-    filterset_class = filters.InterfaceFilterSet
+    filterset_class = filters.VMInterfaceFilterSet

+ 2 - 2
netbox/virtualization/filters.py

@@ -15,8 +15,8 @@ __all__ = (
     'ClusterFilterSet',
     'ClusterGroupFilterSet',
     'ClusterTypeFilterSet',
-    'InterfaceFilterSet',
     'VirtualMachineFilterSet',
+    'VMInterfaceFilterSet',
 )
 
 
@@ -201,7 +201,7 @@ class VirtualMachineFilterSet(
         )
 
 
-class InterfaceFilterSet(BaseFilterSet):
+class VMInterfaceFilterSet(BaseFilterSet):
     q = django_filters.CharFilter(
         method='search',
         label='Search',

+ 5 - 5
netbox/virtualization/forms.py

@@ -571,7 +571,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
 # VM interfaces
 #
 
-class InterfaceForm(BootstrapMixin, forms.ModelForm):
+class VMInterfaceForm(BootstrapMixin, forms.ModelForm):
     untagged_vlan = DynamicModelChoiceField(
         queryset=VLAN.objects.all(),
         required=False,
@@ -643,7 +643,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
             self.cleaned_data['tagged_vlans'] = []
 
 
-class InterfaceCreateForm(BootstrapMixin, forms.Form):
+class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
     virtual_machine = forms.ModelChoiceField(
         queryset=VirtualMachine.objects.all(),
         widget=forms.HiddenInput()
@@ -715,7 +715,7 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form):
             self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
 
 
-class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
+class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=VMInterface.objects.all(),
         widget=forms.MultipleHiddenInput()
@@ -785,7 +785,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
                 self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
 
 
-class InterfaceFilterForm(forms.Form):
+class VMInterfaceFilterForm(forms.Form):
     model = VMInterface
     enabled = forms.NullBooleanField(
         required=False,
@@ -815,7 +815,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
         return ','.join(self.cleaned_data.get('tags'))
 
 
-class InterfaceBulkCreateForm(
+class VMInterfaceBulkCreateForm(
     form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
     VirtualMachineBulkAddComponentForm
 ):

+ 4 - 0
netbox/virtualization/models.py

@@ -475,6 +475,10 @@ class VMInterface(BaseInterface):
             object_data=serialize_object(self)
         )
 
+    @property
+    def parent(self):
+        return self.virtual_machine
+
     @property
     def count_ipaddresses(self):
         return self.ip_addresses.count()

+ 1 - 1
netbox/virtualization/tables.py

@@ -172,7 +172,7 @@ class VirtualMachineDetailTable(VirtualMachineTable):
 # VM components
 #
 
-class InterfaceTable(BaseTable):
+class VMInterfaceTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = VMInterface

+ 1 - 1
netbox/virtualization/tests/test_api.py

@@ -194,7 +194,7 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
 
 
 # TODO: Standardize InterfaceTest (pending #4721)
-class InterfaceTest(APITestCase):
+class VMInterfaceTest(APITestCase):
 
     def setUp(self):
 

+ 1 - 1
netbox/virtualization/tests/test_filters.py

@@ -367,7 +367,7 @@ class VirtualMachineTestCase(TestCase):
 
 class InterfaceTestCase(TestCase):
     queryset = VMInterface.objects.all()
-    filterset = InterfaceFilterSet
+    filterset = VMInterfaceFilterSet
 
     @classmethod
     def setUpTestData(cls):

+ 4 - 6
netbox/virtualization/tests/test_views.py

@@ -191,11 +191,11 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
 
 # TODO: Update base class to DeviceComponentViewTestCase
 # Blocked by #4721
-class InterfaceTestCase(
+class VMInterfaceTestCase(
     ViewTestCases.GetObjectViewTestCase,
     ViewTestCases.EditObjectViewTestCase,
     ViewTestCases.DeleteObjectViewTestCase,
-    # ViewTestCases.BulkCreateObjectsViewTestCase,
+    ViewTestCases.BulkCreateObjectsViewTestCase,
     ViewTestCases.BulkEditObjectsViewTestCase,
     ViewTestCases.BulkDeleteObjectsViewTestCase,
 ):
@@ -234,7 +234,6 @@ class InterfaceTestCase(
             'virtual_machine': virtualmachines[1].pk,
             'name': 'Interface X',
             'enabled': False,
-            'mgmt_only': False,
             'mac_address': EUI('01-02-03-04-05-06'),
             'mtu': 2000,
             'description': 'New description',
@@ -248,7 +247,6 @@ class InterfaceTestCase(
             'virtual_machine': virtualmachines[1].pk,
             'name_pattern': 'Interface [4-6]',
             'enabled': False,
-            'mgmt_only': False,
             'mac_address': EUI('01-02-03-04-05-06'),
             'mtu': 2000,
             'description': 'New description',
@@ -264,6 +262,6 @@ class InterfaceTestCase(
             'mtu': 2000,
             'description': 'New description',
             'mode': InterfaceModeChoices.MODE_TAGGED,
-            # 'untagged_vlan': vlans[0].pk,
-            # 'tagged_vlans': [v.pk for v in vlans[1:4]],
+            'untagged_vlan': vlans[0].pk,
+            'tagged_vlans': [v.pk for v in vlans[1:4]],
         }

+ 11 - 11
netbox/virtualization/views.py

@@ -291,9 +291,9 @@ class VirtualMachineBulkDeleteView(BulkDeleteView):
 
 class InterfaceListView(ObjectListView):
     queryset = VMInterface.objects.prefetch_related('virtual_machine', 'virtual_machine__tenant', 'cable')
-    filterset = filters.InterfaceFilterSet
-    filterset_form = forms.InterfaceFilterForm
-    table = tables.InterfaceTable
+    filterset = filters.VMInterfaceFilterSet
+    filterset_form = forms.VMInterfaceFilterForm
+    table = tables.VMInterfaceTable
     action_buttons = ('import', 'export')
 
 
@@ -334,14 +334,14 @@ class InterfaceView(ObjectView):
 # TODO: This should not use ComponentCreateView
 class InterfaceCreateView(ComponentCreateView):
     queryset = VMInterface.objects.all()
-    form = forms.InterfaceCreateForm
-    model_form = forms.InterfaceForm
+    form = forms.VMInterfaceCreateForm
+    model_form = forms.VMInterfaceForm
     template_name = 'virtualization/virtualmachine_component_add.html'
 
 
 class InterfaceEditView(ObjectEditView):
     queryset = VMInterface.objects.all()
-    model_form = forms.InterfaceForm
+    model_form = forms.VMInterfaceForm
     template_name = 'virtualization/vminterface_edit.html'
 
 
@@ -351,13 +351,13 @@ class InterfaceDeleteView(ObjectDeleteView):
 
 class InterfaceBulkEditView(BulkEditView):
     queryset = VMInterface.objects.all()
-    table = tables.InterfaceTable
-    form = forms.InterfaceBulkEditForm
+    table = tables.VMInterfaceTable
+    form = forms.VMInterfaceBulkEditForm
 
 
 class InterfaceBulkDeleteView(BulkDeleteView):
     queryset = VMInterface.objects.all()
-    table = tables.InterfaceTable
+    table = tables.VMInterfaceTable
 
 
 #
@@ -367,9 +367,9 @@ class InterfaceBulkDeleteView(BulkDeleteView):
 class VirtualMachineBulkAddInterfaceView(BulkComponentCreateView):
     parent_model = VirtualMachine
     parent_field = 'virtual_machine'
-    form = forms.InterfaceBulkCreateForm
+    form = forms.VMInterfaceBulkCreateForm
     queryset = VMInterface.objects.all()
-    model_form = forms.InterfaceForm
+    model_form = forms.VMInterfaceForm
     filterset = filters.VirtualMachineFilterSet
     table = tables.VirtualMachineTable
     default_return_url = 'virtualization:virtualmachine_list'