From 4416657681c36669d526e30da6d3487876baebbd Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 14 Feb 2024 08:54:49 -0800 Subject: [PATCH] 15049 add missing gettext to error strings --- netbox/circuits/models/circuits.py | 4 ++-- netbox/core/forms/model_forms.py | 4 ++-- netbox/core/models/data.py | 4 ++-- netbox/dcim/models/cables.py | 20 ++++++++++++-------- netbox/dcim/tests/test_api.py | 3 ++- netbox/extras/api/serializers.py | 11 ++++++----- netbox/extras/dashboard/widgets.py | 4 ++-- netbox/extras/management/commands/reindex.py | 3 ++- netbox/extras/scripts.py | 7 ++++--- netbox/ipam/api/views.py | 3 ++- netbox/ipam/fields.py | 3 ++- netbox/ipam/formfields.py | 15 ++++++++------- netbox/ipam/forms/model_forms.py | 2 +- netbox/ipam/validators.py | 9 +++++---- netbox/netbox/api/fields.py | 9 +++++---- netbox/netbox/plugins/navigation.py | 9 +++++---- netbox/netbox/plugins/templates.py | 3 ++- netbox/netbox/registry.py | 5 +++-- netbox/utilities/fields.py | 2 +- netbox/utilities/forms/bulk_import.py | 2 +- netbox/utilities/testing/views.py | 3 ++- netbox/utilities/utils.py | 19 +++++++++++-------- 22 files changed, 82 insertions(+), 62 deletions(-) diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 4dc775364..7b65d52ad 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -234,9 +234,9 @@ class CircuitTermination( # Must define either site *or* provider network if self.site is None and self.provider_network is None: - raise ValidationError("A circuit termination must attach to either a site or a provider network.") + raise ValidationError(_("A circuit termination must attach to either a site or a provider network.")) if self.site and self.provider_network: - raise ValidationError("A circuit termination cannot attach to both a site and a provider network.") + raise ValidationError(_("A circuit termination cannot attach to both a site and a provider network.")) def to_objectchange(self, action): objectchange = super().to_objectchange(action) diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 652728734..ae891dd59 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -103,9 +103,9 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm): super().clean() if self.cleaned_data.get('upload_file') and self.cleaned_data.get('data_file'): - raise forms.ValidationError("Cannot upload a file and sync from an existing file") + raise forms.ValidationError(_("Cannot upload a file and sync from an existing file")) if not self.cleaned_data.get('upload_file') and not self.cleaned_data.get('data_file'): - raise forms.ValidationError("Must upload a file or select a data file to sync") + raise forms.ValidationError(_("Must upload a file or select a data file to sync")) return self.cleaned_data diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index 6597a4b4d..4ceb22ba9 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -177,7 +177,7 @@ class DataSource(JobsMixin, PrimaryModel): Create/update/delete child DataFiles as necessary to synchronize with the remote source. """ if self.status == DataSourceStatusChoices.SYNCING: - raise SyncError("Cannot initiate sync; syncing already in progress.") + raise SyncError(_("Cannot initiate sync; syncing already in progress.")) # Emit the pre_sync signal pre_sync.send(sender=self.__class__, instance=self) @@ -190,7 +190,7 @@ class DataSource(JobsMixin, PrimaryModel): backend = self.get_backend() except ModuleNotFoundError as e: raise SyncError( - f"There was an error initializing the backend. A dependency needs to be installed: {e}" + _("There was an error initializing the backend. A dependency needs to be installed: ") + str(e) ) with backend.fetch() as local_path: diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index d1c80d0be..562c08139 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -160,24 +160,24 @@ class Cable(PrimaryModel): # Validate length and length_unit if self.length is not None and not self.length_unit: - raise ValidationError("Must specify a unit when setting a cable length") + raise ValidationError(_("Must specify a unit when setting a cable length")) if self.pk is None and (not self.a_terminations or not self.b_terminations): - raise ValidationError("Must define A and B terminations when creating a new cable.") + raise ValidationError(_("Must define A and B terminations when creating a new cable.")) if self._terminations_modified: # Check that all termination objects for either end are of the same type for terms in (self.a_terminations, self.b_terminations): if len(terms) > 1 and not all(isinstance(t, type(terms[0])) for t in terms[1:]): - raise ValidationError("Cannot connect different termination types to same end of cable.") + raise ValidationError(_("Cannot connect different termination types to same end of cable.")) # Check that termination types are compatible if self.a_terminations and self.b_terminations: a_type = self.a_terminations[0]._meta.model_name b_type = self.b_terminations[0]._meta.model_name if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type): - raise ValidationError(f"Incompatible termination types: {a_type} and {b_type}") + raise ValidationError(_("Incompatible termination types: ") + str(a_type) + _(" and ") + str(b_type)) if a_type == b_type: # can't directly use self.a_terminations here as possible they @@ -323,17 +323,21 @@ class CableTermination(ChangeLoggedModel): ).first() if existing_termination is not None: raise ValidationError( - f"Duplicate termination found for {self.termination_type.app_label}.{self.termination_type.model} " - f"{self.termination_id}: cable {existing_termination.cable.pk}" + _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format( + app_label=self.termination_type.app_label, + model=self.termination_type.model, + termination_id=self.termination_id, + cable_pk=existing_termination.cable.pk)) ) # Validate interface type (if applicable) if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES: - raise ValidationError(f"Cables cannot be terminated to {self.termination.get_type_display()} interfaces") + raise ValidationError(_("Cables cannot be terminated to {type_display} interfaces").format( + type_display=self.termination.get_type_display())) # A CircuitTermination attached to a ProviderNetwork cannot have a Cable if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None: - raise ValidationError("Circuit terminations attached to a provider network may not be cabled.") + raise ValidationError(_("Circuit terminations attached to a provider network may not be cabled.")) def save(self, *args, **kwargs): diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index f36b11033..d02422c6f 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1,6 +1,7 @@ from django.contrib.auth import get_user_model from django.test import override_settings from django.urls import reverse +from django.utils.translation import gettext as _ from rest_framework import status from dcim.choices import * @@ -45,7 +46,7 @@ class Mixins: name='Peer Device' ) if self.peer_termination_type is None: - raise NotImplementedError("Test case must set peer_termination_type") + raise NotImplementedError(_("Test case must set peer_termination_type")) peer_obj = self.peer_termination_type.objects.create( device=peer_device, name='Peer Termination' diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 714c92548..8f00e11d9 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -1,5 +1,6 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import gettext as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework import serializers @@ -150,7 +151,7 @@ class CustomFieldSerializer(ValidatedModelSerializer): def validate_type(self, value): if self.instance and self.instance.type != value: - raise serializers.ValidationError('Changing the type of custom fields is not supported.') + raise serializers.ValidationError(_('Changing the type of custom fields is not supported.')) return value @@ -545,12 +546,12 @@ class ReportInputSerializer(serializers.Serializer): def validate_schedule_at(self, value): if value and not self.context['report'].scheduling_enabled: - raise serializers.ValidationError("Scheduling is not enabled for this report.") + raise serializers.ValidationError(_("Scheduling is not enabled for this report.")) return value def validate_interval(self, value): if value and not self.context['report'].scheduling_enabled: - raise serializers.ValidationError("Scheduling is not enabled for this report.") + raise serializers.ValidationError(_("Scheduling is not enabled for this report.")) return value @@ -595,12 +596,12 @@ class ScriptInputSerializer(serializers.Serializer): def validate_schedule_at(self, value): if value and not self.context['script'].scheduling_enabled: - raise serializers.ValidationError("Scheduling is not enabled for this script.") + raise serializers.ValidationError(_("Scheduling is not enabled for this script.")) return value def validate_interval(self, value): if value and not self.context['script'].scheduling_enabled: - raise serializers.ValidationError("Scheduling is not enabled for this script.") + raise serializers.ValidationError(_("Scheduling is not enabled for this script.")) return value diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 8cfbb4c61..6f274ae87 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -178,7 +178,7 @@ class ObjectCountsWidget(DashboardWidget): try: dict(data) except TypeError: - raise forms.ValidationError("Invalid format. Object filters must be passed as a dictionary.") + raise forms.ValidationError(_("Invalid format. Object filters must be passed as a dictionary.")) return data def render(self, request): @@ -232,7 +232,7 @@ class ObjectListWidget(DashboardWidget): try: urlencode(data) except (TypeError, ValueError): - raise forms.ValidationError("Invalid format. URL parameters must be passed as a dictionary.") + raise forms.ValidationError(_("Invalid format. URL parameters must be passed as a dictionary.")) return data def render(self, request): diff --git a/netbox/extras/management/commands/reindex.py b/netbox/extras/management/commands/reindex.py index e072c220a..e20fad0ce 100644 --- a/netbox/extras/management/commands/reindex.py +++ b/netbox/extras/management/commands/reindex.py @@ -1,5 +1,6 @@ from django.contrib.contenttypes.models import ContentType from django.core.management.base import BaseCommand, CommandError +from django.utils.translation import gettext as _ from netbox.registry import registry from netbox.search.backends import search_backend @@ -62,7 +63,7 @@ class Command(BaseCommand): # Determine which models to reindex indexers = self._get_indexers(*model_labels) if not indexers: - raise CommandError("No indexers found!") + raise CommandError(_("No indexers found!")) self.stdout.write(f'Reindexing {len(indexers)} models.') # Clear all cached values for the specified models (if not being lazy) diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index f28465547..7d86472c9 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -11,6 +11,7 @@ from django.conf import settings from django.core.validators import RegexValidator from django.db import transaction from django.utils.functional import classproperty +from django.utils.translation import gettext as _ from core.choices import JobStatusChoices from core.models import Job @@ -356,7 +357,7 @@ class BaseScript: return ordered_vars def run(self, data, commit): - raise NotImplementedError("The script must define a run() method.") + raise NotImplementedError(_("The script must define a run() method.")) # Form rendering @@ -367,11 +368,11 @@ class BaseScript: fieldsets.extend(self.fieldsets) else: fields = list(name for name, _ in self._get_vars().items()) - fieldsets.append(('Script Data', fields)) + fieldsets.append((_('Script Data'), fields)) # Append the default fieldset if defined in the Meta class exec_parameters = ('_schedule_at', '_interval', '_commit') if self.scheduling_enabled else ('_commit',) - fieldsets.append(('Script Execution Parameters', exec_parameters)) + fieldsets.append((_('Script Execution Parameters'), exec_parameters)) return fieldsets diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 4439e82b4..62e2b9eca 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -3,6 +3,7 @@ from copy import deepcopy from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction from django.shortcuts import get_object_or_404 +from django.utils.translation import gettext as _ from django_pglocks import advisory_lock from drf_spectacular.utils import extend_schema from netaddr import IPSet @@ -379,7 +380,7 @@ class AvailablePrefixesView(AvailableObjectsView): 'vrf': parent.vrf.pk if parent.vrf else None, }) else: - raise ValidationError("Insufficient space is available to accommodate the requested prefix size(s)") + raise ValidationError(_("Insufficient space is available to accommodate the requested prefix size(s)")) return requested_objects diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index 2d55deae4..0849a3c79 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -1,6 +1,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models +from django.utils.translation import gettext as _ from netaddr import AddrFormatError, IPNetwork from . import lookups, validators @@ -32,7 +33,7 @@ class BaseIPField(models.Field): # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.) return IPNetwork(value) except AddrFormatError: - raise ValidationError("Invalid IP address format: {}".format(value)) + raise ValidationError(_("Invalid IP address format: {}").format(value)) except (TypeError, ValueError) as e: raise ValidationError(e) diff --git a/netbox/ipam/formfields.py b/netbox/ipam/formfields.py index e8d171d7f..f8a4ff514 100644 --- a/netbox/ipam/formfields.py +++ b/netbox/ipam/formfields.py @@ -1,6 +1,7 @@ from django import forms from django.core.exceptions import ValidationError from django.core.validators import validate_ipv4_address, validate_ipv6_address +from django.utils.translation import gettext_lazy as _ from netaddr import IPAddress, IPNetwork, AddrFormatError @@ -10,7 +11,7 @@ from netaddr import IPAddress, IPNetwork, AddrFormatError class IPAddressFormField(forms.Field): default_error_messages = { - 'invalid': "Enter a valid IPv4 or IPv6 address (without a mask).", + 'invalid': _("Enter a valid IPv4 or IPv6 address (without a mask)."), } def to_python(self, value): @@ -28,19 +29,19 @@ class IPAddressFormField(forms.Field): try: validate_ipv6_address(value) except ValidationError: - raise ValidationError("Invalid IPv4/IPv6 address format: {}".format(value)) + raise ValidationError(_("Invalid IPv4/IPv6 address format: {}").format(value)) try: return IPAddress(value) except ValueError: - raise ValidationError('This field requires an IP address without a mask.') + raise ValidationError(_('This field requires an IP address without a mask.')) except AddrFormatError: - raise ValidationError("Please specify a valid IPv4 or IPv6 address.") + raise ValidationError(_("Please specify a valid IPv4 or IPv6 address.")) class IPNetworkFormField(forms.Field): default_error_messages = { - 'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).", + 'invalid': _("Enter a valid IPv4 or IPv6 address (with CIDR mask)."), } def to_python(self, value): @@ -52,9 +53,9 @@ class IPNetworkFormField(forms.Field): # Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128. if len(value.split('/')) != 2: - raise ValidationError('CIDR mask (e.g. /24) is required.') + raise ValidationError(_('CIDR mask (e.g. /24) is required.')) try: return IPNetwork(value) except AddrFormatError: - raise ValidationError("Please specify a valid IPv4 or IPv6 address.") + raise ValidationError(_("Please specify a valid IPv4 or IPv6 address.")) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 34b7c5958..c7e3f92a3 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -751,4 +751,4 @@ class ServiceCreateForm(ServiceForm): if not self.cleaned_data['description']: self.cleaned_data['description'] = service_template.description elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')): - raise forms.ValidationError("Must specify name, protocol, and port(s) if not using a service template.") + raise forms.ValidationError(_("Must specify name, protocol, and port(s) if not using a service template.")) diff --git a/netbox/ipam/validators.py b/netbox/ipam/validators.py index 50faea8b8..07ff245f2 100644 --- a/netbox/ipam/validators.py +++ b/netbox/ipam/validators.py @@ -1,14 +1,15 @@ from django.core.exceptions import ValidationError from django.core.validators import BaseValidator, RegexValidator +from django.utils.translation import gettext_lazy as _ def prefix_validator(prefix): if prefix.ip != prefix.cidr.ip: - raise ValidationError("{} is not a valid prefix. Did you mean {}?".format(prefix, prefix.cidr)) + raise ValidationError(_("{} is not a valid prefix. Did you mean {}?").format(prefix, prefix.cidr)) class MaxPrefixLengthValidator(BaseValidator): - message = 'The prefix length must be less than or equal to %(limit_value)s.' + message = _('The prefix length must be less than or equal to %(limit_value)s.') code = 'max_prefix_length' def compare(self, a, b): @@ -16,7 +17,7 @@ class MaxPrefixLengthValidator(BaseValidator): class MinPrefixLengthValidator(BaseValidator): - message = 'The prefix length must be greater than or equal to %(limit_value)s.' + message = _('The prefix length must be greater than or equal to %(limit_value)s.') code = 'min_prefix_length' def compare(self, a, b): @@ -25,6 +26,6 @@ class MinPrefixLengthValidator(BaseValidator): DNSValidator = RegexValidator( regex=r'^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$', - message='Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names', + message=_('Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names'), code='invalid' ) diff --git a/netbox/netbox/api/fields.py b/netbox/netbox/api/fields.py index d6e43ea75..347b81c86 100644 --- a/netbox/netbox/api/fields.py +++ b/netbox/netbox/api/fields.py @@ -1,4 +1,5 @@ from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import gettext as _ from drf_spectacular.utils import extend_schema_field from drf_spectacular.types import OpenApiTypes from netaddr import IPNetwork @@ -58,11 +59,11 @@ class ChoiceField(serializers.Field): if data == '': if self.allow_blank: return data - raise ValidationError("This field may not be blank.") + raise ValidationError(_("This field may not be blank.")) # Provide an explicit error message if the request is trying to write a dict or list if isinstance(data, (dict, list)): - raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.') + raise ValidationError(_('Value must be passed directly (e.g. "foo": 123); do not use a dictionary or list.')) # Check for string representations of boolean/integer values if hasattr(data, 'lower'): @@ -95,8 +96,8 @@ class ContentTypeField(RelatedField): Represent a ContentType as '.' """ default_error_messages = { - "does_not_exist": "Invalid content type: {content_type}", - "invalid": "Invalid value. Specify a content type as '.'.", + "does_not_exist": _("Invalid content type: {content_type}"), + "invalid": _("Invalid value. Specify a content type as '.'."), } def to_internal_value(self, data): diff --git a/netbox/netbox/plugins/navigation.py b/netbox/netbox/plugins/navigation.py index 2075c97b6..aae569412 100644 --- a/netbox/netbox/plugins/navigation.py +++ b/netbox/netbox/plugins/navigation.py @@ -1,6 +1,7 @@ from netbox.navigation import MenuGroup from utilities.choices import ButtonColorChoices from django.utils.text import slugify +from django.utils.translation import gettext as _ __all__ = ( 'PluginMenu', @@ -42,11 +43,11 @@ class PluginMenuItem: self.staff_only = staff_only if permissions is not None: if type(permissions) not in (list, tuple): - raise TypeError("Permissions must be passed as a tuple or list.") + raise TypeError(_("Permissions must be passed as a tuple or list.")) self.permissions = permissions if buttons is not None: if type(buttons) not in (list, tuple): - raise TypeError("Buttons must be passed as a tuple or list.") + raise TypeError(_("Buttons must be passed as a tuple or list.")) self.buttons = buttons @@ -64,9 +65,9 @@ class PluginMenuButton: self.icon_class = icon_class if permissions is not None: if type(permissions) not in (list, tuple): - raise TypeError("Permissions must be passed as a tuple or list.") + raise TypeError(_("Permissions must be passed as a tuple or list.")) self.permissions = permissions if color is not None: if color not in ButtonColorChoices.values(): - raise ValueError("Button color must be a choice within ButtonColorChoices.") + raise ValueError(_("Button color must be a choice within ButtonColorChoices.")) self.color = color diff --git a/netbox/netbox/plugins/templates.py b/netbox/netbox/plugins/templates.py index e9b9a9dca..85229dbaf 100644 --- a/netbox/netbox/plugins/templates.py +++ b/netbox/netbox/plugins/templates.py @@ -1,4 +1,5 @@ from django.template.loader import get_template +from django.utils.translation import gettext as _ __all__ = ( 'PluginTemplateExtension', @@ -31,7 +32,7 @@ class PluginTemplateExtension: if extra_context is None: extra_context = {} elif not isinstance(extra_context, dict): - raise TypeError("extra_context must be a dictionary") + raise TypeError(_("extra_context must be a dictionary")) return get_template(template_name).render({**self.context, **extra_context}) diff --git a/netbox/netbox/registry.py b/netbox/netbox/registry.py index ad8c18dcf..943273756 100644 --- a/netbox/netbox/registry.py +++ b/netbox/netbox/registry.py @@ -1,4 +1,5 @@ import collections +from django.utils.translation import gettext as _ class Registry(dict): @@ -13,10 +14,10 @@ class Registry(dict): raise KeyError(f"Invalid store: {key}") def __setitem__(self, key, value): - raise TypeError("Cannot add stores to registry after initialization") + raise TypeError(_("Cannot add stores to registry after initialization")) def __delitem__(self, key): - raise TypeError("Cannot delete stores from registry") + raise TypeError(_("Cannot delete stores from registry")) # Initialize the global registry diff --git a/netbox/utilities/fields.py b/netbox/utilities/fields.py index 65e01db81..7747e0101 100644 --- a/netbox/utilities/fields.py +++ b/netbox/utilities/fields.py @@ -93,7 +93,7 @@ class RestrictedGenericForeignKey(GenericForeignKey): if type(queryset) is dict: restrict_params = queryset elif queryset is not None: - raise ValueError("Custom queryset can't be used for this lookup.") + raise ValueError(_("Custom queryset can't be used for this lookup.")) # For efficiency, group the instances by content type and then do one # query per model diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py index 57362d3dd..e81ebb6ec 100644 --- a/netbox/utilities/forms/bulk_import.py +++ b/netbox/utilities/forms/bulk_import.py @@ -49,7 +49,7 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form): # Determine whether we're reading from form data or an uploaded file if self.cleaned_data['data'] and import_method != ImportMethodChoices.DIRECT: - raise forms.ValidationError("Form data must be empty when uploading/selecting a file.") + raise forms.ValidationError(_("Form data must be empty when uploading/selecting a file.")) if import_method == ImportMethodChoices.UPLOAD: self.upload_file = 'upload_file' file = self.files.get('upload_file') diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index 0a84c5d1b..daa44b905 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import ForeignKey from django.test import override_settings from django.urls import reverse +from django.utils.translation import gettext as _ from extras.choices import ObjectChangeActionChoices from extras.models import ObjectChange @@ -621,7 +622,7 @@ class ViewTestCases: @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_bulk_update_objects_with_permission(self): if not hasattr(self, 'csv_update_data'): - raise NotImplementedError("The test must define csv_update_data.") + raise NotImplementedError(_("The test must define csv_update_data.")) initial_count = self._get_queryset().count() array, csv_data = self._get_update_csv_data() diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 05597b80c..22c1f5e38 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -15,6 +15,7 @@ from django.utils import timezone from django.utils.datastructures import MultiValueDict from django.utils.html import escape from django.utils.timezone import localtime +from django.utils.translation import gettext as _ from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel @@ -306,13 +307,14 @@ def to_meters(length, unit): """ try: if length < 0: - raise ValueError("Length must be a positive number") + raise ValueError(_("Length must be a positive number")) except TypeError: - raise TypeError(f"Invalid value '{length}' for length (must be a number)") + raise TypeError(_("Invalid value '{length}' for length (must be a number)").format(length=length)) valid_units = CableLengthUnitChoices.values() if unit not in valid_units: - raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") + raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format( + unit=unit, valid_units=', '.join(valid_units)))) if unit == CableLengthUnitChoices.UNIT_KILOMETER: return length * 1000 @@ -326,7 +328,7 @@ def to_meters(length, unit): return length * Decimal(0.3048) if unit == CableLengthUnitChoices.UNIT_INCH: return length * Decimal(0.0254) - raise ValueError(f"Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.") + raise ValueError(_("Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.").format(unit=unit)) def to_grams(weight, unit): @@ -335,13 +337,14 @@ def to_grams(weight, unit): """ try: if weight < 0: - raise ValueError("Weight must be a positive number") + raise ValueError(_("Weight must be a positive number")) except TypeError: - raise TypeError(f"Invalid value '{weight}' for weight (must be a number)") + raise TypeError(_("Invalid value '{weight}' for weight (must be a number)").format(weight=weight)) valid_units = WeightUnitChoices.values() if unit not in valid_units: - raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") + raise ValueError(_("Unknown unit {unit}. Must be one of the following: {valid_units}".format( + unit=unit, valid_units=', '.join(valid_units)))) if unit == WeightUnitChoices.UNIT_KILOGRAM: return weight * 1000 @@ -351,7 +354,7 @@ def to_grams(weight, unit): return weight * Decimal(453.592) if unit == WeightUnitChoices.UNIT_OUNCE: return weight * Decimal(28.3495) - raise ValueError(f"Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.") + raise ValueError(_("Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.").format(unit=unit)) def render_jinja2(template_code, context):