diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 9ff0b4d63..36e05774e 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -102,7 +102,7 @@ class GitBackend(DataBackend): try: porcelain.clone(self.url, local_path.name, **clone_args) except BaseException as e: - raise SyncError(f"Fetching remote data failed ({type(e).__name__}): {e}") + raise SyncError(_("Fetching remote data failed ({name}): {e}").format(name=type(e).__name__, e=e)) yield local_path.name diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 7cc62a15a..63ce4ed14 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -181,7 +181,7 @@ class Job(models.Model): """ valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES if status not in valid_statuses: - raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}") + raise ValueError(_("Invalid status for job termination. Choices are: {choices}").format(choices=', '.join(valid_statuses))) # Mark the job as completed self.status = status diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index db1a28d39..535756bac 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -1,6 +1,7 @@ from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models +from django.utils.translation import gettext as _ from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded from .lookups import PathContains @@ -41,7 +42,7 @@ class MACAddressField(models.Field): try: return EUI(value, version=48, dialect=mac_unix_expanded_uppercase) except AddrFormatError: - raise ValidationError(f"Invalid MAC address format: {value}") + raise ValidationError(_("Invalid MAC address format: {value}").format(value=value)) def db_type(self, connection): return 'macaddr' @@ -67,7 +68,7 @@ class WWNField(models.Field): try: return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase) except AddrFormatError: - raise ValidationError(f"Invalid WWN format: {value}") + raise ValidationError(_("Invalid WWN format: {value}").format(value=value)) def db_type(self, connection): return 'macaddr8' diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 732bb87ae..62fdb8767 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -870,7 +870,9 @@ class InterfaceImportForm(NetBoxModelImportForm): def clean_vdcs(self): for vdc in self.cleaned_data['vdcs']: if vdc.device != self.cleaned_data['device']: - raise forms.ValidationError(f"VDC {vdc} is not assigned to device {self.cleaned_data['device']}") + raise forms.ValidationError( + _("VDC {vdc} is not assigned to device {device}").format( + vdc=vdc, device=self.cleaned_data['device'])) return self.cleaned_data['vdcs'] @@ -1075,7 +1077,8 @@ class InventoryItemImportForm(NetBoxModelImportForm): component = model.objects.get(device=device, name=component_name) self.instance.component = component except ObjectDoesNotExist: - raise forms.ValidationError(f"Component not found: {device} - {component_name}") + raise forms.ValidationError(_("Component not found: {device} - {component_name}").format( + device=device, component_name=component_name)) # @@ -1193,9 +1196,13 @@ class CableImportForm(NetBoxModelImportForm): else: termination_object = model.objects.get(device=device, name=name) if termination_object.cable is not None and termination_object.cable != self.instance: - raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected") + raise forms.ValidationError( + _("Side {side_upper}: {device} {termination_object} is already connected").format( + side_upper=side.upper(), device=device, termination_object=termination_object)) except ObjectDoesNotExist: - raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}") + raise forms.ValidationError( + _("{side_upper} side termination not found: {device} {name}").format( + side_upper=side.upper(), device=device, name=name)) setattr(self.instance, f'{side}_terminations', [termination_object]) return termination_object diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index ce3b388f4..6cd3a245e 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.utils.translation import gettext as _ from drf_spectacular.utils import extend_schema_field from drf_spectacular.types import OpenApiTypes from rest_framework.fields import Field @@ -88,7 +89,7 @@ class CustomFieldsDataField(Field): if serializer.is_valid(): data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id'] else: - raise ValidationError(f"Unknown related object(s): {data[cf.name]}") + raise ValidationError(_("Unknown related object(s): {name}").format(name=data[cf.name])) # If updating an existing instance, start with existing custom_field_data if self.parent.instance: diff --git a/netbox/extras/conditions.py b/netbox/extras/conditions.py index db054149e..238f3f8ef 100644 --- a/netbox/extras/conditions.py +++ b/netbox/extras/conditions.py @@ -1,5 +1,6 @@ import functools import re +from django.utils.translation import gettext as _ __all__ = ( 'Condition', @@ -50,11 +51,12 @@ class Condition: def __init__(self, attr, value, op=EQ, negate=False): if op not in self.OPERATORS: - raise ValueError(f"Unknown operator: {op}. Must be one of: {', '.join(self.OPERATORS)}") + raise ValueError(_("Unknown operator: {op}. Must be one of: {operators}").format( + op=op, operators=', '.join(self.OPERATORS))) if type(value) not in self.TYPES: - raise ValueError(f"Unsupported value type: {type(value)}") + raise ValueError(_("Unsupported value type: {value}").format(value=type(value))) if op not in self.TYPES[type(value)]: - raise ValueError(f"Invalid type for {op} operation: {type(value)}") + raise ValueError(_("Invalid type for {op} operation: {value}").format(op=op, value=type(value))) self.attr = attr self.value = value @@ -131,14 +133,16 @@ class ConditionSet: """ def __init__(self, ruleset): if type(ruleset) is not dict: - raise ValueError(f"Ruleset must be a dictionary, not {type(ruleset)}.") + raise ValueError(_("Ruleset must be a dictionary, not {ruleset}.").format(ruleset=type(ruleset))) if len(ruleset) != 1: - raise ValueError(f"Ruleset must have exactly one logical operator (found {len(ruleset)})") + raise ValueError(_("Ruleset must have exactly one logical operator (found {ruleset})").format( + ruleset=len(ruleset))) # Determine the logic type logic = list(ruleset.keys())[0] if type(logic) is not str or logic.lower() not in (AND, OR): - raise ValueError(f"Invalid logic type: {logic} (must be '{AND}' or '{OR}')") + raise ValueError(_("Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')").format( + logic=logic, op_and=AND, op_or=OR)) self.logic = logic.lower() # Compile the set of Conditions diff --git a/netbox/extras/dashboard/utils.py b/netbox/extras/dashboard/utils.py index 812b8db1b..20c508b2a 100644 --- a/netbox/extras/dashboard/utils.py +++ b/netbox/extras/dashboard/utils.py @@ -2,6 +2,7 @@ import uuid from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import gettext as _ from netbox.registry import registry from extras.constants import DEFAULT_DASHBOARD @@ -32,7 +33,7 @@ def get_widget_class(name): try: return registry['widgets'][name] except KeyError: - raise ValueError(f"Unregistered widget class: {name}") + raise ValueError(_("Unregistered widget class: {name}").format(name=name)) def get_dashboard(user): diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 6f274ae87..92e33f0b1 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -112,7 +112,8 @@ class DashboardWidget: Params: request: The current request """ - raise NotImplementedError(f"{self.__class__} must define a render() method.") + raise NotImplementedError(_("{class_name} must define a render() method.").format( + class_name=self.__class__)) @property def name(self): diff --git a/netbox/extras/events.py b/netbox/extras/events.py index c50f4488d..c4e3dc024 100644 --- a/netbox/extras/events.py +++ b/netbox/extras/events.py @@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone from django.utils.module_loading import import_string +from django.utils.translation import gettext as _ from django_rq import get_queue from core.models import Job @@ -129,7 +130,8 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna ) else: - raise ValueError(f"Unknown action type for an event rule: {event_rule.action_type}") + raise ValueError(_("Unknown action type for an event rule: {action_type}").format( + action_type=event_rule.action_type)) def process_event_queue(events): @@ -175,4 +177,4 @@ def flush_events(queue): func = import_string(name) func(queue) except Exception as e: - logger.error(f"Cannot import events pipeline {name} error: {e}") + logger.error(_("Cannot import events pipeline {name} error: {e}").format(name=name, e=e)) diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index f8d3ffb7f..fe70df584 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -202,7 +202,7 @@ class EventRuleImportForm(NetBoxModelImportForm): try: webhook = Webhook.objects.get(name=action_object) except Webhook.DoesNotExist: - raise forms.ValidationError(f"Webhook {action_object} not found") + raise forms.ValidationError(_("Webhook {action_object} not found").format(action_object=action_object)) self.instance.action_object = webhook # Script elif action_type == EventRuleActionChoices.SCRIPT: @@ -211,7 +211,7 @@ class EventRuleImportForm(NetBoxModelImportForm): try: module, script = get_module_and_script(module_name, script_name) except ObjectDoesNotExist: - raise forms.ValidationError(f"Script {action_object} not found") + raise forms.ValidationError(_("Script {action_object} not found").format(action_object=action_object)) self.instance.action_object = module self.instance.action_object_type = ContentType.objects.get_for_model(module, for_concrete_model=False) self.instance.action_parameters = { diff --git a/netbox/netbox/api/fields.py b/netbox/netbox/api/fields.py index 347b81c86..31cbacca1 100644 --- a/netbox/netbox/api/fields.py +++ b/netbox/netbox/api/fields.py @@ -83,7 +83,7 @@ class ChoiceField(serializers.Field): except TypeError: # Input is an unhashable type pass - raise ValidationError(f"{data} is not a valid choice.") + raise ValidationError(_("{data} is not a valid choice.").format(data=data)) @property def choices(self): diff --git a/netbox/netbox/api/serializers/nested.py b/netbox/netbox/api/serializers/nested.py index 95dcd560c..bbc171c03 100644 --- a/netbox/netbox/api/serializers/nested.py +++ b/netbox/netbox/api/serializers/nested.py @@ -1,4 +1,5 @@ from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -30,9 +31,10 @@ class WritableNestedSerializer(BaseModelSerializer): try: return queryset.get(**params) except ObjectDoesNotExist: - raise ValidationError(f"Related object not found using the provided attributes: {params}") + raise ValidationError( + _("Related object not found using the provided attributes: {params}").format(params=params)) except MultipleObjectsReturned: - raise ValidationError(f"Multiple objects match the provided attributes: {params}") + raise ValidationError(_("Multiple objects match the provided attributes: {params}").format(params=params)) except FieldError as e: raise ValidationError(e) @@ -42,15 +44,14 @@ class WritableNestedSerializer(BaseModelSerializer): pk = int(data) except (TypeError, ValueError): raise ValidationError( - f"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an " - f"unrecognized value: {data}" + _("Related objects must be referenced by numeric ID or by dictionary of attributes. Received an unrecognized value: {data}").format(data=data) ) # Look up object by PK try: return self.Meta.model.objects.get(pk=pk) except ObjectDoesNotExist: - raise ValidationError(f"Related object not found using the provided numeric ID: {pk}") + raise ValidationError(_("Related object not found using the provided numeric ID: {pk}").format(pk=pk)) # Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 1b0d50ca5..1e53f6729 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -7,6 +7,7 @@ from django.contrib.auth.backends import ModelBackend, RemoteUserBackend as _Rem from django.contrib.auth.models import Group, AnonymousUser from django.core.exceptions import ImproperlyConfigured from django.db.models import Q +from django.utils.translation import gettext_lazy as _ from users.constants import CONSTRAINT_TOKEN_USER from users.models import ObjectPermission @@ -132,7 +133,7 @@ class ObjectPermissionMixin: # Sanity check: Ensure that the requested permission applies to the specified object model = obj._meta.concrete_model if model._meta.label_lower != '.'.join((app_label, model_name)): - raise ValueError(f"Invalid permission {perm} for model {model}") + raise ValueError(_("Invalid permission {perm} for model {model}").format(perm=perm, model=model)) # Compile a QuerySet filter that matches all instances of the specified model tokens = { diff --git a/netbox/netbox/config/__init__.py b/netbox/netbox/config/__init__.py index c536ceadb..1c16d6769 100644 --- a/netbox/netbox/config/__init__.py +++ b/netbox/netbox/config/__init__.py @@ -4,6 +4,7 @@ import threading from django.conf import settings from django.core.cache import cache from django.db.utils import DatabaseError +from django.utils.translation import gettext_lazy as _ from .parameters import PARAMS @@ -63,7 +64,7 @@ class Config: if item in self.defaults: return self.defaults[item] - raise AttributeError(f"Invalid configuration parameter: {item}") + raise AttributeError(_("Invalid configuration parameter: {item}").format(item=item)) def _populate_from_cache(self): """Populate config data from Redis cache""" diff --git a/netbox/netbox/forms/mixins.py b/netbox/netbox/forms/mixins.py index d76eb56c8..3f262df3a 100644 --- a/netbox/netbox/forms/mixins.py +++ b/netbox/netbox/forms/mixins.py @@ -35,7 +35,8 @@ class CustomFieldsMixin: Return the ContentType of the form's model. """ if not getattr(self, 'model', None): - raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.") + raise NotImplementedError(_("{class_name} must specify a model class.").format( + class_name=self.__class__.__name__)) return ContentType.objects.get_for_model(self.model) def _get_custom_fields(self, content_type): diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index a13b84bed..263cca597 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -275,16 +275,18 @@ class CustomFieldsMixin(models.Model): # Validate all field values for field_name, value in self.custom_field_data.items(): if field_name not in custom_fields: - raise ValidationError(f"Unknown field name '{field_name}' in custom field data.") + raise ValidationError(_("Unknown field name '{field_name}' in custom field data.").format( + field_name=field_name)) try: custom_fields[field_name].validate(value) except ValidationError as e: - raise ValidationError(f"Invalid value for custom field '{field_name}': {e.message}") + raise ValidationError(_("Invalid value for custom field '{field_name}': {message}").format( + field_name=field_name, message=e.message)) # Check for missing required values for cf in custom_fields.values(): if cf.required and cf.name not in self.custom_field_data: - raise ValidationError(f"Missing required custom field '{cf.name}'.") + raise ValidationError(_("Missing required custom field '{name}'.").format(name=cf.name)) class CustomLinksMixin(models.Model): @@ -547,7 +549,8 @@ class SyncedDataMixin(models.Model): Inheriting models must override this method with specific logic to copy data from the assigned DataFile to the local instance. This method should *NOT* call save() on the instance. """ - raise NotImplementedError(f"{self.__class__} must implement a sync_data() method.") + raise NotImplementedError(_("{class_name} must implement a sync_data() method.").format( + class_name=self.__class__)) # diff --git a/netbox/netbox/plugins/registration.py b/netbox/netbox/plugins/registration.py index 3be538441..2592b4bfe 100644 --- a/netbox/netbox/plugins/registration.py +++ b/netbox/netbox/plugins/registration.py @@ -1,5 +1,6 @@ import inspect +from django.utils.translation import gettext_lazy as _ from netbox.registry import registry from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem from .templates import PluginTemplateExtension @@ -20,18 +21,24 @@ def register_template_extensions(class_list): # Validation for template_extension in class_list: if not inspect.isclass(template_extension): - raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!") + raise TypeError( + _("PluginTemplateExtension class {template_extension} was passed as an instance!").format( + template_extension=template_extension)) if not issubclass(template_extension, PluginTemplateExtension): - raise TypeError(f"{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!") + raise TypeError( + _("{template_extension} is not a subclass of netbox.plugins.PluginTemplateExtension!").format( + template_extension=template_extension)) if template_extension.model is None: - raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!") + raise TypeError( + _("PluginTemplateExtension class {template_extension} does not define a valid model!").format( + template_extension=template_extension)) registry['plugins']['template_extensions'][template_extension.model].append(template_extension) def register_menu(menu): if not isinstance(menu, PluginMenu): - raise TypeError(f"{menu} must be an instance of netbox.plugins.PluginMenu") + raise TypeError(_("{menu} must be an instance of netbox.plugins.PluginMenu").format(menu=menu)) registry['plugins']['menus'].append(menu) @@ -42,10 +49,12 @@ def register_menu_items(section_name, class_list): # Validation for menu_link in class_list: if not isinstance(menu_link, PluginMenuItem): - raise TypeError(f"{menu_link} must be an instance of netbox.plugins.PluginMenuItem") + raise TypeError(_("{menu_link} must be an instance of netbox.plugins.PluginMenuItem").format( + menu_link=menu_link)) for button in menu_link.buttons: if not isinstance(button, PluginMenuButton): - raise TypeError(f"{button} must be an instance of netbox.plugins.PluginMenuButton") + raise TypeError(_("{button} must be an instance of netbox.plugins.PluginMenuButton").format( + button=button)) registry['plugins']['menu_items'][section_name] = class_list diff --git a/netbox/netbox/registry.py b/netbox/netbox/registry.py index 943273756..d783647ec 100644 --- a/netbox/netbox/registry.py +++ b/netbox/netbox/registry.py @@ -11,7 +11,7 @@ class Registry(dict): try: return super().__getitem__(key) except KeyError: - raise KeyError(f"Invalid store: {key}") + raise KeyError(_("Invalid store: {key}").format(key=key)) def __setitem__(self, key, value): raise TypeError(_("Cannot add stores to registry after initialization")) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 615db6181..17c77d3e6 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -14,6 +14,7 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from django_tables2.export import TableExport from extras.models import ExportTemplate @@ -390,7 +391,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): try: instance = prefetched_objects[object_id] except KeyError: - form.add_error('data', f"Row {i}: Object with ID {object_id} does not exist") + form.add_error('data', _("Row {i}: Object with ID {object_id} does not exist").format(i=i, object_id=object_id)) raise ValidationError('') # Take a snapshot for change logging diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 90b6e9495..4df85cac4 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -11,6 +11,7 @@ from django.shortcuts import redirect, render from django.urls import reverse from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from extras.signals import clear_events from utilities.error_handlers import handle_protectederror @@ -101,7 +102,8 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): request: The current request parent: The parent object """ - raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()') + raise NotImplementedError(_('{class_name} must implement get_children()').format( + class_name=self.__class__.__name__)) def prep_table_data(self, request, queryset, parent): """ diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py index e81ebb6ec..223766a65 100644 --- a/netbox/utilities/forms/bulk_import.py +++ b/netbox/utilities/forms/bulk_import.py @@ -78,7 +78,7 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form): elif format == ImportFormatChoices.YAML: self.cleaned_data['data'] = self._clean_yaml(data) else: - raise forms.ValidationError(f"Unknown data format: {format}") + raise forms.ValidationError(_("Unknown data format: {format}").format(format=format)) def _detect_format(self, data): """ diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index de8e22727..f3abc673f 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -2,6 +2,7 @@ import re from django import forms from django.forms.models import fields_for_model +from django.utils.translation import gettext as _ from utilities.choices import unpack_grouped_choices from utilities.querysets import RestrictedQuerySet @@ -38,7 +39,7 @@ def parse_numeric_range(string, base=10): try: begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1 except ValueError: - raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range)) values.extend(range(begin, end)) return sorted(set(values)) @@ -61,7 +62,7 @@ def parse_alphanumeric_range(string): begin, end = dash_range, dash_range if begin.isdigit() and end.isdigit(): if int(begin) >= int(end): - raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range)) for n in list(range(int(begin), int(end) + 1)): values.append(n) @@ -73,10 +74,10 @@ def parse_alphanumeric_range(string): else: # Not a valid range (more than a single character) if not len(begin) == len(end) == 1: - raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range)) if ord(begin) >= ord(end): - raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + raise forms.ValidationError(_('Range "{dash_range}" is invalid.').format(dash_range=dash_range)) for n in list(range(ord(begin), ord(end) + 1)): values.append(chr(n)) @@ -221,18 +222,21 @@ def parse_csv(reader): if '.' in header: field, to_field = header.split('.', 1) if field in headers: - raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"') + raise forms.ValidationError(_('Duplicate or conflicting column header for "{field}"').format( + field=field)) headers[field] = to_field else: if header in headers: - raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"') + raise forms.ValidationError(_('Duplicate or conflicting column header for "{header}"').format( + header=header)) headers[header] = None # Parse CSV rows into a list of dictionaries mapped from the column headers. for i, row in enumerate(reader, start=1): if len(row) != len(headers): raise forms.ValidationError( - f"Row {i}: Expected {len(headers)} columns but found {len(row)}" + _("Row {i}: Expected {len_headers} columns but found {len_row}").format( + len_headers=len(headers), len_row=len(row)) ) row = [col.strip() for col in row] record = dict(zip(headers.keys(), row)) @@ -253,14 +257,16 @@ def validate_csv(headers, fields, required_fields): is_update = True continue if field not in fields: - raise forms.ValidationError(f'Unexpected column header "{field}" found.') + raise forms.ValidationError(_('Unexpected column header "{field}" found.').format(field=field)) if to_field and not hasattr(fields[field], 'to_field_name'): - raise forms.ValidationError(f'Column "{field}" is not a related object; cannot use dots') + raise forms.ValidationError(_('Column "{field}" is not a related object; cannot use dots').format( + field=field)) if to_field and not hasattr(fields[field].queryset.model, to_field): - raise forms.ValidationError(f'Invalid related object attribute for column "{field}": {to_field}') + raise forms.ValidationError(_('Invalid related object attribute for column "{field}": {to_field}').format( + field=field, to_field=to_field)) # Validate required fields (if not an update) if not is_update: for f in required_fields: if f not in headers: - raise forms.ValidationError(f'Required column header "{f}" not found.') + raise forms.ValidationError(_('Required column header "{f}" not found.').format(f=f)) diff --git a/netbox/utilities/forms/widgets/apiselect.py b/netbox/utilities/forms/widgets/apiselect.py index e4b02cb1d..d49cffdc4 100644 --- a/netbox/utilities/forms/widgets/apiselect.py +++ b/netbox/utilities/forms/widgets/apiselect.py @@ -3,6 +3,7 @@ from typing import Dict, List, Tuple from django import forms from django.conf import settings +from django.utils.translation import gettext_lazy as _ __all__ = ( 'APISelect', @@ -119,7 +120,9 @@ class APISelect(forms.Select): update = [{'fieldName': f, 'queryParam': q} for (f, q) in self.dynamic_params.items()] self._serialize_params(key, update) except IndexError as error: - raise RuntimeError(f"Missing required value for dynamic query param: '{self.dynamic_params}'") from error + raise RuntimeError( + _("Missing required value for dynamic query param: '{dynamic_params}'").format( + dynamic_params=self.dynamic_params)) from error def _add_static_params(self): """ @@ -132,7 +135,9 @@ class APISelect(forms.Select): update = [{'queryParam': k, 'queryValue': v} for (k, v) in self.static_params.items()] self._serialize_params(key, update) except IndexError as error: - raise RuntimeError(f"Missing required value for static query param: '{self.static_params}'") from error + raise RuntimeError( + _("Missing required value for static query param: '{static_params}'").format( + static_params=self.static_params)) from error def add_query_params(self, query_params): """ diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py index 813a8f944..c72a72db7 100644 --- a/netbox/utilities/permissions.py +++ b/netbox/utilities/permissions.py @@ -1,6 +1,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db.models import Q +from django.utils.translation import gettext_lazy as _ __all__ = ( 'get_permission_for_model', @@ -36,7 +37,7 @@ def resolve_permission(name): action, model_name = codename.rsplit('_', 1) except ValueError: raise ValueError( - f"Invalid permission name: {name}. Must be in the format ._" + _("Invalid permission name: {name}. Must be in the format ._").format(name=name) ) return app_label, action, model_name @@ -53,7 +54,7 @@ def resolve_permission_ct(name): try: content_type = ContentType.objects.get(app_label=app_label, model=model_name) except ContentType.DoesNotExist: - raise ValueError(f"Unknown app_label/model_name for {name}") + raise ValueError(_("Unknown app_label/model_name for {name}").format(name=name)) return content_type, action diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index a5ca145e9..3ae01f326 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -1,3 +1,4 @@ +from django.utils.translation import gettext_lazy as _ from netaddr import AddrFormatError, IPAddress from urllib.parse import urlparse @@ -29,7 +30,7 @@ def get_client_ip(request, additional_headers=()): return IPAddress(ip) except AddrFormatError: # We did our best - raise ValueError(f"Invalid IP address set for {header}: {ip}") + raise ValueError(_("Invalid IP address set for {header}: {ip}").format(header=header, ip=ip)) # Could not determine the client IP address from request headers return None diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 654eb02be..84e4251f2 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -1,3 +1,4 @@ +from django.utils.translation import gettext_lazy as _ from netbox.registry import registry __all__ = ( @@ -43,5 +44,6 @@ def register_table_column(column, name, *tables): for table in tables: reg = registry['tables'][table] if name in reg: - raise ValueError(f"A column named {name} is already defined for table {table.__name__}") + raise ValueError(_("A column named {name} is already defined for table {table_name}").format( + name=name, table_name=table.__name__)) reg[name] = column diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py index eaea1c34b..0e896e52a 100644 --- a/netbox/utilities/validators.py +++ b/netbox/utilities/validators.py @@ -2,6 +2,7 @@ import re from django.core.exceptions import ValidationError from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile +from django.utils.translation import gettext_lazy as _ from netbox.config import get_config @@ -61,4 +62,4 @@ def validate_regex(value): try: re.compile(value) except re.error: - raise ValidationError(f"{value} is not a valid regular expression.") + raise ValidationError(_("{value} is not a valid regular expression.").format(value=value)) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 589b71f50..1ed15a744 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -2,6 +2,7 @@ from django.contrib.auth.mixins import AccessMixin from django.core.exceptions import ImproperlyConfigured from django.urls import reverse from django.urls.exceptions import NoReverseMatch +from django.utils.translation import gettext_lazy as _ from netbox.registry import registry from .permissions import resolve_permission @@ -34,7 +35,8 @@ class ContentTypePermissionRequiredMixin(AccessMixin): """ Return the specific permission necessary to perform the requested action on an object. """ - raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()") + raise NotImplementedError(_("{self.__class__.__name__} must implement get_required_permission()").format( + class_name=self.__class__.__name__)) def has_permission(self): user = self.request.user @@ -68,7 +70,8 @@ class ObjectPermissionRequiredMixin(AccessMixin): """ Return the specific permission necessary to perform the requested action on an object. """ - raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()") + raise NotImplementedError(_("{class_name} must implement get_required_permission()").format( + class_name=self.__class__.__name__)) def has_permission(self): user = self.request.user @@ -89,8 +92,8 @@ class ObjectPermissionRequiredMixin(AccessMixin): if not hasattr(self, 'queryset'): raise ImproperlyConfigured( - '{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define ' - 'a base queryset'.format(self.__class__.__name__) + _('{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define a base queryset').format( + self.__class__.__name__) ) if not self.has_permission(): diff --git a/netbox/wireless/utils.py b/netbox/wireless/utils.py index d98d6a853..c68489bd3 100644 --- a/netbox/wireless/utils.py +++ b/netbox/wireless/utils.py @@ -1,4 +1,5 @@ from decimal import Decimal +from django.utils.translation import gettext_lazy as _ from .choices import WirelessChannelChoices @@ -12,7 +13,7 @@ def get_channel_attr(channel, attr): Return the specified attribute of a given WirelessChannelChoices value. """ if channel not in WirelessChannelChoices.values(): - raise ValueError(f"Invalid channel value: {channel}") + raise ValueError(_("Invalid channel value: {channel}").format(channel=channel)) channel_values = channel.split('-') attrs = { @@ -22,6 +23,6 @@ def get_channel_attr(channel, attr): 'width': Decimal(channel_values[3]), } if attr not in attrs: - raise ValueError(f"Invalid channel attribute: {attr}") + raise ValueError(_("Invalid channel attribute: {attr}").format(attr=attr)) return attrs[attr]