13132 add gettext_lazy to models

This commit is contained in:
Arthur 2023-07-11 12:48:58 +07:00 committed by Jeremy Stretch
parent 2eb2703219
commit 54c610130c

View File

@ -7,7 +7,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import Sum from django.db.models import Sum
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import * from dcim.choices import *
@ -52,6 +52,7 @@ class ComponentModel(NetBoxModel):
related_name='%(class)ss' related_name='%(class)ss'
) )
name = models.CharField( name = models.CharField(
verbose_name=_('name'),
max_length=64 max_length=64
) )
_name = NaturalOrderingField( _name = NaturalOrderingField(
@ -60,11 +61,13 @@ class ComponentModel(NetBoxModel):
blank=True blank=True
) )
label = models.CharField( label = models.CharField(
verbose_name=_('label'),
max_length=64, max_length=64,
blank=True, blank=True,
help_text=_("Physical label") help_text=_('Physical label')
) )
description = models.CharField( description = models.CharField(
verbose_name=_('description'),
max_length=200, max_length=200,
blank=True blank=True
) )
@ -101,7 +104,7 @@ class ComponentModel(NetBoxModel):
# Check list of Modules that allow device field to be changed # Check list of Modules that allow device field to be changed
if (type(self) not in [InventoryItem]) and (self.pk is not None) and (self._original_device != self.device_id): if (type(self) not in [InventoryItem]) and (self.pk is not None) and (self._original_device != self.device_id):
raise ValidationError({ raise ValidationError({
"device": "Components cannot be moved to a different device." "device": _("Components cannot be moved to a different device.")
}) })
@property @property
@ -140,13 +143,15 @@ class CabledObjectModel(models.Model):
null=True null=True
) )
cable_end = models.CharField( cable_end = models.CharField(
verbose_name=_('cable end'),
max_length=1, max_length=1,
blank=True, blank=True,
choices=CableEndChoices choices=CableEndChoices
) )
mark_connected = models.BooleanField( mark_connected = models.BooleanField(
verbose_name=_('mark connected'),
default=False, default=False,
help_text=_("Treat as if a cable is connected") help_text=_('Treat as if a cable is connected')
) )
cable_terminations = GenericRelation( cable_terminations = GenericRelation(
@ -164,15 +169,15 @@ class CabledObjectModel(models.Model):
if self.cable and not self.cable_end: if self.cable and not self.cable_end:
raise ValidationError({ raise ValidationError({
"cable_end": "Must specify cable end (A or B) when attaching a cable." "cable_end": _("Must specify cable end (A or B) when attaching a cable.")
}) })
if self.cable_end and not self.cable: if self.cable_end and not self.cable:
raise ValidationError({ raise ValidationError({
"cable_end": "Cable end must not be set without a cable." "cable_end": _("Cable end must not be set without a cable.")
}) })
if self.mark_connected and self.cable: if self.mark_connected and self.cable:
raise ValidationError({ raise ValidationError({
"mark_connected": "Cannot mark as connected with a cable attached." "mark_connected": _("Cannot mark as connected with a cable attached.")
}) })
@property @property
@ -195,7 +200,7 @@ class CabledObjectModel(models.Model):
@property @property
def parent_object(self): def parent_object(self):
raise NotImplementedError(f"{self.__class__.__name__} models must declare a parent_object property") raise NotImplementedError(_("{class_name} models must declare a parent_object property").format(class_name=self.__class__.__name__))
@property @property
def opposite_cable_end(self): def opposite_cable_end(self):
@ -275,12 +280,14 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
blank=True, blank=True,
help_text=_('Physical port type') help_text=_('Physical port type')
) )
speed = models.PositiveIntegerField( speed = models.PositiveIntegerField(
verbose_name=_('speed'),
choices=ConsolePortSpeedChoices, choices=ConsolePortSpeedChoices,
blank=True, blank=True,
null=True, null=True,
@ -298,12 +305,14 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint,
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
blank=True, blank=True,
help_text=_('Physical port type') help_text=_('Physical port type')
) )
speed = models.PositiveIntegerField( speed = models.PositiveIntegerField(
verbose_name=_('speed'),
choices=ConsolePortSpeedChoices, choices=ConsolePortSpeedChoices,
blank=True, blank=True,
null=True, null=True,
@ -325,22 +334,25 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
blank=True, blank=True,
help_text=_('Physical port type') help_text=_('Physical port type')
) )
maximum_draw = models.PositiveIntegerField( maximum_draw = models.PositiveIntegerField(
verbose_name=_('maximum draw'),
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text=_("Maximum power draw (watts)") help_text=_("Maximum power draw (watts)")
) )
allocated_draw = models.PositiveIntegerField( allocated_draw = models.PositiveIntegerField(
verbose_name=_('allocated draw'),
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text=_("Allocated power draw (watts)") help_text=_('Allocated power draw (watts)')
) )
clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw') clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
@ -354,7 +366,7 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
if self.maximum_draw is not None and self.allocated_draw is not None: if self.maximum_draw is not None and self.allocated_draw is not None:
if self.allocated_draw > self.maximum_draw: if self.allocated_draw > self.maximum_draw:
raise ValidationError({ raise ValidationError({
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)." 'allocated_draw': _("Allocated draw cannot exceed the maximum draw ({maximum_draw}W).").format(maximum_draw=self.maximum_draw)
}) })
def get_downstream_powerports(self, leg=None): def get_downstream_powerports(self, leg=None):
@ -434,6 +446,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
A physical power outlet (output) within a Device which provides power to a PowerPort. A physical power outlet (output) within a Device which provides power to a PowerPort.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
blank=True, blank=True,
@ -447,10 +460,11 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
related_name='poweroutlets' related_name='poweroutlets'
) )
feed_leg = models.CharField( feed_leg = models.CharField(
verbose_name=_('feed leg'),
max_length=50, max_length=50,
choices=PowerOutletFeedLegChoices, choices=PowerOutletFeedLegChoices,
blank=True, blank=True,
help_text=_("Phase (for three-phase feeds)") help_text=_('Phase (for three-phase feeds)')
) )
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg') clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
@ -463,7 +477,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
# Validate power port assignment # Validate power port assignment
if self.power_port and self.power_port.device != self.device: if self.power_port and self.power_port.device != self.device:
raise ValidationError(f"Parent power port ({self.power_port}) must belong to the same device") raise ValidationError(_("Parent power port ({power_port}) must belong to the same device").format(power_port=self.power_port))
# #
@ -475,12 +489,13 @@ class BaseInterface(models.Model):
Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface. Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
""" """
enabled = models.BooleanField( enabled = models.BooleanField(
verbose_name=_('enabled'),
default=True default=True
) )
mac_address = MACAddressField( mac_address = MACAddressField(
null=True, null=True,
blank=True, blank=True,
verbose_name='MAC Address' verbose_name=_('MAC Address')
) )
mtu = models.PositiveIntegerField( mtu = models.PositiveIntegerField(
blank=True, blank=True,
@ -489,13 +504,14 @@ class BaseInterface(models.Model):
MinValueValidator(INTERFACE_MTU_MIN), MinValueValidator(INTERFACE_MTU_MIN),
MaxValueValidator(INTERFACE_MTU_MAX) MaxValueValidator(INTERFACE_MTU_MAX)
], ],
verbose_name='MTU' verbose_name=_('MTU')
) )
mode = models.CharField( mode = models.CharField(
verbose_name=_('mode'),
max_length=50, max_length=50,
choices=InterfaceModeChoices, choices=InterfaceModeChoices,
blank=True, blank=True,
help_text=_("IEEE 802.1Q tagging strategy") help_text=_('IEEE 802.1Q tagging strategy')
) )
parent = models.ForeignKey( parent = models.ForeignKey(
to='self', to='self',
@ -503,7 +519,7 @@ class BaseInterface(models.Model):
related_name='child_interfaces', related_name='child_interfaces',
null=True, null=True,
blank=True, blank=True,
verbose_name='Parent interface' verbose_name=_('Parent interface')
) )
bridge = models.ForeignKey( bridge = models.ForeignKey(
to='self', to='self',
@ -511,7 +527,7 @@ class BaseInterface(models.Model):
related_name='bridge_interfaces', related_name='bridge_interfaces',
null=True, null=True,
blank=True, blank=True,
verbose_name='Bridge interface' verbose_name=_('Bridge interface')
) )
class Meta: class Meta:
@ -559,23 +575,25 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
related_name='member_interfaces', related_name='member_interfaces',
null=True, null=True,
blank=True, blank=True,
verbose_name='Parent LAG' verbose_name=_('Parent LAG')
) )
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=InterfaceTypeChoices choices=InterfaceTypeChoices
) )
mgmt_only = models.BooleanField( mgmt_only = models.BooleanField(
default=False, default=False,
verbose_name='Management only', verbose_name=_('Management only'),
help_text=_('This interface is used only for out-of-band management') help_text=_('This interface is used only for out-of-band management')
) )
speed = models.PositiveIntegerField( speed = models.PositiveIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name='Speed (Kbps)' verbose_name=_('Speed (Kbps)')
) )
duplex = models.CharField( duplex = models.CharField(
verbose_name=_('duplex'),
max_length=50, max_length=50,
blank=True, blank=True,
null=True, null=True,
@ -584,27 +602,27 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
wwn = WWNField( wwn = WWNField(
null=True, null=True,
blank=True, blank=True,
verbose_name='WWN', verbose_name=_('WWN'),
help_text=_('64-bit World Wide Name') help_text=_('64-bit World Wide Name')
) )
rf_role = models.CharField( rf_role = models.CharField(
max_length=30, max_length=30,
choices=WirelessRoleChoices, choices=WirelessRoleChoices,
blank=True, blank=True,
verbose_name='Wireless role' verbose_name=_('Wireless role')
) )
rf_channel = models.CharField( rf_channel = models.CharField(
max_length=50, max_length=50,
choices=WirelessChannelChoices, choices=WirelessChannelChoices,
blank=True, blank=True,
verbose_name='Wireless channel' verbose_name=_('Wireless channel')
) )
rf_channel_frequency = models.DecimalField( rf_channel_frequency = models.DecimalField(
max_digits=7, max_digits=7,
decimal_places=2, decimal_places=2,
blank=True, blank=True,
null=True, null=True,
verbose_name='Channel frequency (MHz)', verbose_name=_('Channel frequency (MHz)'),
help_text=_("Populated by selected channel (if set)") help_text=_("Populated by selected channel (if set)")
) )
rf_channel_width = models.DecimalField( rf_channel_width = models.DecimalField(
@ -612,26 +630,26 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
decimal_places=3, decimal_places=3,
blank=True, blank=True,
null=True, null=True,
verbose_name='Channel width (MHz)', verbose_name=('Channel width (MHz)'),
help_text=_("Populated by selected channel (if set)") help_text=_("Populated by selected channel (if set)")
) )
tx_power = models.PositiveSmallIntegerField( tx_power = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
validators=(MaxValueValidator(127),), validators=(MaxValueValidator(127),),
verbose_name='Transmit power (dBm)' verbose_name=_('Transmit power (dBm)')
) )
poe_mode = models.CharField( poe_mode = models.CharField(
max_length=50, max_length=50,
choices=InterfacePoEModeChoices, choices=InterfacePoEModeChoices,
blank=True, blank=True,
verbose_name='PoE mode' verbose_name=_('PoE mode')
) )
poe_type = models.CharField( poe_type = models.CharField(
max_length=50, max_length=50,
choices=InterfacePoETypeChoices, choices=InterfacePoETypeChoices,
blank=True, blank=True,
verbose_name='PoE type' verbose_name=_('PoE type')
) )
wireless_link = models.ForeignKey( wireless_link = models.ForeignKey(
to='wireless.WirelessLink', to='wireless.WirelessLink',
@ -644,7 +662,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
to='wireless.WirelessLAN', to='wireless.WirelessLAN',
related_name='interfaces', related_name='interfaces',
blank=True, blank=True,
verbose_name='Wireless LANs' verbose_name=_('Wireless LANs')
) )
untagged_vlan = models.ForeignKey( untagged_vlan = models.ForeignKey(
to='ipam.VLAN', to='ipam.VLAN',
@ -652,13 +670,13 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
related_name='interfaces_as_untagged', related_name='interfaces_as_untagged',
null=True, null=True,
blank=True, blank=True,
verbose_name='Untagged VLAN' verbose_name=_('Untagged VLAN')
) )
tagged_vlans = models.ManyToManyField( tagged_vlans = models.ManyToManyField(
to='ipam.VLAN', to='ipam.VLAN',
related_name='interfaces_as_tagged', related_name='interfaces_as_tagged',
blank=True, blank=True,
verbose_name='Tagged VLANs' verbose_name=_('Tagged VLANs')
) )
vrf = models.ForeignKey( vrf = models.ForeignKey(
to='ipam.VRF', to='ipam.VRF',
@ -666,7 +684,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
related_name='interfaces', related_name='interfaces',
null=True, null=True,
blank=True, blank=True,
verbose_name='VRF' verbose_name=_('VRF')
) )
ip_addresses = GenericRelation( ip_addresses = GenericRelation(
to='ipam.IPAddress', to='ipam.IPAddress',
@ -704,77 +722,84 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
# Virtual Interfaces cannot have a Cable attached # Virtual Interfaces cannot have a Cable attached
if self.is_virtual and self.cable: if self.is_virtual and self.cable:
raise ValidationError({ raise ValidationError({
'type': f"{self.get_type_display()} interfaces cannot have a cable attached." 'type': _("{display_type} interfaces cannot have a cable attached.").format(display_type=self.get_type_display())
}) })
# Virtual Interfaces cannot be marked as connected # Virtual Interfaces cannot be marked as connected
if self.is_virtual and self.mark_connected: if self.is_virtual and self.mark_connected:
raise ValidationError({ raise ValidationError({
'mark_connected': f"{self.get_type_display()} interfaces cannot be marked as connected." 'mark_connected': _("{display_type} interfaces cannot be marked as connected.".format(display_type=self.get_type_display()))
}) })
# Parent validation # Parent validation
# An interface cannot be its own parent # An interface cannot be its own parent
if self.pk and self.parent_id == self.pk: if self.pk and self.parent_id == self.pk:
raise ValidationError({'parent': "An interface cannot be its own parent."}) raise ValidationError({'parent': _("An interface cannot be its own parent.")})
# A physical interface cannot have a parent interface # A physical interface cannot have a parent interface
if self.type != InterfaceTypeChoices.TYPE_VIRTUAL and self.parent is not None: if self.type != InterfaceTypeChoices.TYPE_VIRTUAL and self.parent is not None:
raise ValidationError({'parent': "Only virtual interfaces may be assigned to a parent interface."}) raise ValidationError({'parent': _("Only virtual interfaces may be assigned to a parent interface.")})
# An interface's parent must belong to the same device or virtual chassis # An interface's parent must belong to the same device or virtual chassis
if self.parent and self.parent.device != self.device: if self.parent and self.parent.device != self.device:
if self.device.virtual_chassis is None: if self.device.virtual_chassis is None:
raise ValidationError({ raise ValidationError({
'parent': f"The selected parent interface ({self.parent}) belongs to a different device " 'parent': _("The selected parent interface ({selected_parent}) belongs to a different device ({parent_device})").format(
f"({self.parent.device})." selected_parent=self.parent, parent_device=self.parent.device)
}) })
elif self.parent.device.virtual_chassis != self.parent.virtual_chassis: elif self.parent.device.virtual_chassis != self.parent.virtual_chassis:
raise ValidationError({ raise ValidationError({
'parent': f"The selected parent interface ({self.parent}) belongs to {self.parent.device}, which " 'parent': _("""
f"is not part of virtual chassis {self.device.virtual_chassis}." The selected parent interface ({parent}) belongs to {parent_device}, which
is not part of virtual chassis {virtual_chassis}.
""").format(parent=self.parent, parent_device=self.parent_device, virtual_chassis=self.device.virtual_chassis)
}) })
# Bridge validation # Bridge validation
# An interface cannot be bridged to itself # An interface cannot be bridged to itself
if self.pk and self.bridge_id == self.pk: if self.pk and self.bridge_id == self.pk:
raise ValidationError({'bridge': "An interface cannot be bridged to itself."}) raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
# A bridged interface belong to the same device or virtual chassis # A bridged interface belong 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:
raise ValidationError({ raise ValidationError({
'bridge': f"The selected bridge interface ({self.bridge}) belongs to a different device " 'bridge': _("""
f"({self.bridge.device})." The selected bridge interface ({bridge}) belongs to a different device
({device}).""").format(bridge=self.bridge, device=self.bridge.device)
}) })
elif self.bridge.device.virtual_chassis != self.device.virtual_chassis: elif self.bridge.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({ raise ValidationError({
'bridge': f"The selected bridge interface ({self.bridge}) belongs to {self.bridge.device}, which " 'bridge': _("""
f"is not part of virtual chassis {self.device.virtual_chassis}." The selected bridge interface ({bridge}) belongs to {device}, which "
is not part of virtual chassis {virtual_chassis}.
""").format(bridge=self.bridge, device=self.bridge.device, virtual_chassis=self.device.virtual_chassis)
}) })
# LAG validation # LAG validation
# A virtual interface cannot have a parent LAG # A virtual interface cannot have a parent LAG
if self.type == InterfaceTypeChoices.TYPE_VIRTUAL and self.lag is not None: if self.type == InterfaceTypeChoices.TYPE_VIRTUAL and self.lag is not None:
raise ValidationError({'lag': "Virtual interfaces cannot have a parent LAG interface."}) raise ValidationError({'lag': _("Virtual interfaces cannot have a parent LAG interface.")})
# A LAG interface cannot be its own parent # A LAG interface cannot be its own parent
if self.pk and self.lag_id == self.pk: if self.pk and self.lag_id == self.pk:
raise ValidationError({'lag': "A LAG interface cannot be its own parent."}) raise ValidationError({'lag': _("A LAG interface cannot be its own parent.")})
# An interface's LAG must belong to the same device or virtual chassis # An interface's LAG must belong to the same device or virtual chassis
if self.lag and self.lag.device != self.device: if self.lag and self.lag.device != self.device:
if self.device.virtual_chassis is None: if self.device.virtual_chassis is None:
raise ValidationError({ raise ValidationError({
'lag': f"The selected LAG interface ({self.lag}) belongs to a different device ({self.lag.device})." 'lag': _("The selected LAG interface ({lag}) belongs to a different device ({device}).").format(lag=self.lag, device=self.lag.device)
}) })
elif self.lag.device.virtual_chassis != self.device.virtual_chassis: elif self.lag.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({ raise ValidationError({
'lag': f"The selected LAG interface ({self.lag}) belongs to {self.lag.device}, which is not part " 'lag': _("""
f"of virtual chassis {self.device.virtual_chassis}." The selected LAG interface ({lag}) belongs to {device}, which is not part
of virtual chassis {virtual_chassis}.
""".format(lag=self.lag, device=self.lag.device, virtual_chassis=self.device.virtual_chassis))
}) })
# PoE validation # PoE validation
@ -782,52 +807,54 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
# Only physical interfaces may have a PoE mode/type assigned # Only physical interfaces may have a PoE mode/type assigned
if self.poe_mode and self.is_virtual: if self.poe_mode and self.is_virtual:
raise ValidationError({ raise ValidationError({
'poe_mode': "Virtual interfaces cannot have a PoE mode." 'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
}) })
if self.poe_type and self.is_virtual: if self.poe_type and self.is_virtual:
raise ValidationError({ raise ValidationError({
'poe_type': "Virtual interfaces cannot have a PoE type." 'poe_type': _("Virtual interfaces cannot have a PoE type.")
}) })
# An interface with a PoE type set must also specify a mode # An interface with a PoE type set must also specify a mode
if self.poe_type and not self.poe_mode: if self.poe_type and not self.poe_mode:
raise ValidationError({ raise ValidationError({
'poe_type': "Must specify PoE mode when designating a PoE type." '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 # RF role & channel may only be set for wireless interfaces
if self.rf_role and not self.is_wireless: if self.rf_role and not self.is_wireless:
raise ValidationError({'rf_role': "Wireless role may be set only on wireless interfaces."}) raise ValidationError({'rf_role': _("Wireless role may be set only on 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.")})
# Validate channel frequency against interface type and selected channel (if any) # Validate channel frequency against interface type and selected channel (if any)
if self.rf_channel_frequency: if self.rf_channel_frequency:
if not self.is_wireless: if not self.is_wireless:
raise ValidationError({ raise ValidationError({
'rf_channel_frequency': "Channel frequency may be set only on wireless interfaces.", 'rf_channel_frequency': _("Channel frequency may be set only on wireless interfaces."),
}) })
if self.rf_channel and self.rf_channel_frequency != get_channel_attr(self.rf_channel, 'frequency'): if self.rf_channel and self.rf_channel_frequency != get_channel_attr(self.rf_channel, 'frequency'):
raise ValidationError({ raise ValidationError({
'rf_channel_frequency': "Cannot specify custom frequency with channel selected.", 'rf_channel_frequency': _("Cannot specify custom frequency with channel selected."),
}) })
# Validate channel width against interface type and selected channel (if any) # Validate channel width against interface type and selected channel (if any)
if self.rf_channel_width: if self.rf_channel_width:
if not self.is_wireless: if not self.is_wireless:
raise ValidationError({'rf_channel_width': "Channel width may be set only on wireless interfaces."}) raise ValidationError({'rf_channel_width': _("Channel width may be set only on wireless interfaces.")})
if self.rf_channel and self.rf_channel_width != get_channel_attr(self.rf_channel, 'width'): if self.rf_channel and self.rf_channel_width != get_channel_attr(self.rf_channel, 'width'):
raise ValidationError({'rf_channel_width': "Cannot specify custom width with channel selected."}) raise ValidationError({'rf_channel_width': _("Cannot specify custom width with channel selected.")})
# VLAN validation # VLAN validation
# Validate untagged VLAN # Validate untagged VLAN
if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]: if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]:
raise ValidationError({ raise ValidationError({
'untagged_vlan': f"The untagged VLAN ({self.untagged_vlan}) must belong to the same site as the " 'untagged_vlan': _("""
f"interface's parent device, or it must be global." The untagged VLAN ({untagged_vlan}) must belong to the same site as the
interface's parent device, or it must be global.
""").format(untagged_vlan=self.untagged_vlan)
}) })
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -894,10 +921,12 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
A pass-through port on the front of a Device. A pass-through port on the front of a Device.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
) )
color = ColorField( color = ColorField(
verbose_name=_('color'),
blank=True blank=True
) )
rear_port = models.ForeignKey( rear_port = models.ForeignKey(
@ -906,6 +935,7 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
related_name='frontports' related_name='frontports'
) )
rear_port_position = models.PositiveSmallIntegerField( rear_port_position = models.PositiveSmallIntegerField(
verbose_name=_('rear_port_position'),
default=1, default=1,
validators=[ validators=[
MinValueValidator(REARPORT_POSITIONS_MIN), MinValueValidator(REARPORT_POSITIONS_MIN),
@ -939,14 +969,16 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
# Validate rear port assignment # Validate rear port assignment
if self.rear_port.device != self.device: if self.rear_port.device != self.device:
raise ValidationError({ raise ValidationError({
"rear_port": f"Rear port ({self.rear_port}) must belong to the same device" "rear_port": _("Rear port ({rear_port}) must belong to the same device").format(rear_port=self.rear_port)
}) })
# Validate rear port position assignment # Validate rear port position assignment
if self.rear_port_position > self.rear_port.positions: if self.rear_port_position > self.rear_port.positions:
raise ValidationError({ raise ValidationError({
"rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port " "rear_port_position": _("""
f"{self.rear_port.name} has only {self.rear_port.positions} positions" 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)
}) })
@ -955,13 +987,16 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
A pass-through port on the rear of a Device. A pass-through port on the rear of a Device.
""" """
type = models.CharField( type = models.CharField(
verbose_name=_('type'),
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
) )
color = ColorField( color = ColorField(
verbose_name=_('color'),
blank=True blank=True
) )
positions = models.PositiveSmallIntegerField( positions = models.PositiveSmallIntegerField(
verbose_name=_('positions'),
default=1, default=1,
validators=[ validators=[
MinValueValidator(REARPORT_POSITIONS_MIN), MinValueValidator(REARPORT_POSITIONS_MIN),
@ -982,8 +1017,9 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
frontport_count = self.frontports.count() frontport_count = self.frontports.count()
if self.positions < frontport_count: if self.positions < frontport_count:
raise ValidationError({ raise ValidationError({
"positions": f"The number of positions cannot be less than the number of mapped front ports " "positions": _("""
f"({frontport_count})" The number of positions cannot be less than the number of mapped front ports
({frontport_count})""").format(frontport_count=frontport_count)
}) })
@ -996,6 +1032,7 @@ class ModuleBay(ComponentModel, TrackingModelMixin):
An empty space within a Device which can house a child device An empty space within a Device which can house a child device
""" """
position = models.CharField( position = models.CharField(
verbose_name=_('position'),
max_length=30, max_length=30,
blank=True, blank=True,
help_text=_('Identifier to reference when renaming installed components') help_text=_('Identifier to reference when renaming installed components')
@ -1014,7 +1051,7 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
installed_device = models.OneToOneField( installed_device = models.OneToOneField(
to='dcim.Device', to='dcim.Device',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name='parent_bay', related_name=_('parent_bay'),
blank=True, blank=True,
null=True null=True
) )
@ -1029,20 +1066,20 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
# Validate that the parent Device can have DeviceBays # Validate that the parent Device can have DeviceBays
if not self.device.device_type.is_parent_device: if not self.device.device_type.is_parent_device:
raise ValidationError("This type of device ({}) does not support device bays.".format( raise ValidationError(_("This type of device ({}) does not support device bays.").format(
self.device.device_type self.device.device_type
)) ))
# Cannot install a device into itself, obviously # Cannot install a device into itself, obviously
if self.device == self.installed_device: if self.device == self.installed_device:
raise ValidationError("Cannot install a device into itself.") raise ValidationError(_("Cannot install a device into itself."))
# Check that the installed device is not already installed elsewhere # Check that the installed device is not already installed elsewhere
if self.installed_device: if self.installed_device:
current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first() current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first()
if current_bay and current_bay != self: if current_bay and current_bay != self:
raise ValidationError({ raise ValidationError({
'installed_device': "Cannot install the specified device; device is already installed in {}".format( 'installed_device': _("Cannot install the specified device; device is already installed in {}").format(
current_bay current_bay
) )
}) })
@ -1058,6 +1095,7 @@ class InventoryItemRole(OrganizationalModel):
Inventory items may optionally be assigned a functional role. Inventory items may optionally be assigned a functional role.
""" """
color = ColorField( color = ColorField(
verbose_name=_('color'),
default=ColorChoices.COLOR_GREY default=ColorChoices.COLOR_GREY
) )
@ -1110,13 +1148,13 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
) )
part_id = models.CharField( part_id = models.CharField(
max_length=50, max_length=50,
verbose_name='Part ID', verbose_name=_('Part ID'),
blank=True, blank=True,
help_text=_('Manufacturer-assigned part identifier') help_text=_('Manufacturer-assigned part identifier')
) )
serial = models.CharField( serial = models.CharField(
max_length=50, max_length=50,
verbose_name='Serial number', verbose_name=_('Serial number'),
blank=True blank=True
) )
asset_tag = models.CharField( asset_tag = models.CharField(
@ -1124,10 +1162,11 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
unique=True, unique=True,
blank=True, blank=True,
null=True, null=True,
verbose_name='Asset tag', verbose_name=_('Asset tag'),
help_text=_('A unique tag used to identify this item') help_text=_('A unique tag used to identify this item')
) )
discovered = models.BooleanField( discovered = models.BooleanField(
verbose_name=_('discovered'),
default=False, default=False,
help_text=_('This item was automatically discovered') help_text=_('This item was automatically discovered')
) )
@ -1154,7 +1193,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
# An InventoryItem cannot be its own parent # An InventoryItem cannot be its own parent
if self.pk and self.parent_id == self.pk: if self.pk and self.parent_id == self.pk:
raise ValidationError({ raise ValidationError({
"parent": "Cannot assign self as parent." "parent": _("Cannot assign self as parent.")
}) })
# Validation for moving InventoryItems # Validation for moving InventoryItems
@ -1162,13 +1201,13 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
# Cannot move an InventoryItem to another device if it has a parent # Cannot move an InventoryItem to another device if it has a parent
if self.parent and self.parent.device != self.device: if self.parent and self.parent.device != self.device:
raise ValidationError({ raise ValidationError({
"parent": "Parent inventory item does not belong to the same device." "parent": _("Parent inventory item does not belong to the same device.")
}) })
# Prevent moving InventoryItems with children # Prevent moving InventoryItems with children
first_child = self.get_children().first() first_child = self.get_children().first()
if first_child and first_child.device != self.device: if first_child and first_child.device != self.device:
raise ValidationError("Cannot move an inventory item with dependent children") raise ValidationError(_("Cannot move an inventory item with dependent children"))
# When moving an InventoryItem to another device, remove any associated component # When moving an InventoryItem to another device, remove any associated component
if self.component and self.component.device != self.device: if self.component and self.component.device != self.device:
@ -1176,5 +1215,5 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
else: else:
if self.component and self.component.device != self.device: if self.component and self.component.device != self.device:
raise ValidationError({ raise ValidationError({
"device": "Cannot assign inventory item to component on another device" "device": _("Cannot assign inventory item to component on another device")
}) })