15094 Add missing gettext to error strings for internationalization (#15155)

* 15049 add missing gettext to error strings

* 15049 add missing gettext to error strings

* 15094 review change

* 15094 review change

* Formatting cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson 2024-02-20 06:44:02 -08:00 committed by GitHub
parent 29f029d480
commit af27bf5eff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 275 additions and 133 deletions

View File

@ -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)

View File

@ -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}): {error}").format(name=type(e).__name__, error=e))
yield local_path.name

View File

@ -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

View File

@ -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:

View File

@ -181,7 +181,11 @@ 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

View File

@ -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'

View File

@ -870,7 +870,11 @@ 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 +1079,11 @@ 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,10 +1201,17 @@ 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

View File

@ -160,25 +160,26 @@ 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: {type_a} and {type_b}").format(type_a=a_type, type_b=b_type)
)
if a_type == b_type:
# can't directly use self.a_terminations here as possible they
# don't have pk yet
@ -323,17 +324,24 @@ 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):

View File

@ -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'

View File

@ -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:

View File

@ -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

View File

@ -1,5 +1,6 @@
import functools
import re
from django.utils.translation import gettext as _
__all__ = (
'Condition',
@ -50,11 +51,13 @@ 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 +134,17 @@ 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

View File

@ -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):

View File

@ -112,7 +112,9 @@ 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):
@ -178,7 +180,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 +234,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):

View File

@ -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,9 @@ 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 +178,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: {error}").format(name=name, error=e))

View File

@ -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 {name} not found").format(name=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 {name} not found").format(name=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 = {

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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: {address}").format(address=value))
except (TypeError, ValueError) as e:
raise ValidationError(e)

View File

@ -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: {address}").format(address=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."))

View File

@ -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."))

View File

@ -1,14 +1,19 @@
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(
_("{prefix} is not a valid prefix. Did you mean {suggested}?").format(
prefix=prefix, suggested=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 +21,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 +30,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'
)

View File

@ -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'):
@ -82,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(_("{value} is not a valid choice.").format(value=data))
@property
def choices(self):
@ -95,8 +96,8 @@ class ContentTypeField(RelatedField):
Represent a ContentType as '<app_label>.<model>'
"""
default_error_messages = {
"does_not_exist": "Invalid content type: {content_type}",
"invalid": "Invalid value. Specify a content type as '<app_label>.<model_name>'.",
"does_not_exist": _("Invalid content type: {content_type}"),
"invalid": _("Invalid value. Specify a content type as '<app_label>.<model_name>'."),
}
def to_internal_value(self, data):

View File

@ -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,12 @@ 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 +46,17 @@ 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: {value}"
).format(value=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: {id}").format(id=pk))
# Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers

View File

@ -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,9 @@ 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 {permission} for model {model}").format(
permission=perm, model=model
))
# Compile a QuerySet filter that matches all instances of the specified model
tokens = {

View File

@ -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"""

View File

@ -35,7 +35,9 @@ 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):

View File

@ -275,16 +275,20 @@ 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 '{name}' in custom field data.").format(
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 '{name}': {error}").format(
name=field_name, error=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 +551,9 @@ 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__
))
#

View File

@ -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

View File

@ -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,32 @@ 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(_("{item} must be an instance of netbox.plugins.PluginMenuItem").format(
item=menu_link
))
registry['plugins']['menus'].append(menu)
@ -42,10 +57,14 @@ 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

View File

@ -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})

View File

@ -1,4 +1,5 @@
import collections
from django.utils.translation import gettext as _
class Registry(dict):
@ -10,13 +11,13 @@ 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")
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

View File

@ -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 {id} does not exist").format(i=i, id=object_id))
raise ValidationError('')
# Take a snapshot for change logging

View File

@ -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,9 @@ 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):
"""

View File

@ -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

View File

@ -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')
@ -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):
"""

View File

@ -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 "{value}" is invalid.').format(value=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 "{value}" is invalid.').format(value=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 "{value}" is invalid.').format(value=dash_range))
if ord(begin) >= ord(end):
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
raise forms.ValidationError(_('Range "{value}" is invalid.').format(value=dash_range))
for n in list(range(ord(begin), ord(end) + 1)):
values.append(chr(n))
@ -221,18 +222,24 @@ 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 {count_expected} columns but found {count_found}").format(
count_expected=len(headers), count_found=len(row)
)
)
row = [col.strip() for col in row]
record = dict(zip(headers.keys(), row))
@ -253,14 +260,18 @@ 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 "{header}" not found.').format(header=f))

View File

@ -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,11 @@ 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 +137,11 @@ 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):
"""

View File

@ -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 <app_label>.<action>_<model>"
_("Invalid permission name: {name}. Must be in the format <app_label>.<action>_<model>").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

View File

@ -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

View File

@ -1,3 +1,4 @@
from django.utils.translation import gettext_lazy as _
from netbox.registry import registry
__all__ = (
@ -43,5 +44,7 @@ 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

View File

@ -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()

View File

@ -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,17 @@ 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 +331,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 +340,17 @@ 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 +360,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):

View File

@ -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))

View File

@ -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,9 @@ 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 +71,9 @@ 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 +94,10 @@ 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__)
_(
'{class_name} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views '
'which define a base queryset'
).format(class_name=self.__class__.__name__)
)
if not self.has_permission():

View File

@ -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: {name}").format(name=attr))
return attrs[attr]