mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
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:
parent
29f029d480
commit
af27bf5eff
@ -234,9 +234,9 @@ class CircuitTermination(
|
|||||||
|
|
||||||
# Must define either site *or* provider network
|
# Must define either site *or* provider network
|
||||||
if self.site is None and self.provider_network is None:
|
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:
|
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):
|
def to_objectchange(self, action):
|
||||||
objectchange = super().to_objectchange(action)
|
objectchange = super().to_objectchange(action)
|
||||||
|
@ -102,7 +102,7 @@ class GitBackend(DataBackend):
|
|||||||
try:
|
try:
|
||||||
porcelain.clone(self.url, local_path.name, **clone_args)
|
porcelain.clone(self.url, local_path.name, **clone_args)
|
||||||
except BaseException as e:
|
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
|
yield local_path.name
|
||||||
|
|
||||||
|
@ -103,9 +103,9 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.cleaned_data.get('upload_file') and self.cleaned_data.get('data_file'):
|
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'):
|
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
|
return self.cleaned_data
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
|
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
|
||||||
"""
|
"""
|
||||||
if self.status == DataSourceStatusChoices.SYNCING:
|
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
|
# Emit the pre_sync signal
|
||||||
pre_sync.send(sender=self.__class__, instance=self)
|
pre_sync.send(sender=self.__class__, instance=self)
|
||||||
@ -190,7 +190,7 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
backend = self.get_backend()
|
backend = self.get_backend()
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise SyncError(
|
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:
|
with backend.fetch() as local_path:
|
||||||
|
|
||||||
|
@ -181,7 +181,11 @@ class Job(models.Model):
|
|||||||
"""
|
"""
|
||||||
valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
|
valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
if status not in valid_statuses:
|
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
|
# Mark the job as completed
|
||||||
self.status = status
|
self.status = status
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
|
from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
|
||||||
|
|
||||||
from .lookups import PathContains
|
from .lookups import PathContains
|
||||||
@ -41,7 +42,7 @@ class MACAddressField(models.Field):
|
|||||||
try:
|
try:
|
||||||
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
||||||
except AddrFormatError:
|
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):
|
def db_type(self, connection):
|
||||||
return 'macaddr'
|
return 'macaddr'
|
||||||
@ -67,7 +68,7 @@ class WWNField(models.Field):
|
|||||||
try:
|
try:
|
||||||
return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase)
|
return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase)
|
||||||
except AddrFormatError:
|
except AddrFormatError:
|
||||||
raise ValidationError(f"Invalid WWN format: {value}")
|
raise ValidationError(_("Invalid WWN format: {value}").format(value=value))
|
||||||
|
|
||||||
def db_type(self, connection):
|
def db_type(self, connection):
|
||||||
return 'macaddr8'
|
return 'macaddr8'
|
||||||
|
@ -870,7 +870,11 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
def clean_vdcs(self):
|
def clean_vdcs(self):
|
||||||
for vdc in self.cleaned_data['vdcs']:
|
for vdc in self.cleaned_data['vdcs']:
|
||||||
if vdc.device != self.cleaned_data['device']:
|
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']
|
return self.cleaned_data['vdcs']
|
||||||
|
|
||||||
|
|
||||||
@ -1075,7 +1079,11 @@ class InventoryItemImportForm(NetBoxModelImportForm):
|
|||||||
component = model.objects.get(device=device, name=component_name)
|
component = model.objects.get(device=device, name=component_name)
|
||||||
self.instance.component = component
|
self.instance.component = component
|
||||||
except ObjectDoesNotExist:
|
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:
|
else:
|
||||||
termination_object = model.objects.get(device=device, name=name)
|
termination_object = model.objects.get(device=device, name=name)
|
||||||
if termination_object.cable is not None and termination_object.cable != self.instance:
|
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:
|
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])
|
setattr(self.instance, f'{side}_terminations', [termination_object])
|
||||||
return termination_object
|
return termination_object
|
||||||
|
|
||||||
|
@ -160,25 +160,26 @@ class Cable(PrimaryModel):
|
|||||||
|
|
||||||
# Validate length and length_unit
|
# Validate length and length_unit
|
||||||
if self.length is not None and not self.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):
|
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:
|
if self._terminations_modified:
|
||||||
|
|
||||||
# Check that all termination objects for either end are of the same type
|
# Check that all termination objects for either end are of the same type
|
||||||
for terms in (self.a_terminations, self.b_terminations):
|
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:]):
|
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
|
# Check that termination types are compatible
|
||||||
if self.a_terminations and self.b_terminations:
|
if self.a_terminations and self.b_terminations:
|
||||||
a_type = self.a_terminations[0]._meta.model_name
|
a_type = self.a_terminations[0]._meta.model_name
|
||||||
b_type = self.b_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):
|
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:
|
if a_type == b_type:
|
||||||
# can't directly use self.a_terminations here as possible they
|
# can't directly use self.a_terminations here as possible they
|
||||||
# don't have pk yet
|
# don't have pk yet
|
||||||
@ -323,17 +324,24 @@ class CableTermination(ChangeLoggedModel):
|
|||||||
).first()
|
).first()
|
||||||
if existing_termination is not None:
|
if existing_termination is not None:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Duplicate termination found for {self.termination_type.app_label}.{self.termination_type.model} "
|
_("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format(
|
||||||
f"{self.termination_id}: cable {existing_termination.cable.pk}"
|
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)
|
# Validate interface type (if applicable)
|
||||||
if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
|
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
|
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
||||||
if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
|
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):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
@ -45,7 +46,7 @@ class Mixins:
|
|||||||
name='Peer Device'
|
name='Peer Device'
|
||||||
)
|
)
|
||||||
if self.peer_termination_type is None:
|
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(
|
peer_obj = self.peer_termination_type.objects.create(
|
||||||
device=peer_device,
|
device=peer_device,
|
||||||
name='Peer Termination'
|
name='Peer Termination'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
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.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from rest_framework.fields import Field
|
from rest_framework.fields import Field
|
||||||
@ -88,7 +89,7 @@ class CustomFieldsDataField(Field):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id']
|
data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id']
|
||||||
else:
|
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 updating an existing instance, start with existing custom_field_data
|
||||||
if self.parent.instance:
|
if self.parent.instance:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@ -150,7 +151,7 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
def validate_type(self, value):
|
def validate_type(self, value):
|
||||||
if self.instance and self.instance.type != 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
|
return value
|
||||||
|
|
||||||
@ -545,12 +546,12 @@ class ReportInputSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def validate_schedule_at(self, value):
|
def validate_schedule_at(self, value):
|
||||||
if value and not self.context['report'].scheduling_enabled:
|
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
|
return value
|
||||||
|
|
||||||
def validate_interval(self, value):
|
def validate_interval(self, value):
|
||||||
if value and not self.context['report'].scheduling_enabled:
|
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
|
return value
|
||||||
|
|
||||||
|
|
||||||
@ -595,12 +596,12 @@ class ScriptInputSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def validate_schedule_at(self, value):
|
def validate_schedule_at(self, value):
|
||||||
if value and not self.context['script'].scheduling_enabled:
|
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
|
return value
|
||||||
|
|
||||||
def validate_interval(self, value):
|
def validate_interval(self, value):
|
||||||
if value and not self.context['script'].scheduling_enabled:
|
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
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Condition',
|
'Condition',
|
||||||
@ -50,11 +51,13 @@ class Condition:
|
|||||||
|
|
||||||
def __init__(self, attr, value, op=EQ, negate=False):
|
def __init__(self, attr, value, op=EQ, negate=False):
|
||||||
if op not in self.OPERATORS:
|
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:
|
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)]:
|
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.attr = attr
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -131,14 +134,17 @@ class ConditionSet:
|
|||||||
"""
|
"""
|
||||||
def __init__(self, ruleset):
|
def __init__(self, ruleset):
|
||||||
if type(ruleset) is not dict:
|
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:
|
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
|
# Determine the logic type
|
||||||
logic = list(ruleset.keys())[0]
|
logic = list(ruleset.keys())[0]
|
||||||
if type(logic) is not str or logic.lower() not in (AND, OR):
|
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()
|
self.logic = logic.lower()
|
||||||
|
|
||||||
# Compile the set of Conditions
|
# Compile the set of Conditions
|
||||||
|
@ -2,6 +2,7 @@ import uuid
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from extras.constants import DEFAULT_DASHBOARD
|
from extras.constants import DEFAULT_DASHBOARD
|
||||||
@ -32,7 +33,7 @@ def get_widget_class(name):
|
|||||||
try:
|
try:
|
||||||
return registry['widgets'][name]
|
return registry['widgets'][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(f"Unregistered widget class: {name}")
|
raise ValueError(_("Unregistered widget class: {name}").format(name=name))
|
||||||
|
|
||||||
|
|
||||||
def get_dashboard(user):
|
def get_dashboard(user):
|
||||||
|
@ -112,7 +112,9 @@ class DashboardWidget:
|
|||||||
Params:
|
Params:
|
||||||
request: The current request
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -178,7 +180,7 @@ class ObjectCountsWidget(DashboardWidget):
|
|||||||
try:
|
try:
|
||||||
dict(data)
|
dict(data)
|
||||||
except TypeError:
|
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
|
return data
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
@ -232,7 +234,7 @@ class ObjectListWidget(DashboardWidget):
|
|||||||
try:
|
try:
|
||||||
urlencode(data)
|
urlencode(data)
|
||||||
except (TypeError, ValueError):
|
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
|
return data
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
|
@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django_rq import get_queue
|
from django_rq import get_queue
|
||||||
|
|
||||||
from core.models import Job
|
from core.models import Job
|
||||||
@ -129,7 +130,9 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
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):
|
def process_event_queue(events):
|
||||||
@ -175,4 +178,4 @@ def flush_events(queue):
|
|||||||
func = import_string(name)
|
func = import_string(name)
|
||||||
func(queue)
|
func(queue)
|
||||||
except Exception as e:
|
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))
|
||||||
|
@ -202,7 +202,7 @@ class EventRuleImportForm(NetBoxModelImportForm):
|
|||||||
try:
|
try:
|
||||||
webhook = Webhook.objects.get(name=action_object)
|
webhook = Webhook.objects.get(name=action_object)
|
||||||
except Webhook.DoesNotExist:
|
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
|
self.instance.action_object = webhook
|
||||||
# Script
|
# Script
|
||||||
elif action_type == EventRuleActionChoices.SCRIPT:
|
elif action_type == EventRuleActionChoices.SCRIPT:
|
||||||
@ -211,7 +211,7 @@ class EventRuleImportForm(NetBoxModelImportForm):
|
|||||||
try:
|
try:
|
||||||
module, script = get_module_and_script(module_name, script_name)
|
module, script = get_module_and_script(module_name, script_name)
|
||||||
except ObjectDoesNotExist:
|
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 = module
|
||||||
self.instance.action_object_type = ContentType.objects.get_for_model(module, for_concrete_model=False)
|
self.instance.action_object_type = ContentType.objects.get_for_model(module, for_concrete_model=False)
|
||||||
self.instance.action_parameters = {
|
self.instance.action_parameters = {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from netbox.search.backends import search_backend
|
from netbox.search.backends import search_backend
|
||||||
@ -62,7 +63,7 @@ class Command(BaseCommand):
|
|||||||
# Determine which models to reindex
|
# Determine which models to reindex
|
||||||
indexers = self._get_indexers(*model_labels)
|
indexers = self._get_indexers(*model_labels)
|
||||||
if not indexers:
|
if not indexers:
|
||||||
raise CommandError("No indexers found!")
|
raise CommandError(_("No indexers found!"))
|
||||||
self.stdout.write(f'Reindexing {len(indexers)} models.')
|
self.stdout.write(f'Reindexing {len(indexers)} models.')
|
||||||
|
|
||||||
# Clear all cached values for the specified models (if not being lazy)
|
# Clear all cached values for the specified models (if not being lazy)
|
||||||
|
@ -11,6 +11,7 @@ from django.conf import settings
|
|||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.functional import classproperty
|
from django.utils.functional import classproperty
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.choices import JobStatusChoices
|
from core.choices import JobStatusChoices
|
||||||
from core.models import Job
|
from core.models import Job
|
||||||
@ -356,7 +357,7 @@ class BaseScript:
|
|||||||
return ordered_vars
|
return ordered_vars
|
||||||
|
|
||||||
def run(self, data, commit):
|
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
|
# Form rendering
|
||||||
|
|
||||||
@ -367,11 +368,11 @@ class BaseScript:
|
|||||||
fieldsets.extend(self.fieldsets)
|
fieldsets.extend(self.fieldsets)
|
||||||
else:
|
else:
|
||||||
fields = list(name for name, _ in self._get_vars().items())
|
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
|
# Append the default fieldset if defined in the Meta class
|
||||||
exec_parameters = ('_schedule_at', '_interval', '_commit') if self.scheduling_enabled else ('_commit',)
|
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
|
return fieldsets
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ from copy import deepcopy
|
|||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django_pglocks import advisory_lock
|
from django_pglocks import advisory_lock
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
from netaddr import IPSet
|
from netaddr import IPSet
|
||||||
@ -379,7 +380,7 @@ class AvailablePrefixesView(AvailableObjectsView):
|
|||||||
'vrf': parent.vrf.pk if parent.vrf else None,
|
'vrf': parent.vrf.pk if parent.vrf else None,
|
||||||
})
|
})
|
||||||
else:
|
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
|
return requested_objects
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from netaddr import AddrFormatError, IPNetwork
|
from netaddr import AddrFormatError, IPNetwork
|
||||||
|
|
||||||
from . import lookups, validators
|
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.)
|
# Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.)
|
||||||
return IPNetwork(value)
|
return IPNetwork(value)
|
||||||
except AddrFormatError:
|
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:
|
except (TypeError, ValueError) as e:
|
||||||
raise ValidationError(e)
|
raise ValidationError(e)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import validate_ipv4_address, validate_ipv6_address
|
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
|
from netaddr import IPAddress, IPNetwork, AddrFormatError
|
||||||
|
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ from netaddr import IPAddress, IPNetwork, AddrFormatError
|
|||||||
|
|
||||||
class IPAddressFormField(forms.Field):
|
class IPAddressFormField(forms.Field):
|
||||||
default_error_messages = {
|
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):
|
def to_python(self, value):
|
||||||
@ -28,19 +29,19 @@ class IPAddressFormField(forms.Field):
|
|||||||
try:
|
try:
|
||||||
validate_ipv6_address(value)
|
validate_ipv6_address(value)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
raise ValidationError("Invalid IPv4/IPv6 address format: {}".format(value))
|
raise ValidationError(_("Invalid IPv4/IPv6 address format: {address}").format(address=value))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return IPAddress(value)
|
return IPAddress(value)
|
||||||
except ValueError:
|
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:
|
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):
|
class IPNetworkFormField(forms.Field):
|
||||||
default_error_messages = {
|
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):
|
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.
|
# Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128.
|
||||||
if len(value.split('/')) != 2:
|
if len(value.split('/')) != 2:
|
||||||
raise ValidationError('CIDR mask (e.g. /24) is required.')
|
raise ValidationError(_('CIDR mask (e.g. /24) is required.'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return IPNetwork(value)
|
return IPNetwork(value)
|
||||||
except AddrFormatError:
|
except AddrFormatError:
|
||||||
raise ValidationError("Please specify a valid IPv4 or IPv6 address.")
|
raise ValidationError(_("Please specify a valid IPv4 or IPv6 address."))
|
||||||
|
@ -751,4 +751,4 @@ class ServiceCreateForm(ServiceForm):
|
|||||||
if not self.cleaned_data['description']:
|
if not self.cleaned_data['description']:
|
||||||
self.cleaned_data['description'] = service_template.description
|
self.cleaned_data['description'] = service_template.description
|
||||||
elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')):
|
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."))
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import BaseValidator, RegexValidator
|
from django.core.validators import BaseValidator, RegexValidator
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
def prefix_validator(prefix):
|
def prefix_validator(prefix):
|
||||||
if prefix.ip != prefix.cidr.ip:
|
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):
|
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'
|
code = 'max_prefix_length'
|
||||||
|
|
||||||
def compare(self, a, b):
|
def compare(self, a, b):
|
||||||
@ -16,7 +21,7 @@ class MaxPrefixLengthValidator(BaseValidator):
|
|||||||
|
|
||||||
|
|
||||||
class MinPrefixLengthValidator(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'
|
code = 'min_prefix_length'
|
||||||
|
|
||||||
def compare(self, a, b):
|
def compare(self, a, b):
|
||||||
@ -25,6 +30,6 @@ class MinPrefixLengthValidator(BaseValidator):
|
|||||||
|
|
||||||
DNSValidator = RegexValidator(
|
DNSValidator = RegexValidator(
|
||||||
regex=r'^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$',
|
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'
|
code='invalid'
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
@ -58,11 +59,11 @@ class ChoiceField(serializers.Field):
|
|||||||
if data == '':
|
if data == '':
|
||||||
if self.allow_blank:
|
if self.allow_blank:
|
||||||
return data
|
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
|
# Provide an explicit error message if the request is trying to write a dict or list
|
||||||
if isinstance(data, (dict, 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
|
# Check for string representations of boolean/integer values
|
||||||
if hasattr(data, 'lower'):
|
if hasattr(data, 'lower'):
|
||||||
@ -82,7 +83,7 @@ class ChoiceField(serializers.Field):
|
|||||||
except TypeError: # Input is an unhashable type
|
except TypeError: # Input is an unhashable type
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise ValidationError(f"{data} is not a valid choice.")
|
raise ValidationError(_("{value} is not a valid choice.").format(value=data))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def choices(self):
|
def choices(self):
|
||||||
@ -95,8 +96,8 @@ class ContentTypeField(RelatedField):
|
|||||||
Represent a ContentType as '<app_label>.<model>'
|
Represent a ContentType as '<app_label>.<model>'
|
||||||
"""
|
"""
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
"does_not_exist": "Invalid content type: {content_type}",
|
"does_not_exist": _("Invalid content type: {content_type}"),
|
||||||
"invalid": "Invalid value. Specify a content type as '<app_label>.<model_name>'.",
|
"invalid": _("Invalid value. Specify a content type as '<app_label>.<model_name>'."),
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
|
from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
@ -30,9 +31,12 @@ class WritableNestedSerializer(BaseModelSerializer):
|
|||||||
try:
|
try:
|
||||||
return queryset.get(**params)
|
return queryset.get(**params)
|
||||||
except ObjectDoesNotExist:
|
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:
|
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:
|
except FieldError as e:
|
||||||
raise ValidationError(e)
|
raise ValidationError(e)
|
||||||
|
|
||||||
@ -42,15 +46,17 @@ class WritableNestedSerializer(BaseModelSerializer):
|
|||||||
pk = int(data)
|
pk = int(data)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raise ValidationError(
|
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
|
# Look up object by PK
|
||||||
try:
|
try:
|
||||||
return self.Meta.model.objects.get(pk=pk)
|
return self.Meta.model.objects.get(pk=pk)
|
||||||
except ObjectDoesNotExist:
|
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
|
# Declared here for use by PrimaryModelSerializer, but should be imported from extras.api.nested_serializers
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.auth.backends import ModelBackend, RemoteUserBackend as _Rem
|
|||||||
from django.contrib.auth.models import Group, AnonymousUser
|
from django.contrib.auth.models import Group, AnonymousUser
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from users.constants import CONSTRAINT_TOKEN_USER
|
from users.constants import CONSTRAINT_TOKEN_USER
|
||||||
from users.models import ObjectPermission
|
from users.models import ObjectPermission
|
||||||
@ -132,7 +133,9 @@ class ObjectPermissionMixin:
|
|||||||
# Sanity check: Ensure that the requested permission applies to the specified object
|
# Sanity check: Ensure that the requested permission applies to the specified object
|
||||||
model = obj._meta.concrete_model
|
model = obj._meta.concrete_model
|
||||||
if model._meta.label_lower != '.'.join((app_label, model_name)):
|
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
|
# Compile a QuerySet filter that matches all instances of the specified model
|
||||||
tokens = {
|
tokens = {
|
||||||
|
@ -4,6 +4,7 @@ import threading
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.utils import DatabaseError
|
from django.db.utils import DatabaseError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .parameters import PARAMS
|
from .parameters import PARAMS
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class Config:
|
|||||||
if item in self.defaults:
|
if item in self.defaults:
|
||||||
return self.defaults[item]
|
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):
|
def _populate_from_cache(self):
|
||||||
"""Populate config data from Redis cache"""
|
"""Populate config data from Redis cache"""
|
||||||
|
@ -35,7 +35,9 @@ class CustomFieldsMixin:
|
|||||||
Return the ContentType of the form's model.
|
Return the ContentType of the form's model.
|
||||||
"""
|
"""
|
||||||
if not getattr(self, 'model', None):
|
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)
|
return ContentType.objects.get_for_model(self.model)
|
||||||
|
|
||||||
def _get_custom_fields(self, content_type):
|
def _get_custom_fields(self, content_type):
|
||||||
|
@ -275,16 +275,20 @@ class CustomFieldsMixin(models.Model):
|
|||||||
# Validate all field values
|
# Validate all field values
|
||||||
for field_name, value in self.custom_field_data.items():
|
for field_name, value in self.custom_field_data.items():
|
||||||
if field_name not in custom_fields:
|
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:
|
try:
|
||||||
custom_fields[field_name].validate(value)
|
custom_fields[field_name].validate(value)
|
||||||
except ValidationError as e:
|
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
|
# Check for missing required values
|
||||||
for cf in custom_fields.values():
|
for cf in custom_fields.values():
|
||||||
if cf.required and cf.name not in self.custom_field_data:
|
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):
|
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
|
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.
|
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__
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from netbox.navigation import MenuGroup
|
from netbox.navigation import MenuGroup
|
||||||
from utilities.choices import ButtonColorChoices
|
from utilities.choices import ButtonColorChoices
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'PluginMenu',
|
'PluginMenu',
|
||||||
@ -42,11 +43,11 @@ class PluginMenuItem:
|
|||||||
self.staff_only = staff_only
|
self.staff_only = staff_only
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
if type(permissions) not in (list, tuple):
|
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
|
self.permissions = permissions
|
||||||
if buttons is not None:
|
if buttons is not None:
|
||||||
if type(buttons) not in (list, tuple):
|
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
|
self.buttons = buttons
|
||||||
|
|
||||||
|
|
||||||
@ -64,9 +65,9 @@ class PluginMenuButton:
|
|||||||
self.icon_class = icon_class
|
self.icon_class = icon_class
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
if type(permissions) not in (list, tuple):
|
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
|
self.permissions = permissions
|
||||||
if color is not None:
|
if color is not None:
|
||||||
if color not in ButtonColorChoices.values():
|
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
|
self.color = color
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem
|
from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem
|
||||||
from .templates import PluginTemplateExtension
|
from .templates import PluginTemplateExtension
|
||||||
@ -20,18 +21,32 @@ def register_template_extensions(class_list):
|
|||||||
# Validation
|
# Validation
|
||||||
for template_extension in class_list:
|
for template_extension in class_list:
|
||||||
if not inspect.isclass(template_extension):
|
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):
|
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:
|
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)
|
registry['plugins']['template_extensions'][template_extension.model].append(template_extension)
|
||||||
|
|
||||||
|
|
||||||
def register_menu(menu):
|
def register_menu(menu):
|
||||||
if not isinstance(menu, PluginMenu):
|
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)
|
registry['plugins']['menus'].append(menu)
|
||||||
|
|
||||||
|
|
||||||
@ -42,10 +57,14 @@ def register_menu_items(section_name, class_list):
|
|||||||
# Validation
|
# Validation
|
||||||
for menu_link in class_list:
|
for menu_link in class_list:
|
||||||
if not isinstance(menu_link, PluginMenuItem):
|
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:
|
for button in menu_link.buttons:
|
||||||
if not isinstance(button, PluginMenuButton):
|
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
|
registry['plugins']['menu_items'][section_name] = class_list
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'PluginTemplateExtension',
|
'PluginTemplateExtension',
|
||||||
@ -31,7 +32,7 @@ class PluginTemplateExtension:
|
|||||||
if extra_context is None:
|
if extra_context is None:
|
||||||
extra_context = {}
|
extra_context = {}
|
||||||
elif not isinstance(extra_context, dict):
|
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})
|
return get_template(template_name).render({**self.context, **extra_context})
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
class Registry(dict):
|
class Registry(dict):
|
||||||
@ -10,13 +11,13 @@ class Registry(dict):
|
|||||||
try:
|
try:
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(f"Invalid store: {key}")
|
raise KeyError(_("Invalid store: {key}").format(key=key))
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
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):
|
def __delitem__(self, key):
|
||||||
raise TypeError("Cannot delete stores from registry")
|
raise TypeError(_("Cannot delete stores from registry"))
|
||||||
|
|
||||||
|
|
||||||
# Initialize the global registry
|
# Initialize the global registry
|
||||||
|
@ -14,6 +14,7 @@ from django.http import HttpResponse
|
|||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django_tables2.export import TableExport
|
from django_tables2.export import TableExport
|
||||||
|
|
||||||
from extras.models import ExportTemplate
|
from extras.models import ExportTemplate
|
||||||
@ -390,7 +391,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
try:
|
try:
|
||||||
instance = prefetched_objects[object_id]
|
instance = prefetched_objects[object_id]
|
||||||
except KeyError:
|
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('')
|
raise ValidationError('')
|
||||||
|
|
||||||
# Take a snapshot for change logging
|
# Take a snapshot for change logging
|
||||||
|
@ -11,6 +11,7 @@ from django.shortcuts import redirect, render
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from extras.signals import clear_events
|
from extras.signals import clear_events
|
||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
@ -101,7 +102,9 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||||||
request: The current request
|
request: The current request
|
||||||
parent: The parent object
|
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):
|
def prep_table_data(self, request, queryset, parent):
|
||||||
"""
|
"""
|
||||||
|
@ -93,7 +93,7 @@ class RestrictedGenericForeignKey(GenericForeignKey):
|
|||||||
if type(queryset) is dict:
|
if type(queryset) is dict:
|
||||||
restrict_params = queryset
|
restrict_params = queryset
|
||||||
elif queryset is not None:
|
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
|
# For efficiency, group the instances by content type and then do one
|
||||||
# query per model
|
# query per model
|
||||||
|
@ -49,7 +49,7 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form):
|
|||||||
|
|
||||||
# Determine whether we're reading from form data or an uploaded file
|
# Determine whether we're reading from form data or an uploaded file
|
||||||
if self.cleaned_data['data'] and import_method != ImportMethodChoices.DIRECT:
|
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:
|
if import_method == ImportMethodChoices.UPLOAD:
|
||||||
self.upload_file = 'upload_file'
|
self.upload_file = 'upload_file'
|
||||||
file = self.files.get('upload_file')
|
file = self.files.get('upload_file')
|
||||||
@ -78,7 +78,7 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form):
|
|||||||
elif format == ImportFormatChoices.YAML:
|
elif format == ImportFormatChoices.YAML:
|
||||||
self.cleaned_data['data'] = self._clean_yaml(data)
|
self.cleaned_data['data'] = self._clean_yaml(data)
|
||||||
else:
|
else:
|
||||||
raise forms.ValidationError(f"Unknown data format: {format}")
|
raise forms.ValidationError(_("Unknown data format: {format}").format(format=format))
|
||||||
|
|
||||||
def _detect_format(self, data):
|
def _detect_format(self, data):
|
||||||
"""
|
"""
|
||||||
|
@ -2,6 +2,7 @@ import re
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.models import fields_for_model
|
from django.forms.models import fields_for_model
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from utilities.choices import unpack_grouped_choices
|
from utilities.choices import unpack_grouped_choices
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
@ -38,7 +39,7 @@ def parse_numeric_range(string, base=10):
|
|||||||
try:
|
try:
|
||||||
begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1
|
begin, end = int(begin.strip(), base=base), int(end.strip(), base=base) + 1
|
||||||
except ValueError:
|
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))
|
values.extend(range(begin, end))
|
||||||
return sorted(set(values))
|
return sorted(set(values))
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ def parse_alphanumeric_range(string):
|
|||||||
begin, end = dash_range, dash_range
|
begin, end = dash_range, dash_range
|
||||||
if begin.isdigit() and end.isdigit():
|
if begin.isdigit() and end.isdigit():
|
||||||
if int(begin) >= int(end):
|
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)):
|
for n in list(range(int(begin), int(end) + 1)):
|
||||||
values.append(n)
|
values.append(n)
|
||||||
@ -73,10 +74,10 @@ def parse_alphanumeric_range(string):
|
|||||||
else:
|
else:
|
||||||
# Not a valid range (more than a single character)
|
# Not a valid range (more than a single character)
|
||||||
if not len(begin) == len(end) == 1:
|
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):
|
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)):
|
for n in list(range(ord(begin), ord(end) + 1)):
|
||||||
values.append(chr(n))
|
values.append(chr(n))
|
||||||
@ -221,18 +222,24 @@ def parse_csv(reader):
|
|||||||
if '.' in header:
|
if '.' in header:
|
||||||
field, to_field = header.split('.', 1)
|
field, to_field = header.split('.', 1)
|
||||||
if field in headers:
|
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
|
headers[field] = to_field
|
||||||
else:
|
else:
|
||||||
if header in headers:
|
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
|
headers[header] = None
|
||||||
|
|
||||||
# Parse CSV rows into a list of dictionaries mapped from the column headers.
|
# Parse CSV rows into a list of dictionaries mapped from the column headers.
|
||||||
for i, row in enumerate(reader, start=1):
|
for i, row in enumerate(reader, start=1):
|
||||||
if len(row) != len(headers):
|
if len(row) != len(headers):
|
||||||
raise forms.ValidationError(
|
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]
|
row = [col.strip() for col in row]
|
||||||
record = dict(zip(headers.keys(), row))
|
record = dict(zip(headers.keys(), row))
|
||||||
@ -253,14 +260,18 @@ def validate_csv(headers, fields, required_fields):
|
|||||||
is_update = True
|
is_update = True
|
||||||
continue
|
continue
|
||||||
if field not in fields:
|
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'):
|
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):
|
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)
|
# Validate required fields (if not an update)
|
||||||
if not is_update:
|
if not is_update:
|
||||||
for f in required_fields:
|
for f in required_fields:
|
||||||
if f not in headers:
|
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))
|
||||||
|
@ -3,6 +3,7 @@ from typing import Dict, List, Tuple
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'APISelect',
|
'APISelect',
|
||||||
@ -119,7 +120,11 @@ class APISelect(forms.Select):
|
|||||||
update = [{'fieldName': f, 'queryParam': q} for (f, q) in self.dynamic_params.items()]
|
update = [{'fieldName': f, 'queryParam': q} for (f, q) in self.dynamic_params.items()]
|
||||||
self._serialize_params(key, update)
|
self._serialize_params(key, update)
|
||||||
except IndexError as error:
|
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):
|
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()]
|
update = [{'queryParam': k, 'queryValue': v} for (k, v) in self.static_params.items()]
|
||||||
self._serialize_params(key, update)
|
self._serialize_params(key, update)
|
||||||
except IndexError as error:
|
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):
|
def add_query_params(self, query_params):
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'get_permission_for_model',
|
'get_permission_for_model',
|
||||||
@ -36,7 +37,7 @@ def resolve_permission(name):
|
|||||||
action, model_name = codename.rsplit('_', 1)
|
action, model_name = codename.rsplit('_', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise 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
|
return app_label, action, model_name
|
||||||
@ -53,7 +54,7 @@ def resolve_permission_ct(name):
|
|||||||
try:
|
try:
|
||||||
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
|
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
|
||||||
except ContentType.DoesNotExist:
|
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
|
return content_type, action
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from netaddr import AddrFormatError, IPAddress
|
from netaddr import AddrFormatError, IPAddress
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ def get_client_ip(request, additional_headers=()):
|
|||||||
return IPAddress(ip)
|
return IPAddress(ip)
|
||||||
except AddrFormatError:
|
except AddrFormatError:
|
||||||
# We did our best
|
# 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
|
# Could not determine the client IP address from request headers
|
||||||
return None
|
return None
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -43,5 +44,7 @@ def register_table_column(column, name, *tables):
|
|||||||
for table in tables:
|
for table in tables:
|
||||||
reg = registry['tables'][table]
|
reg = registry['tables'][table]
|
||||||
if name in reg:
|
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
|
reg[name] = column
|
||||||
|
@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
from django.db.models import ForeignKey
|
from django.db.models import ForeignKey
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from extras.choices import ObjectChangeActionChoices
|
from extras.choices import ObjectChangeActionChoices
|
||||||
from extras.models import ObjectChange
|
from extras.models import ObjectChange
|
||||||
@ -621,7 +622,7 @@ class ViewTestCases:
|
|||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
def test_bulk_update_objects_with_permission(self):
|
def test_bulk_update_objects_with_permission(self):
|
||||||
if not hasattr(self, 'csv_update_data'):
|
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()
|
initial_count = self._get_queryset().count()
|
||||||
array, csv_data = self._get_update_csv_data()
|
array, csv_data = self._get_update_csv_data()
|
||||||
|
@ -15,6 +15,7 @@ from django.utils import timezone
|
|||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.timezone import localtime
|
from django.utils.timezone import localtime
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from jinja2.sandbox import SandboxedEnvironment
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
from mptt.models import MPTTModel
|
from mptt.models import MPTTModel
|
||||||
|
|
||||||
@ -306,13 +307,17 @@ def to_meters(length, unit):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if length < 0:
|
if length < 0:
|
||||||
raise ValueError("Length must be a positive number")
|
raise ValueError(_("Length must be a positive number"))
|
||||||
except TypeError:
|
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()
|
valid_units = CableLengthUnitChoices.values()
|
||||||
if unit not in valid_units:
|
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:
|
if unit == CableLengthUnitChoices.UNIT_KILOMETER:
|
||||||
return length * 1000
|
return length * 1000
|
||||||
@ -326,7 +331,7 @@ def to_meters(length, unit):
|
|||||||
return length * Decimal(0.3048)
|
return length * Decimal(0.3048)
|
||||||
if unit == CableLengthUnitChoices.UNIT_INCH:
|
if unit == CableLengthUnitChoices.UNIT_INCH:
|
||||||
return length * Decimal(0.0254)
|
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):
|
def to_grams(weight, unit):
|
||||||
@ -335,13 +340,17 @@ def to_grams(weight, unit):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if weight < 0:
|
if weight < 0:
|
||||||
raise ValueError("Weight must be a positive number")
|
raise ValueError(_("Weight must be a positive number"))
|
||||||
except TypeError:
|
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()
|
valid_units = WeightUnitChoices.values()
|
||||||
if unit not in valid_units:
|
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:
|
if unit == WeightUnitChoices.UNIT_KILOGRAM:
|
||||||
return weight * 1000
|
return weight * 1000
|
||||||
@ -351,7 +360,7 @@ def to_grams(weight, unit):
|
|||||||
return weight * Decimal(453.592)
|
return weight * Decimal(453.592)
|
||||||
if unit == WeightUnitChoices.UNIT_OUNCE:
|
if unit == WeightUnitChoices.UNIT_OUNCE:
|
||||||
return weight * Decimal(28.3495)
|
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):
|
def render_jinja2(template_code, context):
|
||||||
|
@ -2,6 +2,7 @@ import re
|
|||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile
|
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
|
from netbox.config import get_config
|
||||||
|
|
||||||
@ -61,4 +62,4 @@ def validate_regex(value):
|
|||||||
try:
|
try:
|
||||||
re.compile(value)
|
re.compile(value)
|
||||||
except re.error:
|
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))
|
||||||
|
@ -2,6 +2,7 @@ from django.contrib.auth.mixins import AccessMixin
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.urls.exceptions import NoReverseMatch
|
from django.urls.exceptions import NoReverseMatch
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from .permissions import resolve_permission
|
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.
|
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):
|
def has_permission(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
@ -68,7 +71,9 @@ class ObjectPermissionRequiredMixin(AccessMixin):
|
|||||||
"""
|
"""
|
||||||
Return the specific permission necessary to perform the requested action on an object.
|
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):
|
def has_permission(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
@ -89,8 +94,10 @@ class ObjectPermissionRequiredMixin(AccessMixin):
|
|||||||
|
|
||||||
if not hasattr(self, 'queryset'):
|
if not hasattr(self, 'queryset'):
|
||||||
raise ImproperlyConfigured(
|
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():
|
if not self.has_permission():
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .choices import WirelessChannelChoices
|
from .choices import WirelessChannelChoices
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ def get_channel_attr(channel, attr):
|
|||||||
Return the specified attribute of a given WirelessChannelChoices value.
|
Return the specified attribute of a given WirelessChannelChoices value.
|
||||||
"""
|
"""
|
||||||
if channel not in WirelessChannelChoices.values():
|
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('-')
|
channel_values = channel.split('-')
|
||||||
attrs = {
|
attrs = {
|
||||||
@ -22,6 +23,6 @@ def get_channel_attr(channel, attr):
|
|||||||
'width': Decimal(channel_values[3]),
|
'width': Decimal(channel_values[3]),
|
||||||
}
|
}
|
||||||
if attr not in attrs:
|
if attr not in attrs:
|
||||||
raise ValueError(f"Invalid channel attribute: {attr}")
|
raise ValueError(_("Invalid channel attribute: {name}").format(name=attr))
|
||||||
|
|
||||||
return attrs[attr]
|
return attrs[attr]
|
||||||
|
Loading…
Reference in New Issue
Block a user