diff --git a/netbox/dcim/migrations/0221_m2m_port_assignments.py b/netbox/dcim/migrations/0221_m2m_port_assignments.py index d9030cd4e..7b29137d4 100644 --- a/netbox/dcim/migrations/0221_m2m_port_assignments.py +++ b/netbox/dcim/migrations/0221_m2m_port_assignments.py @@ -68,8 +68,6 @@ class Migration(migrations.Migration): ( 'front_port_position', models.PositiveSmallIntegerField( - blank=True, - null=True, validators=[ django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024) @@ -129,8 +127,6 @@ class Migration(migrations.Migration): ( 'front_port_position', models.PositiveSmallIntegerField( - blank=True, - null=True, validators=[ django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024) diff --git a/netbox/dcim/models/base.py b/netbox/dcim/models/base.py new file mode 100644 index 000000000..41ef27131 --- /dev/null +++ b/netbox/dcim/models/base.py @@ -0,0 +1,57 @@ +from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from dcim.constants import PORT_POSITION_MAX, PORT_POSITION_MIN + +__all__ = ( + 'PortAssignmentBase', +) + + +class PortAssignmentBase(models.Model): + """ + Base class for PortAssignment and PortAssignment Template + """ + front_port_position = models.PositiveSmallIntegerField( + validators=( + MinValueValidator(PORT_POSITION_MIN), + MaxValueValidator(PORT_POSITION_MAX), + ), + ) + rear_port_position = models.PositiveSmallIntegerField( + validators=( + MinValueValidator(PORT_POSITION_MIN), + MaxValueValidator(PORT_POSITION_MAX), + ), + ) + + class Meta: + abstract = True + constraints = ( + models.UniqueConstraint( + fields=('front_port', 'front_port_position'), + name='%(app_label)s_%(class)s_unique_front_port_position' + ), + models.UniqueConstraint( + fields=('rear_port', 'rear_port_position'), + name='%(app_label)s_%(class)s_unique_rear_port_position' + ), + ) + + def clean(self): + super().clean() + + # Validate rear port position assignment + if self.rear_port_position > self.rear_port.positions: + raise ValidationError({ + "rear_port_position": _( + "Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} " + "positions." + ).format( + rear_port_position=self.rear_port_position, + name=self.rear_port.name, + positions=self.rear_port.positions + ) + }) diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index d9f70ee25..6a6e967cf 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -7,6 +7,7 @@ from mptt.models import MPTTModel, TreeForeignKey from dcim.choices import * from dcim.constants import * +from dcim.models.base import PortAssignmentBase from dcim.models.mixins import InterfaceValidationMixin from netbox.models import ChangeLoggedModel from utilities.fields import ColorField, NaturalOrderingField @@ -28,6 +29,7 @@ __all__ = ( 'InterfaceTemplate', 'InventoryItemTemplate', 'ModuleBayTemplate', + 'PortAssignmentTemplate', 'PowerOutletTemplate', 'PowerPortTemplate', 'RearPortTemplate', @@ -518,7 +520,7 @@ class InterfaceTemplate(InterfaceValidationMixin, ModularComponentTemplateModel) } -class PortAssignmentTemplate(models.Model): +class PortAssignmentTemplate(PortAssignmentBase): """ Maps a FrontPortTemplate & position to a RearPortTemplate & position. """ @@ -526,38 +528,13 @@ class PortAssignmentTemplate(models.Model): to='dcim.FrontPortTemplate', on_delete=models.CASCADE, ) - front_port_position = models.PositiveSmallIntegerField( - blank=True, - null=True, - validators=( - MinValueValidator(PORT_POSITION_MIN), - MaxValueValidator(PORT_POSITION_MAX), - ), - ) rear_port = models.ForeignKey( to='dcim.RearPortTemplate', on_delete=models.CASCADE, ) - rear_port_position = models.PositiveSmallIntegerField( - validators=( - MinValueValidator(PORT_POSITION_MIN), - MaxValueValidator(PORT_POSITION_MAX), - ), - ) - - class Meta: - constraints = ( - models.UniqueConstraint( - fields=('front_port', 'front_port_position'), - name='%(app_label)s_%(class)s_unique_front_port_position' - ), - models.UniqueConstraint( - fields=('rear_port', 'rear_port_position'), - name='%(app_label)s_%(class)s_unique_rear_port_position' - ), - ) def clean(self): + super().clean() # Validate rear port assignment if self.front_port.device_type_id != self.rear_port.device_type_id: @@ -567,19 +544,6 @@ class PortAssignmentTemplate(models.Model): ) }) - # Validate rear port position assignment - if self.rear_port_position > self.rear_port.positions: - raise ValidationError({ - "rear_port_position": _( - "Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} " - "positions." - ).format( - rear_port_position=self.rear_port_position, - name=self.rear_port.name, - positions=self.rear_port.positions - ) - }) - class FrontPortTemplate(ModularComponentTemplateModel): """ diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 49f0df81c..be340fde3 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -11,6 +11,7 @@ from mptt.models import MPTTModel, TreeForeignKey from dcim.choices import * from dcim.constants import * from dcim.fields import WWNField +from dcim.models.base import PortAssignmentBase from dcim.models.mixins import InterfaceValidationMixin from netbox.choices import ColorChoices from netbox.models import OrganizationalModel, NetBoxModel @@ -1070,7 +1071,7 @@ class Interface( # Pass-through ports # -class PortAssignment(models.Model): +class PortAssignment(PortAssignmentBase): """ Maps a FrontPort & position to a RearPort & position. """ @@ -1078,38 +1079,13 @@ class PortAssignment(models.Model): to='dcim.FrontPort', on_delete=models.CASCADE, ) - front_port_position = models.PositiveSmallIntegerField( - blank=True, - null=True, - validators=( - MinValueValidator(PORT_POSITION_MIN), - MaxValueValidator(PORT_POSITION_MAX), - ), - ) rear_port = models.ForeignKey( to='dcim.RearPort', on_delete=models.CASCADE, ) - rear_port_position = models.PositiveSmallIntegerField( - validators=( - MinValueValidator(PORT_POSITION_MIN), - MaxValueValidator(PORT_POSITION_MAX), - ), - ) - - class Meta: - constraints = ( - models.UniqueConstraint( - fields=('front_port', 'front_port_position'), - name='%(app_label)s_%(class)s_unique_front_port_position' - ), - models.UniqueConstraint( - fields=('rear_port', 'rear_port_position'), - name='%(app_label)s_%(class)s_unique_rear_port_position' - ), - ) def clean(self): + super().clean() # Validate rear port assignment if self.front_port.device_id != self.rear_port.device_id: @@ -1119,19 +1095,6 @@ class PortAssignment(models.Model): ) }) - # Validate rear port position assignment - if self.rear_port_position > self.rear_port.positions: - raise ValidationError({ - "rear_port_position": _( - "Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} " - "positions." - ).format( - rear_port_position=self.rear_port_position, - name=self.rear_port.name, - positions=self.rear_port.positions - ) - }) - class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): """