2
0
Эх сурвалжийг харах

More fixes as a result of code review

Daniel Sheppard 3 жил өмнө
parent
commit
30350e3b40

+ 2 - 5
netbox/ipam/api/serializers.py

@@ -464,9 +464,7 @@ class L2VPNSerializer(NetBoxModelSerializer):
         model = L2VPN
         model = L2VPN
         fields = [
         fields = [
             'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
             'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
-            'description', 'tenant',
-            # Extra Fields
-            'tags', 'custom_fields', 'created', 'last_updated'
+            'description', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
         ]
         ]
 
 
 
 
@@ -482,8 +480,7 @@ class L2VPNTerminationSerializer(NetBoxModelSerializer):
         model = L2VPNTermination
         model = L2VPNTermination
         fields = [
         fields = [
             'id', 'url', 'display', 'l2vpn', 'assigned_object_type', 'assigned_object_id',
             'id', 'url', 'display', 'l2vpn', 'assigned_object_type', 'assigned_object_id',
-            'assigned_object',
-            'tags', 'custom_fields', 'created', 'last_updated'
+            'assigned_object', 'tags', 'custom_fields', 'created', 'last_updated'
         ]
         ]
 
 
     @swagger_serializer_method(serializer_or_field=serializers.DictField)
     @swagger_serializer_method(serializer_or_field=serializers.DictField)

+ 8 - 8
netbox/ipam/choices.py

@@ -191,6 +191,14 @@ class L2VPNTypeChoices(ChoiceSet):
             (TYPE_VPWS, 'VPWS'),
             (TYPE_VPWS, 'VPWS'),
             (TYPE_VPLS, 'VPLS'),
             (TYPE_VPLS, 'VPLS'),
         )),
         )),
+        ('VXLAN', (
+            (TYPE_VXLAN, 'VXLAN'),
+            (TYPE_VXLAN_EVPN, 'VXLAN-EVPN'),
+        )),
+        ('L2VPN E-VPN', (
+            (TYPE_MPLS_EVPN, 'MPLS EVPN'),
+            (TYPE_PBB_EVPN, 'PBB EVPN'),
+        )),
         ('E-Line', (
         ('E-Line', (
             (TYPE_EPL, 'EPL'),
             (TYPE_EPL, 'EPL'),
             (TYPE_EVPL, 'EVPL'),
             (TYPE_EVPL, 'EVPL'),
@@ -203,14 +211,6 @@ class L2VPNTypeChoices(ChoiceSet):
             (TYPE_EPTREE, 'Ethernet Private Tree'),
             (TYPE_EPTREE, 'Ethernet Private Tree'),
             (TYPE_EVPTREE, 'Ethernet Virtual Private Tree'),
             (TYPE_EVPTREE, 'Ethernet Virtual Private Tree'),
         )),
         )),
-        ('VXLAN', (
-            (TYPE_VXLAN, 'VXLAN'),
-            (TYPE_VXLAN_EVPN, 'VXLAN-EVPN'),
-        )),
-        ('L2VPN E-VPN', (
-            (TYPE_MPLS_EVPN, 'MPLS EVPN'),
-            (TYPE_PBB_EVPN, 'PBB EVPN'),
-        ))
     )
     )
 
 
     P2P = (
     P2P = (

+ 29 - 3
netbox/ipam/forms/models.py

@@ -929,6 +929,20 @@ class L2VPNTerminationForm(NetBoxModelForm):
         }
         }
     )
     )
 
 
+    virtual_machine = DynamicModelChoiceField(
+        queryset=VirtualMachine.objects.all(),
+        required=False,
+        query_params={}
+    )
+
+    vminterface = DynamicModelChoiceField(
+        queryset=VMInterface.objects.all(),
+        required=False,
+        query_params={
+            'virtual_machine_id': '$virtual_machine'
+        }
+    )
+
     class Meta:
     class Meta:
         model = L2VPNTermination
         model = L2VPNTermination
         fields = ('l2vpn', )
         fields = ('l2vpn', )
@@ -943,6 +957,8 @@ class L2VPNTerminationForm(NetBoxModelForm):
                 initial['interface'] = instance.assigned_object
                 initial['interface'] = instance.assigned_object
             elif type(instance.assigned_object) is VLAN:
             elif type(instance.assigned_object) is VLAN:
                 initial['vlan'] = instance.assigned_object
                 initial['vlan'] = instance.assigned_object
+            elif type(instance.assigned_object) is VMInterface:
+                initial['vminterface'] = instance.assigned_object
             kwargs['initial'] = initial
             kwargs['initial'] = initial
 
 
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
@@ -950,11 +966,21 @@ class L2VPNTerminationForm(NetBoxModelForm):
     def clean(self):
     def clean(self):
         super().clean()
         super().clean()
 
 
-        if not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')):
+        interface = self.cleaned_data.get('interface')
+        vlan = self.cleaned_data.get('vlan')
+        vminterface = self.cleaned_data.get('vminterface')
+
+        if not (interface or vlan or vminterface):
             raise ValidationError('You must have either a interface or a VLAN')
             raise ValidationError('You must have either a interface or a VLAN')
 
 
-        if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'):
+        if interface and vlan and vminterface:
+            raise ValidationError('Cannot assign a interface, vlan and vminterface')
+        elif interface and vlan:
             raise ValidationError('Cannot assign both a interface and vlan')
             raise ValidationError('Cannot assign both a interface and vlan')
+        elif interface and vminterface:
+            raise ValidationError('Cannot assign both a interface and vminterface')
+        elif vlan and vminterface:
+            raise ValidationError('Cannot assign both a vlan and vminterface')
 
 
-        obj = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')
+        obj = interface or vlan or vminterface
         self.instance.assigned_object = obj
         self.instance.assigned_object = obj

+ 5 - 13
netbox/ipam/models/l2vpn.py

@@ -60,23 +60,15 @@ class L2VPNTermination(NetBoxModel):
     l2vpn = models.ForeignKey(
     l2vpn = models.ForeignKey(
         to='ipam.L2VPN',
         to='ipam.L2VPN',
         on_delete=models.CASCADE,
         on_delete=models.CASCADE,
-        related_name='terminations',
-        blank=False,
-        null=False
+        related_name='terminations'
     )
     )
-
     assigned_object_type = models.ForeignKey(
     assigned_object_type = models.ForeignKey(
         to=ContentType,
         to=ContentType,
         limit_choices_to=L2VPN_ASSIGNMENT_MODELS,
         limit_choices_to=L2VPN_ASSIGNMENT_MODELS,
         on_delete=models.PROTECT,
         on_delete=models.PROTECT,
-        related_name='+',
-        blank=True,
-        null=True
-    )
-    assigned_object_id = models.PositiveBigIntegerField(
-        blank=True,
-        null=True
+        related_name='+'
     )
     )
+    assigned_object_id = models.PositiveBigIntegerField()
     assigned_object = GenericForeignKey(
     assigned_object = GenericForeignKey(
         ct_field='assigned_object_type',
         ct_field='assigned_object_type',
         fk_field='assigned_object_id'
         fk_field='assigned_object_id'
@@ -95,13 +87,13 @@ class L2VPNTermination(NetBoxModel):
     def __str__(self):
     def __str__(self):
         if self.pk is not None:
         if self.pk is not None:
             return f'{self.assigned_object} <> {self.l2vpn}'
             return f'{self.assigned_object} <> {self.l2vpn}'
-        return ''
+        return super().__str__()
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('ipam:l2vpntermination', args=[self.pk])
         return reverse('ipam:l2vpntermination', args=[self.pk])
 
 
     def clean(self):
     def clean(self):
-        # Only check is assigned_object is set
+        # Only check is assigned_object is set.  Required otherwise we have an Integrity Error thrown.
         if self.assigned_object:
         if self.assigned_object:
             obj_id = self.assigned_object.pk
             obj_id = self.assigned_object.pk
             obj_type = ContentType.objects.get_for_model(self.assigned_object)
             obj_type = ContentType.objects.get_for_model(self.assigned_object)

+ 21 - 2
netbox/ipam/tables/l2vpn.py

@@ -9,25 +9,44 @@ __all__ = (
     'L2VPNTerminationTable',
     'L2VPNTerminationTable',
 )
 )
 
 
+L2VPN_TARGETS = """
+{% for rt in value.all %}
+  <a href="{{ rt.get_absolute_url }}">{{ rt }}</a>{% if not forloop.last %}<br />{% endif %}
+{% endfor %}
+"""
+
 
 
 class L2VPNTable(NetBoxTable):
 class L2VPNTable(NetBoxTable):
     pk = columns.ToggleColumn()
     pk = columns.ToggleColumn()
     name = tables.Column(
     name = tables.Column(
         linkify=True
         linkify=True
     )
     )
+    import_targets = columns.TemplateColumn(
+        template_code=L2VPN_TARGETS,
+        orderable=False
+    )
+    export_targets = columns.TemplateColumn(
+        template_code=L2VPN_TARGETS,
+        orderable=False
+    )
 
 
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = L2VPN
         model = L2VPN
-        fields = ('pk', 'name', 'description', 'slug', 'type', 'tenant', 'actions')
-        default_columns = ('pk', 'name', 'description', 'actions')
+        fields = ('pk', 'name', 'slug', 'type', 'description', 'import_targets', 'export_targets', 'tenant', 'actions')
+        default_columns = ('pk', 'name', 'type', 'description', 'actions')
 
 
 
 
 class L2VPNTerminationTable(NetBoxTable):
 class L2VPNTerminationTable(NetBoxTable):
     pk = columns.ToggleColumn()
     pk = columns.ToggleColumn()
+    l2vpn = tables.Column(
+        verbose_name='L2VPN',
+        linkify=True
+    )
     assigned_object_type = columns.ContentTypeColumn(
     assigned_object_type = columns.ContentTypeColumn(
         verbose_name='Object Type'
         verbose_name='Object Type'
     )
     )
     assigned_object = tables.Column(
     assigned_object = tables.Column(
+        verbose_name='Assigned Object',
         linkify=True,
         linkify=True,
         orderable=False
         orderable=False
     )
     )

+ 21 - 21
netbox/ipam/urls.py

@@ -187,25 +187,25 @@ urlpatterns = [
     path('services/<int:pk>/journal/', ObjectJournalView.as_view(), name='service_journal', kwargs={'model': Service}),
     path('services/<int:pk>/journal/', ObjectJournalView.as_view(), name='service_journal', kwargs={'model': Service}),
 
 
     # L2VPN
     # L2VPN
-    path('l2vpn/', views.L2VPNListView.as_view(), name='l2vpn_list'),
-    path('l2vpn/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'),
-    path('l2vpn/import/', views.L2VPNBulkImportView.as_view(), name='l2vpn_import'),
-    path('l2vpn/edit/', views.L2VPNBulkEditView.as_view(), name='l2vpn_bulk_edit'),
-    path('l2vpn/delete/', views.L2VPNBulkDeleteView.as_view(), name='l2vpn_bulk_delete'),
-    path('l2vpn/<int:pk>/', views.L2VPNView.as_view(), name='l2vpn'),
-    path('l2vpn/<int:pk>/edit/', views.L2VPNEditView.as_view(), name='l2vpn_edit'),
-    path('l2vpn/<int:pk>/delete/', views.L2VPNDeleteView.as_view(), name='l2vpn_delete'),
-    path('l2vpn/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpn_changelog', kwargs={'model': L2VPN}),
-    path('l2vpn/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpn_journal', kwargs={'model': L2VPN}),
-
-    path('l2vpn-termination/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
-    path('l2vpn-termination/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
-    path('l2vpn-termination/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
-    path('l2vpn-termination/edit/', views.L2VPNTerminationBulkEditView.as_view(), name='l2vpntermination_bulk_edit'),
-    path('l2vpn-termination/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
-    path('l2vpn-termination/<int:pk>/', views.L2VPNTerminationView.as_view(), name='l2vpntermination'),
-    path('l2vpn-termination/<int:pk>/edit/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_edit'),
-    path('l2vpn-termination/<int:pk>/delete/', views.L2VPNTerminationDeleteView.as_view(), name='l2vpntermination_delete'),
-    path('l2vpn-termination/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpntermination_changelog', kwargs={'model': L2VPNTermination}),
-    path('l2vpn-termination/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpntermination_journal', kwargs={'model': L2VPNTermination}),
+    path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'),
+    path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'),
+    path('l2vpns/import/', views.L2VPNBulkImportView.as_view(), name='l2vpn_import'),
+    path('l2vpns/edit/', views.L2VPNBulkEditView.as_view(), name='l2vpn_bulk_edit'),
+    path('l2vpns/delete/', views.L2VPNBulkDeleteView.as_view(), name='l2vpn_bulk_delete'),
+    path('l2vpns/<int:pk>/', views.L2VPNView.as_view(), name='l2vpn'),
+    path('l2vpns/<int:pk>/edit/', views.L2VPNEditView.as_view(), name='l2vpn_edit'),
+    path('l2vpns/<int:pk>/delete/', views.L2VPNDeleteView.as_view(), name='l2vpn_delete'),
+    path('l2vpns/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpn_changelog', kwargs={'model': L2VPN}),
+    path('l2vpns/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpn_journal', kwargs={'model': L2VPN}),
+
+    path('l2vpn-terminations/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
+    path('l2vpn-terminations/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
+    path('l2vpn-terminations/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
+    path('l2vpn-terminations/edit/', views.L2VPNTerminationBulkEditView.as_view(), name='l2vpntermination_bulk_edit'),
+    path('l2vpn-terminations/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
+    path('l2vpn-terminations/<int:pk>/', views.L2VPNTerminationView.as_view(), name='l2vpntermination'),
+    path('l2vpn-terminations/<int:pk>/edit/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_edit'),
+    path('l2vpn-terminations/<int:pk>/delete/', views.L2VPNTerminationDeleteView.as_view(), name='l2vpntermination_delete'),
+    path('l2vpn-terminations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpntermination_changelog', kwargs={'model': L2VPNTermination}),
+    path('l2vpn-terminations/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpntermination_journal', kwargs={'model': L2VPNTermination}),
 ]
 ]

+ 13 - 3
netbox/templates/ipam/l2vpntermination_edit.html

@@ -12,7 +12,7 @@
       <div class="offset-sm-3">
       <div class="offset-sm-3">
         <ul class="nav nav-pills" role="tablist">
         <ul class="nav nav-pills" role="tablist">
           <li role="presentation" class="nav-item">
           <li role="presentation" class="nav-item">
-            <button role="tab" type="button" id="vlan_tab" data-bs-toggle="tab" aria-controls="vlan" data-bs-target="#vlan" class="nav-link {% if not form.initial.interface %}active{% endif %}">
+            <button role="tab" type="button" id="vlan_tab" data-bs-toggle="tab" aria-controls="vlan" data-bs-target="#vlan" class="nav-link {% if not form.initial.interface or form.initial.vminterface %}active{% endif %}">
               VLAN
               VLAN
             </button>
             </button>
           </li>
           </li>
@@ -21,18 +21,28 @@
               Interface
               Interface
             </button>
             </button>
           </li>
           </li>
+          <li role="presentation" class="nav-item">
+            <button role="tab" type="button" id="vminterface_tab" data-bs-toggle="tab" aria-controls="vminterface" data-bs-target="#vminterface" class="nav-link {% if form.initial.vminterface %}active{% endif %}">
+              VM Interface
+            </button>
+          </li>
         </ul>
         </ul>
       </div>
       </div>
     </div>
     </div>
     <div class="row mb-3">
     <div class="row mb-3">
       <div class="tab-content p-0 border-0">
       <div class="tab-content p-0 border-0">
-        {% render_field form.device %}
-        <div class="tab-pane {% if not form.initial.interface %}active{% endif %}" id="vlan" role="tabpanel" aria-labeled-by="vlan_tab">
+        <div class="tab-pane {% if not form.initial.interface or form.initial.vminterface %}active{% endif %}" id="vlan" role="tabpanel" aria-labeled-by="vlan_tab">
+          {% render_field form.device %}
           {% render_field form.vlan %}
           {% render_field form.vlan %}
         </div>
         </div>
         <div class="tab-pane {% if form.initial.interface %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
         <div class="tab-pane {% if form.initial.interface %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
+          {% render_field form.device %}
           {% render_field form.interface %}
           {% render_field form.interface %}
         </div>
         </div>
+        <div class="tab-pane {% if form.initial.vminterface %}active{% endif %}" id="vminterface" role="tabpanel" aria-labeled-by="vminterface_tab">
+          {% render_field form.virtual_machine %}
+          {% render_field form.vminterface %}
+        </div>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>