mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-19 03:42:25 -06:00
Merge branch 'develop' into feature
This commit is contained in:
@@ -77,7 +77,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
|
||||
|
||||
@@ -48,7 +48,7 @@ class BulkImportForm(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')
|
||||
@@ -77,7 +77,7 @@ class BulkImportForm(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):
|
||||
"""
|
||||
|
||||
@@ -93,6 +93,8 @@ class JSONField(_JSONField):
|
||||
"""
|
||||
Custom wrapper around Django's built-in JSONField to avoid presenting "null" as the default text.
|
||||
"""
|
||||
empty_values = [None, '', ()]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
|
||||
@@ -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 {row}: Expected {count_expected} columns but found {count_found}").format(
|
||||
row=i, 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))
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'extras:exporttemplate_add' %}?content_type={{ content_type.pk }}">{% trans "Add export template" %}...</a>
|
||||
<a class="dropdown-item" href="{% url 'extras:exporttemplate_add' %}?content_types={{ content_type.pk }}">{% trans "Add export template" %}...</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -307,13 +308,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
|
||||
@@ -327,7 +332,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):
|
||||
@@ -336,13 +341,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
|
||||
@@ -352,7 +361,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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user