1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #13132: Wrap verbose_name and other model text with gettext_lazy() (i18n)

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson
2023-07-31 22:28:07 +07:00
committed by GitHub
parent 80376abedf
commit 83bebc1bd2
36 changed files with 899 additions and 431 deletions

View File

@ -12,7 +12,7 @@ from django.db.models.functions import Lower
from django.db.models.signals import post_save
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
@ -78,9 +78,11 @@ class DeviceType(PrimaryModel, WeightMixin):
related_name='device_types'
)
model = models.CharField(
verbose_name=_('model'),
max_length=100
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100
)
default_platform = models.ForeignKey(
@ -89,9 +91,10 @@ class DeviceType(PrimaryModel, WeightMixin):
related_name='+',
blank=True,
null=True,
verbose_name='Default platform'
verbose_name=_('default platform')
)
part_number = models.CharField(
verbose_name=_('part number'),
max_length=50,
blank=True,
help_text=_('Discrete part number (optional)')
@ -100,22 +103,23 @@ class DeviceType(PrimaryModel, WeightMixin):
max_digits=4,
decimal_places=1,
default=1.0,
verbose_name='Height (U)'
verbose_name=_('height (U)')
)
is_full_depth = models.BooleanField(
default=True,
verbose_name='Is full depth',
verbose_name=_('is full depth'),
help_text=_('Device consumes both front and rear rack faces')
)
subdevice_role = models.CharField(
max_length=50,
choices=SubdeviceRoleChoices,
blank=True,
verbose_name='Parent/child status',
verbose_name=_('parent/child status'),
help_text=_('Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.')
)
airflow = models.CharField(
verbose_name=_('airflow'),
max_length=50,
choices=DeviceAirflowChoices,
blank=True
@ -176,7 +180,8 @@ class DeviceType(PrimaryModel, WeightMixin):
)
clone_fields = (
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
'weight_unit',
)
prerequisite_models = (
'dcim.Manufacturer',
@ -277,7 +282,7 @@ class DeviceType(PrimaryModel, WeightMixin):
# U height must be divisible by 0.5
if decimal.Decimal(self.u_height) % decimal.Decimal(0.5):
raise ValidationError({
'u_height': "U height must be in increments of 0.5 rack units."
'u_height': _("U height must be in increments of 0.5 rack units.")
})
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
@ -293,8 +298,8 @@ class DeviceType(PrimaryModel, WeightMixin):
)
if d.position not in u_available:
raise ValidationError({
'u_height': "Device {} in rack {} does not have sufficient space to accommodate a height of "
"{}U".format(d, d.rack, self.u_height)
'u_height': _("Device {} in rack {} does not have sufficient space to accommodate a height of "
"{}U").format(d, d.rack, self.u_height)
})
# If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position.
@ -306,23 +311,23 @@ class DeviceType(PrimaryModel, WeightMixin):
if racked_instance_count:
url = f"{reverse('dcim:device_list')}?manufactuer_id={self.manufacturer_id}&device_type_id={self.pk}"
raise ValidationError({
'u_height': mark_safe(
f'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already '
f'mounted within racks.'
)
'u_height': mark_safe(_(
'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already '
'mounted within racks.'
).format(url=url, racked_instance_count=racked_instance_count))
})
if (
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
) and self.pk and self.devicebaytemplates.count():
raise ValidationError({
'subdevice_role': "Must delete all device bay templates associated with this device before "
"declassifying it as a parent device."
'subdevice_role': _("Must delete all device bay templates associated with this device before "
"declassifying it as a parent device.")
})
if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
raise ValidationError({
'u_height': "Child device types must be 0U."
'u_height': _("Child device types must be 0U.")
})
def save(self, *args, **kwargs):
@ -367,9 +372,11 @@ class ModuleType(PrimaryModel, WeightMixin):
related_name='module_types'
)
model = models.CharField(
verbose_name=_('model'),
max_length=100
)
part_number = models.CharField(
verbose_name=_('part number'),
max_length=50,
blank=True,
help_text=_('Discrete part number (optional)')
@ -454,11 +461,12 @@ class DeviceRole(OrganizationalModel):
virtual machines as well.
"""
color = ColorField(
verbose_name=_('color'),
default=ColorChoices.COLOR_GREY
)
vm_role = models.BooleanField(
default=True,
verbose_name='VM Role',
verbose_name=_('VM role'),
help_text=_('Virtual machines may be assigned to this role')
)
config_template = models.ForeignKey(
@ -550,6 +558,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
null=True
)
name = models.CharField(
verbose_name=_('name'),
max_length=64,
blank=True,
null=True
@ -563,7 +572,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
serial = models.CharField(
max_length=50,
blank=True,
verbose_name='Serial number',
verbose_name=_('serial number'),
help_text=_("Chassis serial number, assigned by the manufacturer")
)
asset_tag = models.CharField(
@ -571,7 +580,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
blank=True,
null=True,
unique=True,
verbose_name='Asset tag',
verbose_name=_('asset tag'),
help_text=_('A unique tag used to identify this device')
)
site = models.ForeignKey(
@ -599,21 +608,23 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
blank=True,
null=True,
validators=[MinValueValidator(1), MaxValueValidator(RACK_U_HEIGHT_MAX + 0.5)],
verbose_name='Position (U)',
verbose_name=_('position (U)'),
help_text=_('The lowest-numbered unit occupied by the device')
)
face = models.CharField(
max_length=50,
blank=True,
choices=DeviceFaceChoices,
verbose_name='Rack face'
verbose_name=_('rack face')
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=DeviceStatusChoices,
default=DeviceStatusChoices.STATUS_ACTIVE
)
airflow = models.CharField(
verbose_name=_('airflow'),
max_length=50,
choices=DeviceAirflowChoices,
blank=True
@ -624,7 +635,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
related_name='+',
blank=True,
null=True,
verbose_name='Primary IPv4'
verbose_name=_('primary IPv4')
)
primary_ip6 = models.OneToOneField(
to='ipam.IPAddress',
@ -632,7 +643,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
related_name='+',
blank=True,
null=True,
verbose_name='Primary IPv6'
verbose_name=_('primary IPv6')
)
oob_ip = models.OneToOneField(
to='ipam.IPAddress',
@ -640,7 +651,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
related_name='+',
blank=True,
null=True,
verbose_name='Out-of-band IP'
verbose_name=_('out-of-band IP')
)
cluster = models.ForeignKey(
to='virtualization.Cluster',
@ -657,12 +668,14 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
null=True
)
vc_position = models.PositiveSmallIntegerField(
verbose_name=_('VC position'),
blank=True,
null=True,
validators=[MaxValueValidator(255)],
help_text=_('Virtual chassis position')
)
vc_priority = models.PositiveSmallIntegerField(
verbose_name=_('VC priority'),
blank=True,
null=True,
validators=[MaxValueValidator(255)],
@ -676,6 +689,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
null=True
)
latitude = models.DecimalField(
verbose_name=_('latitude'),
max_digits=8,
decimal_places=6,
blank=True,
@ -683,6 +697,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
longitude = models.DecimalField(
verbose_name=_('longitude'),
max_digits=9,
decimal_places=6,
blank=True,
@ -763,7 +778,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
Lower('name'), 'site',
name='%(app_label)s_%(class)s_unique_name_site',
condition=Q(tenant__isnull=True),
violation_error_message="Device name must be unique per site."
violation_error_message=_("Device name must be unique per site.")
),
models.UniqueConstraint(
fields=('rack', 'position', 'face'),
@ -799,42 +814,48 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
# Validate site/location/rack combination
if self.rack and self.site != self.rack.site:
raise ValidationError({
'rack': f"Rack {self.rack} does not belong to site {self.site}.",
'rack': _("Rack {rack} does not belong to site {site}.").format(rack=self.rack, site=self.site),
})
if self.location and self.site != self.location.site:
raise ValidationError({
'location': f"Location {self.location} does not belong to site {self.site}.",
'location': _(
"Location {location} does not belong to site {site}."
).format(location=self.location, site=self.site)
})
if self.rack and self.location and self.rack.location != self.location:
raise ValidationError({
'rack': f"Rack {self.rack} does not belong to location {self.location}.",
'rack': _(
"Rack {rack} does not belong to location {location}."
).format(rack=self.rack, location=self.location)
})
if self.rack is None:
if self.face:
raise ValidationError({
'face': "Cannot select a rack face without assigning a rack.",
'face': _("Cannot select a rack face without assigning a rack."),
})
if self.position:
raise ValidationError({
'position': "Cannot select a rack position without assigning a rack.",
'position': _("Cannot select a rack position without assigning a rack."),
})
# Validate rack position and face
if self.position and self.position % decimal.Decimal(0.5):
raise ValidationError({
'position': "Position must be in increments of 0.5 rack units."
'position': _("Position must be in increments of 0.5 rack units.")
})
if self.position and not self.face:
raise ValidationError({
'face': "Must specify rack face when defining rack position.",
'face': _("Must specify rack face when defining rack position."),
})
# Prevent 0U devices from being assigned to a specific position
if hasattr(self, 'device_type'):
if self.position and self.device_type.u_height == 0:
raise ValidationError({
'position': f"A U0 device type ({self.device_type}) cannot be assigned to a rack position."
'position': _(
"A U0 device type ({device_type}) cannot be assigned to a rack position."
).format(device_type=self.device_type)
})
if self.rack:
@ -843,13 +864,17 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
# Child devices cannot be assigned to a rack face/unit
if self.device_type.is_child_device and self.face:
raise ValidationError({
'face': "Child device types cannot be assigned to a rack face. This is an attribute of the "
"parent device."
'face': _(
"Child device types cannot be assigned to a rack face. This is an attribute of the parent "
"device."
)
})
if self.device_type.is_child_device and self.position:
raise ValidationError({
'position': "Child device types cannot be assigned to a rack position. This is an attribute of "
"the parent device."
'position': _(
"Child device types cannot be assigned to a rack position. This is an attribute of the "
"parent device."
)
})
# Validate rack space
@ -860,8 +885,12 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
)
if self.position and self.position not in available_units:
raise ValidationError({
'position': f"U{self.position} is already occupied or does not have sufficient space to "
f"accommodate this device type: {self.device_type} ({self.device_type.u_height}U)"
'position': _(
"U{position} is already occupied or does not have sufficient space to accommodate this "
"device type: {device_type} ({u_height}U)"
).format(
position=self.position, device_type=self.device_type, u_height=self.device_type.u_height
)
})
except DeviceType.DoesNotExist:
@ -872,7 +901,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
if self.primary_ip4:
if self.primary_ip4.family != 4:
raise ValidationError({
'primary_ip4': f"{self.primary_ip4} is not an IPv4 address."
'primary_ip4': _("{primary_ip4} is not an IPv4 address.").format(primary_ip4=self.primary_ip4)
})
if self.primary_ip4.assigned_object in vc_interfaces:
pass
@ -880,12 +909,14 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
pass
else:
raise ValidationError({
'primary_ip4': f"The specified IP address ({self.primary_ip4}) is not assigned to this device."
'primary_ip4': _(
"The specified IP address ({primary_ip4}) is not assigned to this device."
).format(primary_ip4=self.primary_ip4)
})
if self.primary_ip6:
if self.primary_ip6.family != 6:
raise ValidationError({
'primary_ip6': f"{self.primary_ip6} is not an IPv6 address."
'primary_ip6': _("{primary_ip6} is not an IPv6 address.").format(primary_ip6=self.primary_ip6m)
})
if self.primary_ip6.assigned_object in vc_interfaces:
pass
@ -893,7 +924,9 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
pass
else:
raise ValidationError({
'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
'primary_ip6': _(
"The specified IP address ({primary_ip6}) is not assigned to this device."
).format(primary_ip6=self.primary_ip6)
})
if self.oob_ip:
if self.oob_ip.assigned_object in vc_interfaces:
@ -909,20 +942,25 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
if hasattr(self, 'device_type') and self.platform:
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
raise ValidationError({
'platform': f"The assigned platform is limited to {self.platform.manufacturer} device types, but "
f"this device's type belongs to {self.device_type.manufacturer}."
'platform': _(
"The assigned platform is limited to {platform_manufacturer} device types, but this device's "
"type belongs to {device_type_manufacturer}."
).format(
platform_manufacturer=self.platform.manufacturer,
device_type_manufacturer=self.device_type.manufacturer
)
})
# A Device can only be assigned to a Cluster in the same Site (or no Site)
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
raise ValidationError({
'cluster': "The assigned cluster belongs to a different site ({})".format(self.cluster.site)
'cluster': _("The assigned cluster belongs to a different site ({})").format(self.cluster.site)
})
# Validate virtual chassis assignment
if self.virtual_chassis and self.vc_position is None:
raise ValidationError({
'vc_position': "A device assigned to a virtual chassis must have its position defined."
'vc_position': _("A device assigned to a virtual chassis must have its position defined.")
})
def _instantiate_components(self, queryset, bulk_create=True):
@ -1107,6 +1145,7 @@ class Module(PrimaryModel, ConfigContextModel):
related_name='instances'
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=ModuleStatusChoices,
default=ModuleStatusChoices.STATUS_ACTIVE
@ -1114,14 +1153,14 @@ class Module(PrimaryModel, ConfigContextModel):
serial = models.CharField(
max_length=50,
blank=True,
verbose_name='Serial number'
verbose_name=_('serial number')
)
asset_tag = models.CharField(
max_length=50,
blank=True,
null=True,
unique=True,
verbose_name='Asset tag',
verbose_name=_('asset tag'),
help_text=_('A unique tag used to identify this device')
)
@ -1144,7 +1183,9 @@ class Module(PrimaryModel, ConfigContextModel):
if hasattr(self, "module_bay") and (self.module_bay.device != self.device):
raise ValidationError(
f"Module must be installed within a module bay belonging to the assigned device ({self.device})."
_("Module must be installed within a module bay belonging to the assigned device ({device}).").format(
device=self.device
)
)
def save(self, *args, **kwargs):
@ -1242,9 +1283,11 @@ class VirtualChassis(PrimaryModel):
null=True
)
name = models.CharField(
verbose_name=_('name'),
max_length=64
)
domain = models.CharField(
verbose_name=_('domain'),
max_length=30,
blank=True
)
@ -1272,7 +1315,9 @@ class VirtualChassis(PrimaryModel):
# VirtualChassis.)
if self.pk and self.master and self.master not in self.members.all():
raise ValidationError({
'master': f"The selected master ({self.master}) is not assigned to this virtual chassis."
'master': _("The selected master ({master}) is not assigned to this virtual chassis.").format(
master=self.master
)
})
def delete(self, *args, **kwargs):
@ -1285,10 +1330,10 @@ class VirtualChassis(PrimaryModel):
lag__device=F('device')
)
if interfaces:
raise ProtectedError(
f"Unable to delete virtual chassis {self}. There are member interfaces which form a cross-chassis LAG",
interfaces
)
raise ProtectedError(_(
"Unable to delete virtual chassis {self}. There are member interfaces which form a cross-chassis LAG "
"interfaces."
).format(self=self, interfaces=InterfaceSpeedChoices))
return super().delete(*args, **kwargs)
@ -1302,14 +1347,17 @@ class VirtualDeviceContext(PrimaryModel):
null=True
)
name = models.CharField(
verbose_name=_('name'),
max_length=64
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=VirtualDeviceContextStatusChoices,
)
identifier = models.PositiveSmallIntegerField(
help_text='Numeric identifier unique to the parent device',
verbose_name=_('identifier'),
help_text=_('Numeric identifier unique to the parent device'),
blank=True,
null=True,
)
@ -1319,7 +1367,7 @@ class VirtualDeviceContext(PrimaryModel):
related_name='+',
blank=True,
null=True,
verbose_name='Primary IPv4'
verbose_name=_('primary IPv4')
)
primary_ip6 = models.OneToOneField(
to='ipam.IPAddress',
@ -1327,7 +1375,7 @@ class VirtualDeviceContext(PrimaryModel):
related_name='+',
blank=True,
null=True,
verbose_name='Primary IPv6'
verbose_name=_('primary IPv6')
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
@ -1337,6 +1385,7 @@ class VirtualDeviceContext(PrimaryModel):
null=True
)
comments = models.TextField(
verbose_name=_('comments'),
blank=True
)
@ -1382,7 +1431,9 @@ class VirtualDeviceContext(PrimaryModel):
continue
if primary_ip.family != family:
raise ValidationError({
f'primary_ip{family}': f"{primary_ip} is not an IPv{family} address."
f'primary_ip{family}': _(
"{primary_ip} is not an IPv{family} address."
).format(family=family, primary_ip=primary_ip)
})
device_interfaces = self.device.vc_interfaces(if_master=False)
if primary_ip.assigned_object not in device_interfaces: