diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index e0f38afef..a24f9ea6d 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -359,6 +359,16 @@ INTERFACE_BUTTONS = """ {% endif %} +{% elif record.type == 'virtual' %} + {% if perms.vpn.add_tunnel and not record.tunnel_termination %} + + + + {% elif perms.vpn.delete_tunneltermination and record.tunnel_termination %} + + + + {% endif %} {% elif record.is_wired and perms.dcim.add_cable %} diff --git a/netbox/vpn/forms/model_forms.py b/netbox/vpn/forms/model_forms.py index 34976a62a..cc8cb4b1f 100644 --- a/netbox/vpn/forms/model_forms.py +++ b/netbox/vpn/forms/model_forms.py @@ -257,6 +257,12 @@ class TunnelTerminationCreateForm(NetBoxModelForm): 'virtual_machine_id': '$parent', }) + def clean(self): + super().clean() + + # Assign the interface + self.instance.interface = self.cleaned_data['interface'] + class IPSecProfileForm(NetBoxModelForm): comments = CommentField() diff --git a/netbox/vpn/models/tunnels.py b/netbox/vpn/models/tunnels.py index b07ac3ab4..4f3bd4110 100644 --- a/netbox/vpn/models/tunnels.py +++ b/netbox/vpn/models/tunnels.py @@ -1,4 +1,5 @@ -from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.fields import GenericForeignKey +from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -127,6 +128,18 @@ class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLo def get_role_color(self): return TunnelTerminationRoleChoices.colors.get(self.role) + def clean(self): + super().clean() + + # Check that the selected Interface is not already attached to a Tunnel + if self.interface.tunnel_termination: + raise ValidationError({ + 'interface': _("Interface {name} is already attached to a tunnel ({tunnel}).").format( + name=self.interface.name, + tunnel=self.interface.tunnel_termination.tunnel + ) + }) + def to_objectchange(self, action): objectchange = super().to_objectchange(action) objectchange.related_object = self.tunnel