diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 62f26776f..543528207 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -7,7 +7,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Sum 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 dcim.choices import * @@ -52,6 +52,7 @@ class ComponentModel(NetBoxModel): related_name='%(class)ss' ) name = models.CharField( + verbose_name=_('name'), max_length=64 ) _name = NaturalOrderingField( @@ -60,11 +61,13 @@ class ComponentModel(NetBoxModel): blank=True ) label = models.CharField( + verbose_name=_('label'), max_length=64, blank=True, - help_text=_("Physical label") + help_text=_('Physical label') ) description = models.CharField( + verbose_name=_('description'), max_length=200, blank=True ) @@ -101,7 +104,7 @@ class ComponentModel(NetBoxModel): # 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): raise ValidationError({ - "device": "Components cannot be moved to a different device." + "device": _("Components cannot be moved to a different device.") }) @property @@ -140,13 +143,15 @@ class CabledObjectModel(models.Model): null=True ) cable_end = models.CharField( + verbose_name=_('cable end'), max_length=1, blank=True, choices=CableEndChoices ) mark_connected = models.BooleanField( + verbose_name=_('mark connected'), default=False, - help_text=_("Treat as if a cable is connected") + help_text=_('Treat as if a cable is connected') ) cable_terminations = GenericRelation( @@ -164,15 +169,15 @@ class CabledObjectModel(models.Model): if self.cable and not self.cable_end: 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: 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: raise ValidationError({ - "mark_connected": "Cannot mark as connected with a cable attached." + "mark_connected": _("Cannot mark as connected with a cable attached.") }) @property @@ -195,7 +200,7 @@ class CabledObjectModel(models.Model): @property 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 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. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=ConsolePortTypeChoices, blank=True, help_text=_('Physical port type') ) speed = models.PositiveIntegerField( + verbose_name=_('speed'), choices=ConsolePortSpeedChoices, blank=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. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=ConsolePortTypeChoices, blank=True, help_text=_('Physical port type') ) speed = models.PositiveIntegerField( + verbose_name=_('speed'), choices=ConsolePortSpeedChoices, blank=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. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=PowerPortTypeChoices, blank=True, help_text=_('Physical port type') ) maximum_draw = models.PositiveIntegerField( + verbose_name=_('maximum draw'), blank=True, null=True, validators=[MinValueValidator(1)], help_text=_("Maximum power draw (watts)") ) allocated_draw = models.PositiveIntegerField( + verbose_name=_('allocated draw'), blank=True, null=True, validators=[MinValueValidator(1)], - help_text=_("Allocated power draw (watts)") + help_text=_('Allocated power draw (watts)') ) 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.allocated_draw > self.maximum_draw: 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): @@ -434,6 +446,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki A physical power outlet (output) within a Device which provides power to a PowerPort. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=PowerOutletTypeChoices, blank=True, @@ -447,10 +460,11 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki related_name='poweroutlets' ) feed_leg = models.CharField( + verbose_name=_('feed leg'), max_length=50, choices=PowerOutletFeedLegChoices, 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') @@ -463,7 +477,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki # Validate power port assignment 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. """ enabled = models.BooleanField( + verbose_name=_('enabled'), default=True ) mac_address = MACAddressField( null=True, blank=True, - verbose_name='MAC Address' + verbose_name=_('MAC Address') ) mtu = models.PositiveIntegerField( blank=True, @@ -489,13 +504,14 @@ class BaseInterface(models.Model): MinValueValidator(INTERFACE_MTU_MIN), MaxValueValidator(INTERFACE_MTU_MAX) ], - verbose_name='MTU' + verbose_name=_('MTU') ) mode = models.CharField( + verbose_name=_('mode'), max_length=50, choices=InterfaceModeChoices, blank=True, - help_text=_("IEEE 802.1Q tagging strategy") + help_text=_('IEEE 802.1Q tagging strategy') ) parent = models.ForeignKey( to='self', @@ -503,7 +519,7 @@ class BaseInterface(models.Model): related_name='child_interfaces', null=True, blank=True, - verbose_name='Parent interface' + verbose_name=_('Parent interface') ) bridge = models.ForeignKey( to='self', @@ -511,7 +527,7 @@ class BaseInterface(models.Model): related_name='bridge_interfaces', null=True, blank=True, - verbose_name='Bridge interface' + verbose_name=_('Bridge interface') ) class Meta: @@ -559,23 +575,25 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd related_name='member_interfaces', null=True, blank=True, - verbose_name='Parent LAG' + verbose_name=_('Parent LAG') ) type = models.CharField( + verbose_name=_('type'), max_length=50, choices=InterfaceTypeChoices ) mgmt_only = models.BooleanField( default=False, - verbose_name='Management only', + verbose_name=_('Management only'), help_text=_('This interface is used only for out-of-band management') ) speed = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Speed (Kbps)' + verbose_name=_('Speed (Kbps)') ) duplex = models.CharField( + verbose_name=_('duplex'), max_length=50, blank=True, null=True, @@ -584,27 +602,27 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd wwn = WWNField( null=True, blank=True, - verbose_name='WWN', + verbose_name=_('WWN'), help_text=_('64-bit World Wide Name') ) rf_role = models.CharField( max_length=30, choices=WirelessRoleChoices, blank=True, - verbose_name='Wireless role' + verbose_name=_('Wireless role') ) rf_channel = models.CharField( max_length=50, choices=WirelessChannelChoices, blank=True, - verbose_name='Wireless channel' + verbose_name=_('Wireless channel') ) rf_channel_frequency = models.DecimalField( max_digits=7, decimal_places=2, blank=True, null=True, - verbose_name='Channel frequency (MHz)', + verbose_name=_('Channel frequency (MHz)'), help_text=_("Populated by selected channel (if set)") ) rf_channel_width = models.DecimalField( @@ -612,26 +630,26 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd decimal_places=3, blank=True, null=True, - verbose_name='Channel width (MHz)', + verbose_name=('Channel width (MHz)'), help_text=_("Populated by selected channel (if set)") ) tx_power = models.PositiveSmallIntegerField( blank=True, null=True, validators=(MaxValueValidator(127),), - verbose_name='Transmit power (dBm)' + verbose_name=_('Transmit power (dBm)') ) poe_mode = models.CharField( max_length=50, choices=InterfacePoEModeChoices, blank=True, - verbose_name='PoE mode' + verbose_name=_('PoE mode') ) poe_type = models.CharField( max_length=50, choices=InterfacePoETypeChoices, blank=True, - verbose_name='PoE type' + verbose_name=_('PoE type') ) wireless_link = models.ForeignKey( to='wireless.WirelessLink', @@ -644,7 +662,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd to='wireless.WirelessLAN', related_name='interfaces', blank=True, - verbose_name='Wireless LANs' + verbose_name=_('Wireless LANs') ) untagged_vlan = models.ForeignKey( to='ipam.VLAN', @@ -652,13 +670,13 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd related_name='interfaces_as_untagged', null=True, blank=True, - verbose_name='Untagged VLAN' + verbose_name=_('Untagged VLAN') ) tagged_vlans = models.ManyToManyField( to='ipam.VLAN', related_name='interfaces_as_tagged', blank=True, - verbose_name='Tagged VLANs' + verbose_name=_('Tagged VLANs') ) vrf = models.ForeignKey( to='ipam.VRF', @@ -666,7 +684,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd related_name='interfaces', null=True, blank=True, - verbose_name='VRF' + verbose_name=_('VRF') ) ip_addresses = GenericRelation( to='ipam.IPAddress', @@ -704,77 +722,84 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd # Virtual Interfaces cannot have a Cable attached if self.is_virtual and self.cable: 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 if self.is_virtual and self.mark_connected: 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 # An interface cannot be its own parent 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 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 if self.parent and self.parent.device != self.device: if self.device.virtual_chassis is None: raise ValidationError({ - 'parent': f"The selected parent interface ({self.parent}) belongs to a different device " - f"({self.parent.device})." + 'parent': _("The selected parent interface ({selected_parent}) belongs to a different device ({parent_device})").format( + selected_parent=self.parent, parent_device=self.parent.device) }) elif self.parent.device.virtual_chassis != self.parent.virtual_chassis: raise ValidationError({ - 'parent': f"The selected parent interface ({self.parent}) belongs to {self.parent.device}, which " - f"is not part of virtual chassis {self.device.virtual_chassis}." + 'parent': _(""" + 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 # 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."}) + raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")}) # A bridged interface belong to the same device or virtual chassis if self.bridge and self.bridge.device != self.device: if self.device.virtual_chassis is None: raise ValidationError({ - 'bridge': f"The selected bridge interface ({self.bridge}) belongs to a different device " - f"({self.bridge.device})." + 'bridge': _(""" + 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: raise ValidationError({ - 'bridge': f"The selected bridge interface ({self.bridge}) belongs to {self.bridge.device}, which " - f"is not part of virtual chassis {self.device.virtual_chassis}." + 'bridge': _(""" + 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 # A virtual interface cannot have a parent LAG 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 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 if self.lag and self.lag.device != self.device: if self.device.virtual_chassis is None: 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: raise ValidationError({ - 'lag': f"The selected LAG interface ({self.lag}) belongs to {self.lag.device}, which is not part " - f"of virtual chassis {self.device.virtual_chassis}." + 'lag': _(""" + 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 @@ -782,52 +807,54 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd # 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." + '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." + '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." + 'poe_type': _("Must specify PoE mode when designating a PoE type.") }) # 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."}) + raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")}) 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) if self.rf_channel_frequency: if not self.is_wireless: 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'): 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) if self.rf_channel_width: 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'): - 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 # Validate untagged VLAN if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]: raise ValidationError({ - 'untagged_vlan': f"The untagged VLAN ({self.untagged_vlan}) must belong to the same site as the " - f"interface's parent device, or it must be global." + 'untagged_vlan': _(""" + 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): @@ -894,10 +921,12 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): A pass-through port on the front of a Device. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=PortTypeChoices ) color = ColorField( + verbose_name=_('color'), blank=True ) rear_port = models.ForeignKey( @@ -906,6 +935,7 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): related_name='frontports' ) rear_port_position = models.PositiveSmallIntegerField( + verbose_name=_('rear_port_position'), default=1, validators=[ MinValueValidator(REARPORT_POSITIONS_MIN), @@ -939,14 +969,16 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): # Validate rear port assignment if self.rear_port.device != self.device: 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 if self.rear_port_position > self.rear_port.positions: raise ValidationError({ - "rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port " - f"{self.rear_port.name} has only {self.rear_port.positions} positions" + "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) }) @@ -955,13 +987,16 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): A pass-through port on the rear of a Device. """ type = models.CharField( + verbose_name=_('type'), max_length=50, choices=PortTypeChoices ) color = ColorField( + verbose_name=_('color'), blank=True ) positions = models.PositiveSmallIntegerField( + verbose_name=_('positions'), default=1, validators=[ MinValueValidator(REARPORT_POSITIONS_MIN), @@ -982,8 +1017,9 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin): frontport_count = self.frontports.count() if self.positions < frontport_count: raise ValidationError({ - "positions": f"The number of positions cannot be less than the number of mapped front ports " - f"({frontport_count})" + "positions": _(""" + 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 """ position = models.CharField( + verbose_name=_('position'), max_length=30, blank=True, help_text=_('Identifier to reference when renaming installed components') @@ -1014,7 +1051,7 @@ class DeviceBay(ComponentModel, TrackingModelMixin): installed_device = models.OneToOneField( to='dcim.Device', on_delete=models.SET_NULL, - related_name='parent_bay', + related_name=_('parent_bay'), blank=True, null=True ) @@ -1029,20 +1066,20 @@ class DeviceBay(ComponentModel, TrackingModelMixin): # Validate that the parent Device can have DeviceBays 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 )) # Cannot install a device into itself, obviously 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 if self.installed_device: current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first() if current_bay and current_bay != self: 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 ) }) @@ -1058,6 +1095,7 @@ class InventoryItemRole(OrganizationalModel): Inventory items may optionally be assigned a functional role. """ color = ColorField( + verbose_name=_('color'), default=ColorChoices.COLOR_GREY ) @@ -1110,13 +1148,13 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin): ) part_id = models.CharField( max_length=50, - verbose_name='Part ID', + verbose_name=_('Part ID'), blank=True, help_text=_('Manufacturer-assigned part identifier') ) serial = models.CharField( max_length=50, - verbose_name='Serial number', + verbose_name=_('Serial number'), blank=True ) asset_tag = models.CharField( @@ -1124,10 +1162,11 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin): unique=True, blank=True, null=True, - verbose_name='Asset tag', + verbose_name=_('Asset tag'), help_text=_('A unique tag used to identify this item') ) discovered = models.BooleanField( + verbose_name=_('discovered'), default=False, help_text=_('This item was automatically discovered') ) @@ -1154,7 +1193,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin): # An InventoryItem cannot be its own parent if self.pk and self.parent_id == self.pk: raise ValidationError({ - "parent": "Cannot assign self as parent." + "parent": _("Cannot assign self as parent.") }) # 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 if self.parent and self.parent.device != self.device: 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 first_child = self.get_children().first() 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 if self.component and self.component.device != self.device: @@ -1176,5 +1215,5 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin): else: if self.component and self.component.device != self.device: raise ValidationError({ - "device": "Cannot assign inventory item to component on another device" + "device": _("Cannot assign inventory item to component on another device") })