Răsfoiți Sursa

Fixes #20584: Ensure consistent validation between Interface & InterfaceTemplate (#20589)

Jeremy Stretch 4 luni în urmă
părinte
comite
addda0538f

+ 2 - 8
netbox/dcim/models/device_component_templates.py

@@ -7,6 +7,7 @@ from mptt.models import MPTTModel, TreeForeignKey
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
+from dcim.models.mixins import InterfaceValidationMixin
 from netbox.models import ChangeLoggedModel
 from netbox.models import ChangeLoggedModel
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.mptt import TreeManager
 from utilities.mptt import TreeManager
@@ -405,7 +406,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
         }
         }
 
 
 
 
-class InterfaceTemplate(ModularComponentTemplateModel):
+class InterfaceTemplate(InterfaceValidationMixin, ModularComponentTemplateModel):
     """
     """
     A template for a physical data interface on a new Device.
     A template for a physical data interface on a new Device.
     """
     """
@@ -469,8 +470,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
         super().clean()
         super().clean()
 
 
         if self.bridge:
         if self.bridge:
-            if self.pk and self.bridge_id == self.pk:
-                raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
             if self.device_type and self.device_type != self.bridge.device_type:
             if self.device_type and self.device_type != self.bridge.device_type:
                 raise ValidationError({
                 raise ValidationError({
                     'bridge': _(
                     'bridge': _(
@@ -484,11 +483,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
                     ).format(bridge=self.bridge)
                     ).format(bridge=self.bridge)
                 })
                 })
 
 
-        if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
-            raise ValidationError({
-                'rf_role': "Wireless role may be set only on wireless interfaces."
-            })
-
     def instantiate(self, **kwargs):
     def instantiate(self, **kwargs):
         return self.component_model(
         return self.component_model(
             name=self.resolve_name(kwargs.get('module')),
             name=self.resolve_name(kwargs.get('module')),

+ 10 - 26
netbox/dcim/models/device_components.py

@@ -11,6 +11,7 @@ from mptt.models import MPTTModel, TreeForeignKey
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
 from dcim.fields import WWNField
 from dcim.fields import WWNField
+from dcim.models.mixins import InterfaceValidationMixin
 from netbox.choices import ColorChoices
 from netbox.choices import ColorChoices
 from netbox.models import OrganizationalModel, NetBoxModel
 from netbox.models import OrganizationalModel, NetBoxModel
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.fields import ColorField, NaturalOrderingField
@@ -676,7 +677,14 @@ class BaseInterface(models.Model):
             return self.primary_mac_address.mac_address
             return self.primary_mac_address.mac_address
 
 
 
 
-class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin):
+class Interface(
+    InterfaceValidationMixin,
+    ModularComponentModel,
+    BaseInterface,
+    CabledObjectModel,
+    PathEndpoint,
+    TrackingModelMixin,
+):
     """
     """
     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.
     """
     """
@@ -893,10 +901,6 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
 
 
         # Bridge validation
         # Bridge validation
 
 
-        # An interface cannot be bridged to itself
-        if self.pk and self.bridge_id == self.pk:
-            raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
-
         # A bridged interface belongs to the same device or virtual chassis
         # A bridged interface belongs to the same device or virtual chassis
         if self.bridge and self.bridge.device != self.device:
         if self.bridge and self.bridge.device != self.device:
             if self.device.virtual_chassis is None:
             if self.device.virtual_chassis is None:
@@ -942,29 +946,9 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
                     )
                     )
                 })
                 })
 
 
-        # PoE validation
-
-        # Only physical interfaces may have a PoE mode/type assigned
-        if self.poe_mode and self.is_virtual:
-            raise ValidationError({
-                'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
-            })
-        if self.poe_type and self.is_virtual:
-            raise ValidationError({
-                'poe_type': _("Virtual interfaces cannot have a PoE type.")
-            })
-
-        # An interface with a PoE type set must also specify a mode
-        if self.poe_type and not self.poe_mode:
-            raise ValidationError({
-                'poe_type': _("Must specify PoE mode when designating a PoE type.")
-            })
-
         # Wireless validation
         # Wireless validation
 
 
-        # RF role & channel may only be set for wireless interfaces
-        if self.rf_role and not self.is_wireless:
-            raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")})
+        # RF channel may only be set for wireless interfaces
         if self.rf_channel and not self.is_wireless:
         if self.rf_channel and not self.is_wireless:
             raise ValidationError({'rf_channel': _("Channel may be set only on wireless interfaces.")})
             raise ValidationError({'rf_channel': _("Channel may be set only on wireless interfaces.")})
 
 

+ 33 - 0
netbox/dcim/models/mixins.py

@@ -4,8 +4,11 @@ from django.core.exceptions import ValidationError
 from django.db import models
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
+from dcim.constants import VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES
+
 __all__ = (
 __all__ = (
     'CachedScopeMixin',
     'CachedScopeMixin',
+    'InterfaceValidationMixin',
     'RenderConfigMixin',
     'RenderConfigMixin',
 )
 )
 
 
@@ -116,3 +119,33 @@ class CachedScopeMixin(models.Model):
                 self._site = self.scope.site
                 self._site = self.scope.site
                 self._location = self.scope
                 self._location = self.scope
     cache_related_objects.alters_data = True
     cache_related_objects.alters_data = True
+
+
+class InterfaceValidationMixin:
+
+    def clean(self):
+        super().clean()
+
+        # An interface cannot be bridged to itself
+        if self.pk and self.bridge_id == self.pk:
+            raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
+
+        # Only physical interfaces may have a PoE mode/type assigned
+        if self.poe_mode and self.type in VIRTUAL_IFACE_TYPES:
+            raise ValidationError({
+                'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
+            })
+        if self.poe_type and self.type in VIRTUAL_IFACE_TYPES:
+            raise ValidationError({
+                'poe_type': _("Virtual interfaces cannot have a PoE type.")
+            })
+
+        # An interface with a PoE type set must also specify a mode
+        if self.poe_type and not self.poe_mode:
+            raise ValidationError({
+                'poe_type': _("Must specify PoE mode when designating a PoE type.")
+            })
+
+        # RF role may be set only for wireless interfaces
+        if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
+            raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")})