diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py
index 444701acc..818629fd7 100644
--- a/netbox/extras/models/change_logging.py
+++ b/netbox/extras/models/change_logging.py
@@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
from extras.choices import *
from ..querysets import ObjectChangeQuerySet
@@ -19,6 +20,7 @@ class ObjectChange(models.Model):
parent device. This will ensure changes made to component models appear in the parent model's changelog.
"""
time = models.DateTimeField(
+ verbose_name=_('time'),
auto_now_add=True,
editable=False,
db_index=True
@@ -31,14 +33,17 @@ class ObjectChange(models.Model):
null=True
)
user_name = models.CharField(
+ verbose_name=_('user name'),
max_length=150,
editable=False
)
request_id = models.UUIDField(
+ verbose_name=_('request id'),
editable=False,
db_index=True
)
action = models.CharField(
+ verbose_name=_('action'),
max_length=50,
choices=ObjectChangeActionChoices
)
@@ -72,11 +77,13 @@ class ObjectChange(models.Model):
editable=False
)
prechange_data = models.JSONField(
+ verbose_name=_('prechange data'),
editable=False,
blank=True,
null=True
)
postchange_data = models.JSONField(
+ verbose_name=_('postchange data'),
editable=False,
blank=True,
null=True
diff --git a/netbox/extras/models/configs.py b/netbox/extras/models/configs.py
index ee9f7cfda..ce6ed3de2 100644
--- a/netbox/extras/models/configs.py
+++ b/netbox/extras/models/configs.py
@@ -2,7 +2,7 @@ from django.conf import settings
from django.core.validators import ValidationError
from django.db import models
from django.urls import reverse
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from jinja2.loaders import BaseLoader
from jinja2.sandbox import SandboxedEnvironment
@@ -31,17 +31,21 @@ class ConfigContext(SyncedDataMixin, CloningMixin, ChangeLoggedModel):
will be available to a Device in site A assigned to tenant B. Data is stored in JSON format.
"""
name = models.CharField(
+ verbose_name=_('name'),
max_length=100,
unique=True
)
weight = models.PositiveSmallIntegerField(
+ verbose_name=_('weight'),
default=1000
)
description = models.CharField(
+ verbose_name=_('description'),
max_length=200,
blank=True
)
is_active = models.BooleanField(
+ verbose_name=_('is active'),
default=True,
)
regions = models.ManyToManyField(
@@ -138,7 +142,7 @@ class ConfigContext(SyncedDataMixin, CloningMixin, ChangeLoggedModel):
# Verify that JSON data is provided as an object
if type(self.data) is not dict:
raise ValidationError(
- {'data': 'JSON data must be in object form. Example: {"foo": 123}'}
+ {'data': _('JSON data must be in object form. Example: {"foo": 123}')}
)
def sync_data(self):
@@ -194,7 +198,7 @@ class ConfigContextModel(models.Model):
# Verify that JSON data is provided as an object
if self.local_context_data and type(self.local_context_data) is not dict:
raise ValidationError(
- {'local_context_data': 'JSON data must be in object form. Example: {"foo": 123}'}
+ {'local_context_data': _('JSON data must be in object form. Example: {"foo": 123}')}
)
@@ -204,16 +208,20 @@ class ConfigContextModel(models.Model):
class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
name = models.CharField(
+ verbose_name=_('name'),
max_length=100
)
description = models.CharField(
+ verbose_name=_('description'),
max_length=200,
blank=True
)
template_code = models.TextField(
+ verbose_name=_('template code'),
help_text=_('Jinja2 template code.')
)
environment_params = models.JSONField(
+ verbose_name=_('environment params'),
blank=True,
null=True,
default=dict,
diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py
index bdb600c88..4878ff282 100644
--- a/netbox/extras/models/customfields.py
+++ b/netbox/extras/models/customfields.py
@@ -12,7 +12,7 @@ from django.db import models
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from extras.choices import *
from extras.utils import FeatureQuery
@@ -64,6 +64,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
help_text=_('The object(s) to which this field applies.')
)
type = models.CharField(
+ verbose_name=_('type'),
max_length=50,
choices=CustomFieldTypeChoices,
default=CustomFieldTypeChoices.TYPE_TEXT,
@@ -77,49 +78,56 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
help_text=_('The type of NetBox object this field maps to (for object fields)')
)
name = models.CharField(
+ verbose_name=_('name'),
max_length=50,
unique=True,
help_text=_('Internal field name'),
validators=(
RegexValidator(
regex=r'^[a-z0-9_]+$',
- message="Only alphanumeric characters and underscores are allowed.",
+ message=_("Only alphanumeric characters and underscores are allowed."),
flags=re.IGNORECASE
),
RegexValidator(
regex=r'__',
- message="Double underscores are not permitted in custom field names.",
+ message=_("Double underscores are not permitted in custom field names."),
flags=re.IGNORECASE,
inverse_match=True
),
)
)
label = models.CharField(
+ verbose_name=_('label'),
max_length=50,
blank=True,
help_text=_('Name of the field as displayed to users (if not provided, '
'the field\'s name will be used)')
)
group_name = models.CharField(
+ verbose_name=_('group name'),
max_length=50,
blank=True,
help_text=_("Custom fields within the same group will be displayed together")
)
description = models.CharField(
+ verbose_name=_('description'),
max_length=200,
blank=True
)
required = models.BooleanField(
+ verbose_name=_('required'),
default=False,
help_text=_('If true, this field is required when creating new objects '
'or editing an existing object.')
)
search_weight = models.PositiveSmallIntegerField(
+ verbose_name=_('search weight'),
default=1000,
help_text=_('Weighting for search. Lower values are considered more important. '
'Fields with a search weight of zero will be ignored.')
)
filter_logic = models.CharField(
+ verbose_name=_('filter logic'),
max_length=50,
choices=CustomFieldFilterLogicChoices,
default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
@@ -127,6 +135,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
'matches the entire field.')
)
default = models.JSONField(
+ verbose_name=_('default'),
blank=True,
null=True,
help_text=_('Default value for the field (must be a JSON value). Encapsulate '
@@ -134,35 +143,41 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
)
weight = models.PositiveSmallIntegerField(
default=100,
- verbose_name='Display weight',
+ verbose_name=_('Display weight'),
help_text=_('Fields with higher weights appear lower in a form.')
)
validation_minimum = models.IntegerField(
blank=True,
null=True,
- verbose_name='Minimum value',
+ verbose_name=_('Minimum value'),
help_text=_('Minimum allowed value (for numeric fields)')
)
validation_maximum = models.IntegerField(
blank=True,
null=True,
- verbose_name='Maximum value',
+ verbose_name=_('Maximum value'),
help_text=_('Maximum allowed value (for numeric fields)')
)
validation_regex = models.CharField(
blank=True,
validators=[validate_regex],
max_length=500,
- verbose_name='Validation regex',
+ verbose_name=_('Validation regex'),
help_text=_(
'Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For '
'example, ^[A-Z]{3}$
will limit values to exactly three uppercase letters.'
)
)
+<<<<<<< HEAD
choice_set = models.ForeignKey(
to='CustomFieldChoiceSet',
on_delete=models.PROTECT,
related_name='choices_for',
+=======
+ choices = ArrayField(
+ verbose_name=_('choices'),
+ base_field=models.CharField(max_length=100),
+>>>>>>> 9a447ed8f (13132 add gettext_lazy to models)
blank=True,
null=True
)
@@ -170,12 +185,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
max_length=50,
choices=CustomFieldVisibilityChoices,
default=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE,
- verbose_name='UI visibility',
+ verbose_name=_('UI visibility'),
help_text=_('Specifies the visibility of custom field in the UI')
)
is_cloneable = models.BooleanField(
default=False,
- verbose_name='Cloneable',
+ verbose_name=_('Cloneable'),
help_text=_('Replicate this value when cloning objects')
)
@@ -265,15 +280,15 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
self.validate(default_value)
except ValidationError as err:
raise ValidationError({
- 'default': f'Invalid default value "{self.default}": {err.message}'
+ 'default': _('Invalid default value "{default}": {message}').format(default=self.default, message=self.message)
})
# Minimum/maximum values can be set only for numeric fields
if self.type not in (CustomFieldTypeChoices.TYPE_INTEGER, CustomFieldTypeChoices.TYPE_DECIMAL):
if self.validation_minimum:
- raise ValidationError({'validation_minimum': "A minimum value may be set only for numeric fields"})
+ raise ValidationError({'validation_minimum': _("A minimum value may be set only for numeric fields")})
if self.validation_maximum:
- raise ValidationError({'validation_maximum': "A maximum value may be set only for numeric fields"})
+ raise ValidationError({'validation_maximum': _("A maximum value may be set only for numeric fields")})
# Regex validation can be set only for text fields
regex_types = (
@@ -283,10 +298,23 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
)
if self.validation_regex and self.type not in regex_types:
raise ValidationError({
- 'validation_regex': "Regular expression validation is supported only for text and URL fields"
+ 'validation_regex': _("Regular expression validation is supported only for text and URL fields")
})
+<<<<<<< HEAD
# Choice set must be set on selection fields, and *only* on selection fields
+=======
+ # Choices can be set only on selection fields
+ if self.choices and self.type not in (
+ CustomFieldTypeChoices.TYPE_SELECT,
+ CustomFieldTypeChoices.TYPE_MULTISELECT
+ ):
+ raise ValidationError({
+ 'choices': _("Choices may be set only for custom selection fields.")
+ })
+
+ # Selection fields must have at least one choice defined
+>>>>>>> 9a447ed8f (13132 add gettext_lazy to models)
if self.type in (
CustomFieldTypeChoices.TYPE_SELECT,
CustomFieldTypeChoices.TYPE_MULTISELECT
@@ -297,24 +325,28 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
})
elif self.choice_set:
raise ValidationError({
+<<<<<<< HEAD
'choice_set': "Choices may be set only on selection fields."
+=======
+ 'choices': _("Selection fields must specify at least one choice.")
+>>>>>>> 9a447ed8f (13132 add gettext_lazy to models)
})
# A selection field's default (if any) must be present in its available choices
if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.default and self.default not in self.choices:
raise ValidationError({
- 'default': f"The specified default value ({self.default}) is not listed as an available choice."
+ 'default': _("The specified default value ({default}) is not listed as an available choice.").format(default=self.default)
})
# Object fields must define an object_type; other fields must not
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
if not self.object_type:
raise ValidationError({
- 'object_type': "Object fields must define an object type."
+ 'object_type': _("Object fields must define an object type.")
})
elif self.object_type:
raise ValidationError({
- 'object_type': f"{self.get_type_display()} fields may not define an object type."
+ 'object_type': _("{type_display} fields may not define an object type.".format(type_display=self.get_type_display()))
})
def serialize(self, value):
@@ -393,8 +425,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
elif self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
choices = (
(None, '---------'),
- (True, 'True'),
- (False, 'False'),
+ (True, _('True')),
+ (False, _('False')),
)
field = forms.NullBooleanField(
required=required, initial=initial, widget=forms.Select(choices=choices)
@@ -463,7 +495,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
field.validators = [
RegexValidator(
regex=self.validation_regex,
- message=mark_safe(f"Values must match this regex: {self.validation_regex}
")
+ message=mark_safe(_("Values must match this regex: {regex}
").format(regex=self.validation_regex))
)
]
@@ -476,7 +508,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
if enforce_visibility and self.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
field.disabled = True
prepend = '
' if field.help_text else ''
- field.help_text += f'{prepend} Field is set to read-only.'
+ field.help_text += f'{prepend} ' + _('Field is set to read-only.')
return field
@@ -558,33 +590,33 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
# Validate text field
if self.type in (CustomFieldTypeChoices.TYPE_TEXT, CustomFieldTypeChoices.TYPE_LONGTEXT):
if type(value) is not str:
- raise ValidationError(f"Value must be a string.")
+ raise ValidationError(_("Value must be a string."))
if self.validation_regex and not re.match(self.validation_regex, value):
- raise ValidationError(f"Value must match regex '{self.validation_regex}'")
+ raise ValidationError(_("Value must match regex '{regex}'").format(regex=self.validation_regex))
# Validate integer
elif self.type == CustomFieldTypeChoices.TYPE_INTEGER:
if type(value) is not int:
- raise ValidationError("Value must be an integer.")
+ raise ValidationError(_("Value must be an integer."))
if self.validation_minimum is not None and value < self.validation_minimum:
- raise ValidationError(f"Value must be at least {self.validation_minimum}")
+ raise ValidationError(_("Value must be at least {validation_minimum}").format(validation_minimum=self.validation_maximum))
if self.validation_maximum is not None and value > self.validation_maximum:
- raise ValidationError(f"Value must not exceed {self.validation_maximum}")
+ raise ValidationError(_("Value must not exceed {validation_maximum}").format(validation_maximum=self.validation_maximum))
# Validate decimal
elif self.type == CustomFieldTypeChoices.TYPE_DECIMAL:
try:
decimal.Decimal(value)
except decimal.InvalidOperation:
- raise ValidationError("Value must be a decimal.")
+ raise ValidationError(_("Value must be a decimal."))
if self.validation_minimum is not None and value < self.validation_minimum:
- raise ValidationError(f"Value must be at least {self.validation_minimum}")
+ raise ValidationError(_("Value must be at least {validation_minimum}").format(validation_minimum=self.validation_minimum))
if self.validation_maximum is not None and value > self.validation_maximum:
- raise ValidationError(f"Value must not exceed {self.validation_maximum}")
+ raise ValidationError(_("Value must not exceed {validation_maximum}").format(validation_maximum=self.validation_maximum))
# Validate boolean
elif self.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value not in [True, False, 1, 0]:
- raise ValidationError("Value must be true or false.")
+ raise ValidationError(_("Value must be true or false."))
# Validate date
elif self.type == CustomFieldTypeChoices.TYPE_DATE:
@@ -592,7 +624,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
try:
date.fromisoformat(value)
except ValueError:
- raise ValidationError("Date values must be in ISO 8601 format (YYYY-MM-DD).")
+ raise ValidationError(_("Date values must be in ISO 8601 format (YYYY-MM-DD)."))
# Validate date & time
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
@@ -600,37 +632,38 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
try:
datetime.fromisoformat(value)
except ValueError:
- raise ValidationError("Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS).")
+ raise ValidationError(_("Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."))
# Validate selected choice
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
if value not in self.choices:
raise ValidationError(
- f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}"
+ _("Invalid choice ({value}). Available choices are: {choices}").format(value=value, choices=', '.join(self.choices))
)
# Validate all selected choices
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
if not set(value).issubset(self.choices):
raise ValidationError(
- f"Invalid choice(s) ({', '.join(value)}). Available choices are: {', '.join(self.choices)}"
+ _("Invalid choice(s) ({invalid_choices}). Available choices are: {available_choices}").format(
+ invalid_choices=', '.join(value), available_choices=', '.join(self.choices))
)
# Validate selected object
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
if type(value) is not int:
- raise ValidationError(f"Value must be an object ID, not {type(value).__name__}")
+ raise ValidationError(_("Value must be an object ID, not {type}").format(type=type(value).__name__))
# Validate selected objects
elif self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
if type(value) is not list:
- raise ValidationError(f"Value must be a list of object IDs, not {type(value).__name__}")
+ raise ValidationError(_("Value must be a list of object IDs, not {type}").format(type=type(value).__name__))
for id in value:
if type(id) is not int:
- raise ValidationError(f"Found invalid object ID: {id}")
+ raise ValidationError(_("Found invalid object ID: {id}").format(id=id))
elif self.required:
- raise ValidationError("Required field cannot be empty.")
+ raise ValidationError(_("Required field cannot be empty."))
class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
@@ -680,3 +713,4 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
self.extra_choices = sorted(self.choices)
return super().save(*args, **kwargs)
+
diff --git a/netbox/extras/models/dashboard.py b/netbox/extras/models/dashboard.py
index a0d300ff6..33bb735c4 100644
--- a/netbox/extras/models/dashboard.py
+++ b/netbox/extras/models/dashboard.py
@@ -1,5 +1,6 @@
from django.contrib.auth import get_user_model
from django.db import models
+from django.utils.translation import gettext_lazy as _
from extras.dashboard.utils import get_widget_class
@@ -15,9 +16,11 @@ class Dashboard(models.Model):
related_name='dashboard'
)
layout = models.JSONField(
+ verbose_name=_('layout'),
default=list
)
config = models.JSONField(
+ verbose_name=_('config'),
default=dict
)
diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py
index 193d3af6a..33837a027 100644
--- a/netbox/extras/models/models.py
+++ b/netbox/extras/models/models.py
@@ -12,7 +12,7 @@ from django.http import HttpResponse
from django.urls import reverse
from django.utils import timezone
from django.utils.formats import date_format
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from rest_framework.utils.encoders import JSONEncoder
from extras.choices import *
@@ -53,64 +53,74 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
help_text=_("The object(s) to which this Webhook applies.")
)
name = models.CharField(
+ verbose_name=_('name'),
max_length=150,
unique=True
)
type_create = models.BooleanField(
+ verbose_name=_('type create'),
default=False,
help_text=_("Triggers when a matching object is created.")
)
type_update = models.BooleanField(
+ verbose_name=_('type update'),
default=False,
help_text=_("Triggers when a matching object is updated.")
)
type_delete = models.BooleanField(
+ verbose_name=_('type delete'),
default=False,
help_text=_("Triggers when a matching object is deleted.")
)
type_job_start = models.BooleanField(
+ verbose_name=_('type job start'),
default=False,
help_text=_("Triggers when a job for a matching object is started.")
)
type_job_end = models.BooleanField(
+ verbose_name=_('type job end'),
default=False,
help_text=_("Triggers when a job for a matching object terminates.")
)
payload_url = models.CharField(
max_length=500,
- verbose_name='URL',
+ verbose_name=_('URL'),
help_text=_('This URL will be called using the HTTP method defined when the webhook is called. '
'Jinja2 template processing is supported with the same context as the request body.')
)
enabled = models.BooleanField(
+ verbose_name=_('enabled'),
default=True
)
http_method = models.CharField(
max_length=30,
choices=WebhookHttpMethodChoices,
default=WebhookHttpMethodChoices.METHOD_POST,
- verbose_name='HTTP method'
+ verbose_name=_('HTTP method')
)
http_content_type = models.CharField(
max_length=100,
default=HTTP_CONTENT_TYPE_JSON,
- verbose_name='HTTP content type',
+ verbose_name=_('HTTP content type'),
help_text=_('The complete list of official content types is available '
'here.')
)
additional_headers = models.TextField(
+ verbose_name=_('additional headers'),
blank=True,
help_text=_("User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. "
"Headers should be defined in the format Name: Value
. Jinja2 template processing is "
"supported with the same context as the request body (below).")
)
body_template = models.TextField(
+ verbose_name=_('body template'),
blank=True,
help_text=_('Jinja2 template for a custom request body. If blank, a JSON object representing the change will be '
'included. Available context data includes: event
, model
, '
'timestamp
, username
, request_id
, and data
.')
)
secret = models.CharField(
+ verbose_name=_('secret'),
max_length=255,
blank=True,
help_text=_("When provided, the request will include a 'X-Hook-Signature' "
@@ -119,20 +129,21 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
"the request.")
)
conditions = models.JSONField(
+ verbose_name=_('conditions'),
blank=True,
null=True,
help_text=_("A set of conditions which determine whether the webhook will be generated.")
)
ssl_verification = models.BooleanField(
default=True,
- verbose_name='SSL verification',
+ verbose_name=_('SSL verification'),
help_text=_("Enable SSL certificate verification. Disable with caution!")
)
ca_file_path = models.CharField(
max_length=4096,
null=True,
blank=True,
- verbose_name='CA File Path',
+ verbose_name=_('CA File Path'),
help_text=_('The specific CA certificate file to use for SSL verification. '
'Leave blank to use the system defaults.')
)
@@ -164,7 +175,7 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
self.type_create, self.type_update, self.type_delete, self.type_job_start, self.type_job_end
]):
raise ValidationError(
- "At least one event type must be selected: create, update, delete, job_start, and/or job_end."
+ _("At least one event type must be selected: create, update, delete, job_start, and/or job_end.")
)
if self.conditions:
@@ -176,7 +187,7 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
# CA file path requires SSL verification enabled
if not self.ssl_verification and self.ca_file_path:
raise ValidationError({
- 'ca_file_path': 'Do not specify a CA certificate file if SSL verification is disabled.'
+ 'ca_file_path': _('Do not specify a CA certificate file if SSL verification is disabled.')
})
def render_headers(self, context):
@@ -219,34 +230,41 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
help_text=_('The object type(s) to which this link applies.')
)
name = models.CharField(
+ verbose_name=_('name'),
max_length=100,
unique=True
)
enabled = models.BooleanField(
+ verbose_name=_('enabled'),
default=True
)
link_text = models.TextField(
+ verbose_name=_('link text'),
help_text=_("Jinja2 template code for link text")
)
link_url = models.TextField(
- verbose_name='Link URL',
+ verbose_name=_('Link URL'),
help_text=_("Jinja2 template code for link URL")
)
weight = models.PositiveSmallIntegerField(
+ verbose_name=_('weight'),
default=100
)
group_name = models.CharField(
+ verbose_name=_('group name'),
max_length=50,
blank=True,
help_text=_("Links with the same group will appear as a dropdown menu")
)
button_class = models.CharField(
+ verbose_name=_('button class'),
max_length=30,
choices=CustomLinkButtonClassChoices,
default=CustomLinkButtonClassChoices.DEFAULT,
help_text=_("The class of the first link in a group will be used for the dropdown button")
)
new_window = models.BooleanField(
+ verbose_name=_('new window'),
default=False,
help_text=_("Force link to open in a new window")
)
@@ -306,9 +324,11 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
help_text=_('The object type(s) to which this template applies.')
)
name = models.CharField(
+ verbose_name=_('name'),
max_length=100
)
description = models.CharField(
+ verbose_name=_('description'),
max_length=200,
blank=True
)
@@ -319,15 +339,17 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
mime_type = models.CharField(
max_length=50,
blank=True,
- verbose_name='MIME type',
+ verbose_name=_('MIME type'),
help_text=_('Defaults to text/plain; charset=utf-8
')
)
file_extension = models.CharField(
+ verbose_name=_('file extension'),
max_length=15,
blank=True,
help_text=_('Extension to append to the rendered filename')
)
as_attachment = models.BooleanField(
+ verbose_name=_('as attachment'),
default=True,
help_text=_("Download file as attachment")
)
@@ -354,7 +376,7 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
if self.name.lower() == 'table':
raise ValidationError({
- 'name': f'"{self.name}" is a reserved name. Please choose a different name.'
+ 'name': _('"{name}" is a reserved name. Please choose a different name.').format(name=self.name)
})
def sync_data(self):
@@ -407,14 +429,17 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
help_text=_('The object type(s) to which this filter applies.')
)
name = models.CharField(
+ verbose_name=_('name'),
max_length=100,
unique=True
)
slug = models.SlugField(
+ verbose_name=_('slug'),
max_length=100,
unique=True
)
description = models.CharField(
+ verbose_name=_('description'),
max_length=200,
blank=True
)
@@ -425,15 +450,20 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
null=True
)
weight = models.PositiveSmallIntegerField(
+ verbose_name=_('weight'),
default=100
)
enabled = models.BooleanField(
+ verbose_name=_('enabled'),
default=True
)
shared = models.BooleanField(
+ verbose_name=_('shared'),
default=True
)
- parameters = models.JSONField()
+ parameters = models.JSONField(
+ verbose_name=_('parameters')
+ )
clone_fields = (
'content_types', 'weight', 'enabled', 'parameters',
@@ -458,7 +488,7 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
# Verify that `parameters` is a JSON object
if type(self.parameters) is not dict:
raise ValidationError(
- {'parameters': 'Filter parameters must be stored as a dictionary of keyword arguments.'}
+ {'parameters': _('Filter parameters must be stored as a dictionary of keyword arguments.')}
)
@property
@@ -485,9 +515,14 @@ class ImageAttachment(ChangeLoggedModel):
height_field='image_height',
width_field='image_width'
)
- image_height = models.PositiveSmallIntegerField()
- image_width = models.PositiveSmallIntegerField()
+ image_height = models.PositiveSmallIntegerField(
+ verbose_name=_('image height'),
+ )
+ image_width = models.PositiveSmallIntegerField(
+ verbose_name=_('image width'),
+ )
name = models.CharField(
+ verbose_name=_('name'),
max_length=50,
blank=True
)
@@ -565,11 +600,14 @@ class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ExportTemplat
null=True
)
kind = models.CharField(
+ verbose_name=_('kind'),
max_length=30,
choices=JournalEntryKindChoices,
default=JournalEntryKindChoices.KIND_INFO
)
- comments = models.TextField()
+ comments = models.TextField(
+ verbose_name=_('comments'),
+ )
class Meta:
ordering = ('-created',)
@@ -588,7 +626,7 @@ class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ExportTemplat
# Prevent the creation of journal entries on unsupported models
permitted_types = ContentType.objects.filter(FeatureQuery('journaling').get_query())
if self.assigned_object_type not in permitted_types:
- raise ValidationError(f"Journaling is not supported for this object type ({self.assigned_object_type}).")
+ raise ValidationError(_("Journaling is not supported for this object type ({type}).").format(type=self.assigned_object_type))
def get_kind_color(self):
return JournalEntryKindChoices.colors.get(self.kind)
@@ -599,6 +637,7 @@ class Bookmark(models.Model):
An object bookmarked by a User.
"""
created = models.DateTimeField(
+ verbose_name=_('created'),
auto_now_add=True
)
object_type = models.ForeignKey(
@@ -637,16 +676,18 @@ class ConfigRevision(models.Model):
An atomic revision of NetBox's configuration.
"""
created = models.DateTimeField(
+ verbose_name=_('created'),
auto_now_add=True
)
comment = models.CharField(
+ verbose_name=_('comment'),
max_length=200,
blank=True
)
data = models.JSONField(
blank=True,
null=True,
- verbose_name='Configuration data'
+ verbose_name=_('Configuration data')
)
objects = RestrictedQuerySet.as_manager()