diff --git a/netbox/circuits/apps.py b/netbox/circuits/apps.py index bc0b7d87d..78b30c972 100644 --- a/netbox/circuits/apps.py +++ b/netbox/circuits/apps.py @@ -1,9 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import gettext as _ class CircuitsConfig(AppConfig): name = "circuits" - verbose_name = "Circuits" + verbose_name = _('Circuits') def ready(self): import circuits.signals diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 206dcc305..b71f91dba 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -1,5 +1,6 @@ import django_filters from django.db.models import Q +from django.utils.translation import gettext as _ from dcim.models import Region, Site from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet @@ -21,31 +22,31 @@ __all__ = ( class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='circuits__terminations__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='circuits__terminations__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='circuits__terminations__site', queryset=Site.objects.all(), - label='Site', + label=_('Site'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='circuits__terminations__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) tag = TagFilter() @@ -75,27 +76,27 @@ class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) provider_id = django_filters.ModelMultipleChoiceFilter( queryset=Provider.objects.all(), - label='Provider (ID)', + label=_('Provider (ID)'), ) provider = django_filters.ModelMultipleChoiceFilter( field_name='provider__slug', queryset=Provider.objects.all(), to_field_name='slug', - label='Provider (slug)', + label=_('Provider (slug)'), ) type_id = django_filters.ModelMultipleChoiceFilter( queryset=CircuitType.objects.all(), - label='Circuit type (ID)', + label=_('Circuit type (ID)'), ) type = django_filters.ModelMultipleChoiceFilter( field_name='type__slug', queryset=CircuitType.objects.all(), to_field_name='slug', - label='Circuit type (slug)', + label=_('Circuit type (slug)'), ) status = django_filters.MultipleChoiceFilter( choices=CircuitStatusChoices, @@ -104,26 +105,26 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr site_id = django_filters.ModelMultipleChoiceFilter( field_name='terminations__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='terminations__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='terminations__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='terminations__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) tag = TagFilter() @@ -147,21 +148,21 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr class CircuitTerminationFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) circuit_id = django_filters.ModelMultipleChoiceFilter( queryset=Circuit.objects.all(), - label='Circuit', + label=_('Circuit'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) class Meta: diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 2185d1eab..37d1b6aa8 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import gettext as _ from dcim.models import Region, Site from extras.forms import ( @@ -64,30 +65,30 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi ) asn = forms.IntegerField( required=False, - label='ASN' + label=_('ASN') ) account = forms.CharField( max_length=30, required=False, - label='Account number' + label=_('Account number') ) portal_url = forms.URLField( required=False, - label='Portal' + label=_('Portal') ) noc_contact = forms.CharField( required=False, widget=SmallTextarea, - label='NOC contact' + label=_('NOC contact') ) admin_contact = forms.CharField( required=False, widget=SmallTextarea, - label='Admin contact' + label=_('Admin contact') ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -100,7 +101,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Provider q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -123,7 +124,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm): ) asn = forms.IntegerField( required=False, - label='ASN' + label=_('ASN') ) tag = TagFilterField(model) @@ -149,7 +150,7 @@ class CircuitTypeCSVForm(CSVModelForm): model = CircuitType fields = CircuitType.csv_headers help_texts = { - 'name': 'Name of circuit type', + 'name': _('Name of circuit type'), } @@ -189,23 +190,23 @@ class CircuitCSVForm(CustomFieldModelCSVForm): provider = CSVModelChoiceField( queryset=Provider.objects.all(), to_field_name='name', - help_text='Assigned provider' + help_text=_('Assigned provider') ) type = CSVModelChoiceField( queryset=CircuitType.objects.all(), to_field_name='name', - help_text='Type of circuit' + help_text=_('Type of circuit') ) status = CSVChoiceField( choices=CircuitStatusChoices, required=False, - help_text='Operational status' + help_text=_('Operational status') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) class Meta: @@ -240,7 +241,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ) commit_rate = forms.IntegerField( required=False, - label='Commit rate (Kbps)' + label=_('Commit rate (Kbps)') ) description = forms.CharField( max_length=100, @@ -248,7 +249,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -264,7 +265,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) type = DynamicModelMultipleChoiceField( queryset=CircuitType.objects.all(), @@ -309,7 +310,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm commit_rate = forms.IntegerField( required=False, min_value=0, - label='Commit rate (Kbps)' + label=_('Commit rate (Kbps)') ) tag = TagFilterField(model) diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 57d41a994..dac3ffde8 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse +from django.utils.translation import gettext as _ from taggit.managers import TaggableManager from dcim.constants import CONNECTION_STATUS_CHOICES @@ -38,25 +39,25 @@ class Provider(ChangeLoggedModel, CustomFieldModel): asn = ASNField( blank=True, null=True, - verbose_name='ASN', - help_text='32-bit autonomous system number' + verbose_name=_('ASN'), + help_text=_('32-bit autonomous system number') ) account = models.CharField( max_length=30, blank=True, - verbose_name='Account number' + verbose_name=_('Account number') ) portal_url = models.URLField( blank=True, - verbose_name='Portal URL' + verbose_name=_('Portal URL') ) noc_contact = models.TextField( blank=True, - verbose_name='NOC contact' + verbose_name=_('NOC contact') ) admin_contact = models.TextField( blank=True, - verbose_name='Admin contact' + verbose_name=_('Admin contact') ) comments = models.TextField( blank=True @@ -143,7 +144,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): """ cid = models.CharField( max_length=50, - verbose_name='Circuit ID' + verbose_name=_('Circuit ID') ) provider = models.ForeignKey( to='circuits.Provider', @@ -170,7 +171,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): install_date = models.DateField( blank=True, null=True, - verbose_name='Date installed' + verbose_name=_('Date installed') ) commit_rate = models.PositiveIntegerField( blank=True, @@ -258,7 +259,7 @@ class CircuitTermination(CableTermination): term_side = models.CharField( max_length=1, choices=CircuitTerminationSideChoices, - verbose_name='Termination' + verbose_name=_('Termination') ) site = models.ForeignKey( to='dcim.Site', @@ -277,23 +278,23 @@ class CircuitTermination(CableTermination): blank=True ) port_speed = models.PositiveIntegerField( - verbose_name='Port speed (Kbps)' + verbose_name=_('Port speed (Kbps)') ) upstream_speed = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Upstream speed (Kbps)', - help_text='Upstream speed, if different from port speed' + verbose_name=_('Upstream speed (Kbps)'), + help_text=_('Upstream speed, if different from port speed') ) xconnect_id = models.CharField( max_length=50, blank=True, - verbose_name='Cross-connect ID' + verbose_name=_('Cross-connect ID') ) pp_info = models.CharField( max_length=100, blank=True, - verbose_name='Patch panel/port(s)' + verbose_name=_('Patch panel/port(s)') ) description = models.CharField( max_length=200, diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index ea17031a1..ad5d5ca66 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables from django_tables2.utils import Accessor +from django.utils.translation import gettext as _ from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, TagColumn, ToggleColumn @@ -29,7 +30,7 @@ class ProviderTable(BaseTable): name = tables.LinkColumn() circuit_count = tables.Column( accessor=Accessor('count_circuits'), - verbose_name='Circuits' + verbose_name=_('Circuits') ) tags = TagColumn( url_name='circuits:provider_list' @@ -51,7 +52,7 @@ class CircuitTypeTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() circuit_count = tables.Column( - verbose_name='Circuits' + verbose_name=_('Circuits') ) actions = tables.TemplateColumn( template_code=CIRCUITTYPE_ACTIONS, @@ -72,7 +73,7 @@ class CircuitTypeTable(BaseTable): class CircuitTable(BaseTable): pk = ToggleColumn() cid = tables.LinkColumn( - verbose_name='ID' + verbose_name=_('ID') ) provider = tables.LinkColumn( viewname='circuits:provider', @@ -85,10 +86,10 @@ class CircuitTable(BaseTable): template_code=COL_TENANT ) a_side = tables.Column( - verbose_name='A Side' + verbose_name=_('A Side') ) z_side = tables.Column( - verbose_name='Z Side' + verbose_name=_('Z Side') ) tags = TagColumn( url_name='circuits:circuit_list' diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 709d2a726..e707d466f 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -5,6 +5,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.db import transaction from django.db.models import Count, OuterRef, Subquery from django.shortcuts import get_object_or_404, redirect, render +from django.utils.translation import gettext as _ from django.views.generic import View from django_tables2 import RequestConfig @@ -250,7 +251,7 @@ def circuit_terminations_swap(request, pk): else: termination_z.term_side = 'A' termination_z.save() - messages.success(request, "Swapped terminations for circuit {}.".format(circuit)) + messages.success(request, _('Swapped terminations for circuit {}.').format(circuit)) return redirect('circuits:circuit', pk=circuit.pk) else: diff --git a/netbox/dcim/apps.py b/netbox/dcim/apps.py index 78a243f84..b0dc5cc0f 100644 --- a/netbox/dcim/apps.py +++ b/netbox/dcim/apps.py @@ -1,9 +1,9 @@ from django.apps import AppConfig - +from django.utils.translation import gettext as _ class DCIMConfig(AppConfig): name = "dcim" - verbose_name = "DCIM" + verbose_name = _('DCIM') def ready(self): diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 8c24180bb..a901a99fd 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1,5 +1,6 @@ import django_filters from django.contrib.auth.models import User +from django.utils.translation import gettext as _ from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet from tenancy.filters import TenancyFilterSet @@ -63,13 +64,13 @@ __all__ = ( class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), - label='Parent region (ID)', + label=_('Parent region (ID)'), ) parent = django_filters.ModelMultipleChoiceFilter( field_name='parent__slug', queryset=Region.objects.all(), to_field_name='slug', - label='Parent region (slug)', + label=_('Parent region (slug)'), ) class Meta: @@ -80,7 +81,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) status = django_filters.MultipleChoiceFilter( choices=SiteStatusChoices, @@ -90,14 +91,14 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat queryset=Region.objects.all(), field_name='region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) tag = TagFilter() @@ -134,34 +135,34 @@ class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) parent_id = django_filters.ModelMultipleChoiceFilter( queryset=RackGroup.objects.all(), - label='Rack group (ID)', + label=_('Rack group (ID)'), ) parent = django_filters.ModelMultipleChoiceFilter( field_name='parent__slug', queryset=RackGroup.objects.all(), to_field_name='slug', - label='Rack group (slug)', + label=_('Rack group (slug)'), ) class Meta: @@ -179,43 +180,43 @@ class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) group_id = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='group', lookup_expr='in', - label='Rack group (ID)', + label=_('Rack group (ID)'), ) group = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='group', lookup_expr='in', to_field_name='slug', - label='Rack group (slug)', + label=_('Rack group (slug)'), ) status = django_filters.MultipleChoiceFilter( choices=RackStatusChoices, @@ -223,13 +224,13 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat ) role_id = django_filters.ModelMultipleChoiceFilter( queryset=RackRole.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='role__slug', queryset=RackRole.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) serial = django_filters.CharFilter( lookup_expr='iexact' @@ -258,45 +259,45 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) rack_id = django_filters.ModelMultipleChoiceFilter( queryset=Rack.objects.all(), - label='Rack (ID)', + label=_('Rack (ID)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='rack__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='rack__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) group_id = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='rack__group', lookup_expr='in', - label='Rack group (ID)', + label=_('Rack group (ID)'), ) group = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='rack__group', lookup_expr='in', to_field_name='slug', - label='Rack group (slug)', + label=_('Rack group (slug)'), ) user_id = django_filters.ModelMultipleChoiceFilter( queryset=User.objects.all(), - label='User (ID)', + label=_('User (ID)'), ) user = django_filters.ModelMultipleChoiceFilter( field_name='user', queryset=User.objects.all(), to_field_name='username', - label='User (name)', + label=_('User (name)'), ) class Meta: @@ -324,45 +325,45 @@ class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) manufacturer_id = django_filters.ModelMultipleChoiceFilter( queryset=Manufacturer.objects.all(), - label='Manufacturer (ID)', + label=_('Manufacturer (ID)'), ) manufacturer = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer__slug', queryset=Manufacturer.objects.all(), to_field_name='slug', - label='Manufacturer (slug)', + label=_('Manufacturer (slug)'), ) console_ports = django_filters.BooleanFilter( method='_console_ports', - label='Has console ports', + label=_('Has console ports'), ) console_server_ports = django_filters.BooleanFilter( method='_console_server_ports', - label='Has console server ports', + label=_('Has console server ports'), ) power_ports = django_filters.BooleanFilter( method='_power_ports', - label='Has power ports', + label=_('Has power ports'), ) power_outlets = django_filters.BooleanFilter( method='_power_outlets', - label='Has power outlets', + label=_('Has power outlets'), ) interfaces = django_filters.BooleanFilter( method='_interfaces', - label='Has interfaces', + label=_('Has interfaces'), ) pass_through_ports = django_filters.BooleanFilter( method='_pass_through_ports', - label='Has pass-through ports', + label=_('Has pass-through ports'), ) device_bays = django_filters.BooleanFilter( method='_device_bays', - label='Has device bays', + label=_('Has device bays'), ) tag = TagFilter() @@ -411,7 +412,7 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet): devicetype_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceType.objects.all(), field_name='device_type_id', - label='Device type (ID)', + label=_('Device type (ID)'), ) @@ -482,13 +483,13 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet): manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer', queryset=Manufacturer.objects.all(), - label='Manufacturer (ID)', + label=_('Manufacturer (ID)'), ) manufacturer = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer__slug', queryset=Manufacturer.objects.all(), to_field_name='slug', - label='Manufacturer (slug)', + label=_('Manufacturer (slug)'), ) class Meta: @@ -505,87 +506,87 @@ class DeviceFilterSet( ): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='device_type__manufacturer', queryset=Manufacturer.objects.all(), - label='Manufacturer (ID)', + label=_('Manufacturer (ID)'), ) manufacturer = django_filters.ModelMultipleChoiceFilter( field_name='device_type__manufacturer__slug', queryset=Manufacturer.objects.all(), to_field_name='slug', - label='Manufacturer (slug)', + label=_('Manufacturer (slug)'), ) device_type_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceType.objects.all(), - label='Device type (ID)', + label=_('Device type (ID)'), ) role_id = django_filters.ModelMultipleChoiceFilter( field_name='device_role_id', queryset=DeviceRole.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='device_role__slug', queryset=DeviceRole.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) platform_id = django_filters.ModelMultipleChoiceFilter( queryset=Platform.objects.all(), - label='Platform (ID)', + label=_('Platform (ID)'), ) platform = django_filters.ModelMultipleChoiceFilter( field_name='platform__slug', queryset=Platform.objects.all(), to_field_name='slug', - label='Platform (slug)', + label=_('Platform (slug)'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) rack_group_id = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='rack__group', lookup_expr='in', - label='Rack group (ID)', + label=_('Rack group (ID)'), ) rack_id = django_filters.ModelMultipleChoiceFilter( field_name='rack', queryset=Rack.objects.all(), - label='Rack (ID)', + label=_('Rack (ID)'), ) cluster_id = django_filters.ModelMultipleChoiceFilter( queryset=Cluster.objects.all(), - label='VM cluster (ID)', + label=_('VM cluster (ID)'), ) model = django_filters.ModelMultipleChoiceFilter( field_name='device_type__slug', queryset=DeviceType.objects.all(), to_field_name='slug', - label='Device model (slug)', + label=_('Device model (slug)'), ) status = django_filters.MultipleChoiceFilter( choices=DeviceStatusChoices, @@ -593,55 +594,55 @@ class DeviceFilterSet( ) is_full_depth = django_filters.BooleanFilter( field_name='device_type__is_full_depth', - label='Is full depth', + label=_('Is full depth'), ) mac_address = MultiValueMACAddressFilter( field_name='interfaces__mac_address', - label='MAC address', + label=_('MAC address'), ) serial = django_filters.CharFilter( lookup_expr='iexact' ) has_primary_ip = django_filters.BooleanFilter( method='_has_primary_ip', - label='Has a primary IP', + label=_('Has a primary IP'), ) virtual_chassis_id = django_filters.ModelMultipleChoiceFilter( field_name='virtual_chassis', queryset=VirtualChassis.objects.all(), - label='Virtual chassis (ID)', + label=_('Virtual chassis (ID)'), ) virtual_chassis_member = django_filters.BooleanFilter( method='_virtual_chassis_member', - label='Is a virtual chassis member' + label=_('Is a virtual chassis member') ) console_ports = django_filters.BooleanFilter( method='_console_ports', - label='Has console ports', + label=_('Has console ports'), ) console_server_ports = django_filters.BooleanFilter( method='_console_server_ports', - label='Has console server ports', + label=_('Has console server ports'), ) power_ports = django_filters.BooleanFilter( method='_power_ports', - label='Has power ports', + label=_('Has power ports'), ) power_outlets = django_filters.BooleanFilter( method='_power_outlets', - label='Has power outlets', + label=_('Has power outlets'), ) interfaces = django_filters.BooleanFilter( method='_interfaces', - label='Has interfaces', + label=_('Has interfaces'), ) pass_through_ports = django_filters.BooleanFilter( method='_pass_through_ports', - label='Has pass-through ports', + label=_('Has pass-through ports'), ) device_bays = django_filters.BooleanFilter( method='_device_bays', - label='Has device bays', + label=_('Has device bays'), ) tag = TagFilter() @@ -703,41 +704,41 @@ class DeviceFilterSet( class DeviceComponentFilterSet(django_filters.FilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='device__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='device__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='device__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='device__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), - label='Device (ID)', + label=_('Device (ID)'), ) device = django_filters.ModelMultipleChoiceFilter( field_name='device__name', queryset=Device.objects.all(), to_field_name='name', - label='Device (name)', + label=_('Device (name)'), ) tag = TagFilter() @@ -817,19 +818,19 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet): class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) # Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis # members device = MultiValueCharFilter( method='filter_device', field_name='name', - label='Device', + label=_('Device'), ) device_id = MultiValueNumberFilter( method='filter_device_id', field_name='pk', - label='Device (ID)', + label=_('Device (ID)'), ) cabled = django_filters.BooleanFilter( field_name='cable', @@ -838,22 +839,22 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet): ) kind = django_filters.CharFilter( method='filter_kind', - label='Kind of interface', + label=_('Kind of interface'), ) lag_id = django_filters.ModelMultipleChoiceFilter( field_name='lag', queryset=Interface.objects.all(), - label='LAG interface (ID)', + label=_('LAG interface (ID)'), ) mac_address = MultiValueMACAddressFilter() tag = TagFilter() vlan_id = django_filters.CharFilter( method='filter_vlan_id', - label='Assigned VLAN' + label=_('Assigned VLAN') ) vlan = django_filters.CharFilter( method='filter_vlan', - label='Assigned VID' + label=_('Assigned VID') ) type = django_filters.MultipleChoiceFilter( choices=InterfaceTypeChoices, @@ -946,54 +947,54 @@ class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet): class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='device__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='device__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='device__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='device__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) device_id = django_filters.ModelChoiceFilter( queryset=Device.objects.all(), - label='Device (ID)', + label=_('Device (ID)'), ) device = django_filters.ModelChoiceFilter( queryset=Device.objects.all(), to_field_name='name', - label='Device (name)', + label=_('Device (name)'), ) parent_id = django_filters.ModelMultipleChoiceFilter( queryset=InventoryItem.objects.all(), - label='Parent inventory item (ID)', + label=_('Parent inventory item (ID)'), ) manufacturer_id = django_filters.ModelMultipleChoiceFilter( queryset=Manufacturer.objects.all(), - label='Manufacturer (ID)', + label=_('Manufacturer (ID)'), ) manufacturer = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer__slug', queryset=Manufacturer.objects.all(), to_field_name='slug', - label='Manufacturer (slug)', + label=_('Manufacturer (slug)'), ) serial = django_filters.CharFilter( lookup_expr='iexact' @@ -1019,42 +1020,42 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): class VirtualChassisFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='master__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='master__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='master__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='master__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) tenant_id = django_filters.ModelMultipleChoiceFilter( field_name='master__tenant', queryset=Tenant.objects.all(), - label='Tenant (ID)', + label=_('Tenant (ID)'), ) tenant = django_filters.ModelMultipleChoiceFilter( field_name='master__tenant__slug', queryset=Tenant.objects.all(), to_field_name='slug', - label='Tenant (slug)', + label=_('Tenant (slug)'), ) tag = TagFilter() @@ -1075,7 +1076,7 @@ class VirtualChassisFilterSet(BaseFilterSet): class CableFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) type = django_filters.MultipleChoiceFilter( choices=CableTypeChoices @@ -1138,7 +1139,7 @@ class CableFilterSet(BaseFilterSet): class ConsoleConnectionFilterSet(BaseFilterSet): site = django_filters.CharFilter( method='filter_site', - label='Site (slug)', + label=_('Site (slug)'), ) device_id = MultiValueNumberFilter( method='filter_device' @@ -1169,7 +1170,7 @@ class ConsoleConnectionFilterSet(BaseFilterSet): class PowerConnectionFilterSet(BaseFilterSet): site = django_filters.CharFilter( method='filter_site', - label='Site (slug)', + label=_('Site (slug)'), ) device_id = MultiValueNumberFilter( method='filter_device' @@ -1200,7 +1201,7 @@ class PowerConnectionFilterSet(BaseFilterSet): class InterfaceConnectionFilterSet(BaseFilterSet): site = django_filters.CharFilter( method='filter_site', - label='Site (slug)', + label=_('Site (slug)'), ) device_id = MultiValueNumberFilter( method='filter_device' @@ -1234,36 +1235,36 @@ class InterfaceConnectionFilterSet(BaseFilterSet): class PowerPanelFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) rack_group_id = TreeNodeMultipleChoiceFilter( queryset=RackGroup.objects.all(), field_name='rack_group', lookup_expr='in', - label='Rack group (ID)', + label=_('Rack group (ID)'), ) class Meta: @@ -1282,40 +1283,40 @@ class PowerPanelFilterSet(BaseFilterSet): class PowerFeedFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='power_panel__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='power_panel__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='power_panel__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='power_panel__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site name (slug)', + label=_('Site name (slug)'), ) power_panel_id = django_filters.ModelMultipleChoiceFilter( queryset=PowerPanel.objects.all(), - label='Power panel (ID)', + label=_('Power panel (ID)'), ) rack_id = django_filters.ModelMultipleChoiceFilter( field_name='rack', queryset=Rack.objects.all(), - label='Rack (ID)', + label=_('Rack (ID)'), ) tag = TagFilter() diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4de81ac54..9c1873b3d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.forms.array import SimpleArrayField from django.core.exceptions import ObjectDoesNotExist from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from mptt.forms import TreeNodeChoiceField from netaddr import EUI from netaddr.core import AddrFormatError @@ -65,7 +66,7 @@ class DeviceComponentFilterForm(BootstrapMixin, forms.Form): ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -92,7 +93,7 @@ class DeviceComponentFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) @@ -136,7 +137,7 @@ class BulkRenameForm(forms.Form): use_regex = forms.BooleanField( required=False, initial=True, - label='Use regular expressions' + label=_('Use regular expressions') ) def clean(self): @@ -197,7 +198,7 @@ class RegionCSVForm(CSVModelForm): queryset=Region.objects.all(), required=False, to_field_name='name', - help_text='Name of parent region' + help_text=_('Name of parent region') ) class Meta: @@ -209,7 +210,7 @@ class RegionFilterForm(BootstrapMixin, forms.Form): model = Site q = forms.CharField( required=False, - label='Search' + label=_('Search') ) @@ -267,19 +268,19 @@ class SiteCSVForm(CustomFieldModelCSVForm): status = CSVChoiceField( choices=SiteStatusChoices, required=False, - help_text='Operational status' + help_text=_('Operational status') ) region = CSVModelChoiceField( queryset=Region.objects.all(), required=False, to_field_name='name', - help_text='Assigned region' + help_text=_('Assigned region') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) class Meta: @@ -316,7 +317,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor min_value=BGP_ASN_MIN, max_value=BGP_ASN_MAX, required=False, - label='ASN' + label=_('ASN') ) description = forms.CharField( max_length=100, @@ -339,7 +340,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): field_order = ['q', 'status', 'region', 'tenant_group', 'tenant'] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) status = forms.MultipleChoiceField( choices=SiteStatusChoices, @@ -387,15 +388,15 @@ class RackGroupCSVForm(CSVModelForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) parent = CSVModelChoiceField( queryset=RackGroup.objects.all(), required=False, to_field_name='name', - help_text='Parent rack group', + help_text=_('Parent rack group'), error_messages={ - 'invalid_choice': 'Rack group not found.', + 'invalid_choice': _('Rack group not found.'), } ) @@ -524,32 +525,32 @@ class RackCSVForm(CustomFieldModelCSVForm): queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Name of assigned tenant' + help_text=_('Name of assigned tenant') ) status = CSVChoiceField( choices=RackStatusChoices, required=False, - help_text='Operational status' + help_text=_('Operational status') ) role = CSVModelChoiceField( queryset=RackRole.objects.all(), required=False, to_field_name='name', - help_text='Name of assigned role' + help_text=_('Name of assigned role') ) type = CSVChoiceField( choices=RackTypeChoices, required=False, - help_text='Rack type' + help_text=_('Rack type') ) width = forms.ChoiceField( choices=RackWidthChoices, - help_text='Rail-to-rail width (in inches)' + help_text=_('Rail-to-rail width (in inches)') ) outer_unit = CSVChoiceField( choices=RackDimensionUnitChoices, required=False, - help_text='Unit for outer dimensions' + help_text=_('Unit for outer dimensions') ) class Meta: @@ -601,7 +602,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor serial = forms.CharField( max_length=50, required=False, - label='Serial Number' + label=_('Serial Number') ) asset_tag = forms.CharField( max_length=50, @@ -619,12 +620,12 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ) u_height = forms.IntegerField( required=False, - label='Height (U)' + label=_('Height (U)') ) desc_units = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect, - label='Descending units' + label=_('Descending units') ) outer_width = forms.IntegerField( required=False, @@ -641,7 +642,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -655,7 +656,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant'] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -684,7 +685,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): 'site' ), required=False, - label='Rack group', + label=_('Rack group'), widget=APISelectMultiple( null_option=True ) @@ -714,7 +715,7 @@ class RackElevationFilterForm(RackFilterForm): field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant'] id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), - label='Rack', + label=_('Rack'), required=False, widget=APISelectMultiple( display_field='display_name', @@ -758,7 +759,7 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm): ) units = NumericArrayField( base_field=forms.IntegerField(), - help_text="Comma-separated list of numeric unit IDs. A range may be specified using a hyphen." + help_text=_('Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.') ) user = forms.ModelChoiceField( queryset=User.objects.order_by( @@ -778,29 +779,29 @@ class RackReservationCSVForm(CSVModelForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Parent site' + help_text=_('Parent site') ) rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, - help_text="Rack's group (if any)" + help_text=_("Rack's group (if any)") ) rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', - help_text='Rack' + help_text=_('Rack') ) units = SimpleArrayField( base_field=forms.IntegerField(), required=True, - help_text='Comma-separated list of individual unit numbers' + help_text=_('Comma-separated list of individual unit numbers') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) class Meta: @@ -853,7 +854,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm): field_order = ['q', 'site', 'group_id', 'tenant_group', 'tenant'] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) site = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -866,7 +867,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm): group_id = DynamicModelMultipleChoiceField( queryset=RackGroup.objects.prefetch_related('site'), required=False, - label='Rack group', + label=_('Rack group'), widget=APISelectMultiple( null_option=True, ) @@ -951,7 +952,7 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE is_full_depth = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect(), - label='Is full depth' + label=_('Is full depth') ) class Meta: @@ -962,7 +963,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): model = DeviceType q = forms.CharField( required=False, - label='Search' + label=_('Search') ) manufacturer = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -979,42 +980,42 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): ) console_ports = forms.NullBooleanField( required=False, - label='Has console ports', + label=_('Has console ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_server_ports = forms.NullBooleanField( required=False, - label='Has console server ports', + label=_('Has console server ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_ports = forms.NullBooleanField( required=False, - label='Has power ports', + label=_('Has power ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_outlets = forms.NullBooleanField( required=False, - label='Has power outlets', + label=_('Has power outlets'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) interfaces = forms.NullBooleanField( required=False, - label='Has interfaces', + label=_('Has interfaces'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) pass_through_ports = forms.NullBooleanField( required=False, - label='Has pass-through ports', + label=_('Has pass-through ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) @@ -1046,7 +1047,7 @@ class ComponentTemplateCreateForm(BootstrapMixin, forms.Form): ) ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) @@ -1138,12 +1139,12 @@ class PowerPortTemplateCreateForm(ComponentTemplateCreateForm): maximum_draw = forms.IntegerField( min_value=1, required=False, - help_text="Maximum power draw (watts)" + help_text=_('Maximum power draw (watts)') ) allocated_draw = forms.IntegerField( min_value=1, required=False, - help_text="Allocated power draw (watts)" + help_text=_('Allocated power draw (watts)') ) @@ -1160,12 +1161,12 @@ class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): maximum_draw = forms.IntegerField( min_value=1, required=False, - help_text="Maximum power draw (watts)" + help_text=_('Maximum power draw (watts)') ) allocated_draw = forms.IntegerField( min_value=1, required=False, - help_text="Allocated power draw (watts)" + help_text=_('Allocated power draw (watts)') ) class Meta: @@ -1282,7 +1283,7 @@ class InterfaceTemplateCreateForm(ComponentTemplateCreateForm): ) mgmt_only = forms.BooleanField( required=False, - label='Management only' + label=_('Management only') ) @@ -1299,7 +1300,7 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm): mgmt_only = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect, - label='Management only' + label=_('Management only') ) class Meta: @@ -1336,8 +1337,8 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm): ) rear_port_set = forms.MultipleChoiceField( choices=[], - label='Rear ports', - help_text='Select one rear port assignment for each front port being created.', + label=_('Rear ports'), + help_text=_('Select one rear port assignment for each front port being created.'), ) def __init__(self, *args, **kwargs): @@ -1423,7 +1424,7 @@ class RearPortTemplateCreateForm(ComponentTemplateCreateForm): min_value=REARPORT_POSITIONS_MIN, max_value=REARPORT_POSITIONS_MAX, initial=1, - help_text='The number of front ports which may be mapped to each rear port' + help_text=_('The number of front ports which may be mapped to each rear port') ) @@ -1641,7 +1642,7 @@ class PlatformCSVForm(CSVModelForm): queryset=Manufacturer.objects.all(), required=False, to_field_name='name', - help_text='Limit platform assignments to this manufacturer' + help_text=_('Limit platform assignments to this manufacturer') ) class Meta: @@ -1672,7 +1673,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): position = forms.TypedChoiceField( required=False, empty_value=None, - help_text="The lowest-numbered unit occupied by the device", + help_text=_('The lowest-numbered unit occupied by the device'), widget=APISelect( api_url='/api/dcim/racks/{{rack}}/elevation/', disabled_indicator='device' @@ -1851,39 +1852,39 @@ class BaseDeviceCSVForm(CustomFieldModelCSVForm): device_role = CSVModelChoiceField( queryset=DeviceRole.objects.all(), to_field_name='name', - help_text='Assigned role' + help_text=_('Assigned role') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) manufacturer = CSVModelChoiceField( queryset=Manufacturer.objects.all(), to_field_name='name', - help_text='Device type manufacturer' + help_text=_('Device type manufacturer') ) device_type = CSVModelChoiceField( queryset=DeviceType.objects.all(), to_field_name='model', - help_text='Device type model' + help_text=_('Device type model') ) platform = CSVModelChoiceField( queryset=Platform.objects.all(), required=False, to_field_name='name', - help_text='Assigned platform' + help_text=_('Assigned platform') ) status = CSVChoiceField( choices=DeviceStatusChoices, - help_text='Operational status' + help_text=_('Operational status') ) cluster = CSVModelChoiceField( queryset=Cluster.objects.all(), to_field_name='name', required=False, - help_text='Virtualization cluster' + help_text=_('Virtualization cluster') ) class Meta: @@ -1904,24 +1905,24 @@ class DeviceCSVForm(BaseDeviceCSVForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, - help_text="Rack's group (if any)" + help_text=_("Rack's group (if any)") ) rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', required=False, - help_text="Assigned rack" + help_text=_('Assigned rack') ) face = CSVChoiceField( choices=DeviceFaceChoices, required=False, - help_text='Mounted rack face' + help_text=_('Mounted rack face') ) class Meta(BaseDeviceCSVForm.Meta): @@ -1951,12 +1952,12 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm): parent = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', - help_text='Parent device' + help_text=_('Parent device') ) device_bay = CSVModelChoiceField( queryset=DeviceBay.objects.all(), to_field_name='name', - help_text='Device bay in which this device is installed' + help_text=_('Device bay in which this device is installed') ) class Meta(BaseDeviceCSVForm.Meta): @@ -2021,7 +2022,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF serial = forms.CharField( max_length=50, required=False, - label='Serial Number' + label=_('Serial Number') ) class Meta: @@ -2038,7 +2039,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -2066,7 +2067,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt rack_group_id = DynamicModelMultipleChoiceField( queryset=RackGroup.objects.all(), required=False, - label='Rack group', + label=_('Rack group'), widget=APISelectMultiple( filter_for={ 'rack_id': 'group_id', @@ -2076,7 +2077,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), required=False, - label='Rack', + label=_('Rack'), widget=APISelectMultiple( null_option=True, ) @@ -2092,7 +2093,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label='Manufacturer', + label=_('Manufacturer'), widget=APISelectMultiple( filter_for={ 'device_type_id': 'manufacturer_id', @@ -2102,7 +2103,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt device_type_id = DynamicModelMultipleChoiceField( queryset=DeviceType.objects.all(), required=False, - label='Model', + label=_('Model'), widget=APISelectMultiple( display_field="model", ) @@ -2123,60 +2124,60 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt ) mac_address = forms.CharField( required=False, - label='MAC address' + label=_('MAC address') ) has_primary_ip = forms.NullBooleanField( required=False, - label='Has a primary IP', + label=_('Has a primary IP'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) virtual_chassis_member = forms.NullBooleanField( required=False, - label='Virtual chassis member', + label=_('Virtual chassis member'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_ports = forms.NullBooleanField( required=False, - label='Has console ports', + label=_('Has console ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_server_ports = forms.NullBooleanField( required=False, - label='Has console server ports', + label=_('Has console server ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_ports = forms.NullBooleanField( required=False, - label='Has power ports', + label=_('Has power ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_outlets = forms.NullBooleanField( required=False, - label='Has power outlets', + label=_('Has power outlets'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) interfaces = forms.NullBooleanField( required=False, - label='Has interfaces', + label=_('Has interfaces'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) pass_through_ports = forms.NullBooleanField( required=False, - label='Has pass-through ports', + label=_('Has pass-through ports'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) @@ -2196,7 +2197,7 @@ class ComponentCreateForm(BootstrapMixin, forms.Form): queryset=Device.objects.all() ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) @@ -2206,7 +2207,7 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form): widget=forms.MultipleHiddenInput() ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) def clean_tags(self): @@ -2428,12 +2429,12 @@ class PowerPortCreateForm(ComponentCreateForm): maximum_draw = forms.IntegerField( min_value=1, required=False, - help_text="Maximum draw in watts" + help_text=_('Maximum draw in watts') ) allocated_draw = forms.IntegerField( min_value=1, required=False, - help_text="Allocated draw in watts" + help_text=_('Allocated draw in watts') ) description = forms.CharField( max_length=100, @@ -2618,12 +2619,12 @@ class PowerOutletCSVForm(CSVModelForm): queryset=PowerPort.objects.all(), required=False, to_field_name='name', - help_text='Local power port which feeds this outlet' + help_text=_('Local power port which feeds this outlet') ) feed_leg = CSVChoiceField( choices=PowerOutletFeedLegChoices, required=False, - help_text='Electrical phase (for three-phase circuits)' + help_text=_('Electrical phase (for three-phase circuits)') ) class Meta: @@ -2673,7 +2674,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm): ) mac_address = forms.CharField( required=False, - label='MAC address' + label=_('MAC address') ) tag = TagFilterField(model) @@ -2682,7 +2683,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): untagged_vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), required=False, - label='Untagged VLAN', + label=_('Untagged VLAN'), widget=APISelect( display_field='display_name', full=True, @@ -2694,7 +2695,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): tagged_vlans = DynamicModelMultipleChoiceField( queryset=VLAN.objects.all(), required=False, - label='Tagged VLANs', + label=_('Tagged VLANs'), widget=APISelectMultiple( display_field='display_name', full=True, @@ -2720,7 +2721,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): 'mode': StaticSelect2(), } labels = { - 'mode': '802.1Q Mode', + 'mode': _('802.1Q Mode'), } help_texts = { 'mode': INTERFACE_MODE_HELP_TEXT, @@ -2757,23 +2758,23 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): lag = forms.ModelChoiceField( queryset=Interface.objects.all(), required=False, - label='Parent LAG', + label=_('Parent LAG'), widget=StaticSelect2(), ) mtu = forms.IntegerField( required=False, min_value=INTERFACE_MTU_MIN, max_value=INTERFACE_MTU_MAX, - label='MTU' + label=_('MTU') ) mac_address = forms.CharField( required=False, - label='MAC Address' + label=_('MAC Address') ) mgmt_only = forms.BooleanField( required=False, - label='Management only', - help_text='This interface is used only for out-of-band management' + label=_('Management only'), + help_text=_('This interface is used only for out-of-band management') ) description = forms.CharField( max_length=100, @@ -2938,16 +2939,16 @@ class InterfaceCSVForm(CSVModelForm): queryset=Interface.objects.all(), required=False, to_field_name='name', - help_text='Parent LAG interface' + help_text=_('Parent LAG interface') ) type = CSVChoiceField( choices=InterfaceTypeChoices, - help_text='Physical medium' + help_text=_('Physical medium') ) mode = CSVChoiceField( choices=InterfaceModeChoices, required=False, - help_text='IEEE 802.1Q operational mode (for L2 interfaces)' + help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)') ) class Meta: @@ -3029,8 +3030,8 @@ class FrontPortCreateForm(ComponentCreateForm): ) rear_port_set = forms.MultipleChoiceField( choices=[], - label='Rear ports', - help_text='Select one rear port assignment for each front port being created.', + label=_('Rear ports'), + help_text=_('Select one rear port assignment for each front port being created.'), ) description = forms.CharField( required=False @@ -3129,18 +3130,18 @@ class FrontPortCSVForm(CSVModelForm): rear_port = CSVModelChoiceField( queryset=RearPort.objects.all(), to_field_name='name', - help_text='Corresponding rear port' + help_text=_('Corresponding rear port') ) type = CSVChoiceField( choices=PortTypeChoices, - help_text='Physical medium classification' + help_text=_('Physical medium classification') ) class Meta: model = FrontPort fields = FrontPort.csv_headers help_texts = { - 'rear_port_position': 'Mapped position on corresponding rear port', + 'rear_port_position': _('Mapped position on corresponding rear port'), } def __init__(self, *args, **kwargs): @@ -3205,7 +3206,7 @@ class RearPortCreateForm(ComponentCreateForm): min_value=REARPORT_POSITIONS_MIN, max_value=REARPORT_POSITIONS_MAX, initial=1, - help_text='The number of front ports which may be mapped to each rear port' + help_text=_('The number of front ports which may be mapped to each rear port') ) description = forms.CharField( required=False @@ -3256,7 +3257,7 @@ class RearPortCSVForm(CSVModelForm): to_field_name='name' ) type = CSVChoiceField( - help_text='Physical medium classification', + help_text=_('Physical medium classification'), choices=PortTypeChoices, ) @@ -3264,7 +3265,7 @@ class RearPortCSVForm(CSVModelForm): model = RearPort fields = RearPort.csv_headers help_texts = { - 'positions': 'Number of front ports which may be mapped' + 'positions': _('Number of front ports which may be mapped') } @@ -3301,8 +3302,8 @@ class DeviceBayCreateForm(ComponentCreateForm): class PopulateDeviceBayForm(BootstrapMixin, forms.Form): installed_device = forms.ModelChoiceField( queryset=Device.objects.all(), - label='Child Device', - help_text="Child devices must first be created and assigned to the site/rack of the parent device.", + label=_('Child Device'), + help_text=_('Child devices must first be created and assigned to the site/rack of the parent device.'), widget=StaticSelect2(), ) @@ -3361,9 +3362,9 @@ class DeviceBayCSVForm(CSVModelForm): queryset=Device.objects.all(), required=False, to_field_name='name', - help_text='Child device installed within this bay', + help_text=_('Child device installed within this bay'), error_messages={ - 'invalid_choice': 'Child device not found.', + 'invalid_choice': _('Child device not found.'), } ) @@ -3408,7 +3409,7 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm): """ termination_b_site = DynamicModelChoiceField( queryset=Site.objects.all(), - label='Site', + label=_('Site'), required=False, widget=APISelect( filter_for={ @@ -3419,7 +3420,7 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm): ) termination_b_rack = DynamicModelChoiceField( queryset=Rack.objects.all(), - label='Rack', + label=_('Rack'), required=False, widget=APISelect( filter_for={ @@ -3432,7 +3433,7 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm): ) termination_b_device = DynamicModelChoiceField( queryset=Device.objects.all(), - label='Device', + label=_('Device'), required=False, widget=APISelect( display_field='display_name', @@ -3457,7 +3458,7 @@ class ConnectCableToDeviceForm(BootstrapMixin, forms.ModelForm): class ConnectCableToConsolePortForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/console-ports/', disabled_indicator='cable', @@ -3467,7 +3468,7 @@ class ConnectCableToConsolePortForm(ConnectCableToDeviceForm): class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/console-server-ports/', disabled_indicator='cable', @@ -3477,7 +3478,7 @@ class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm): class ConnectCableToPowerPortForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/power-ports/', disabled_indicator='cable', @@ -3487,7 +3488,7 @@ class ConnectCableToPowerPortForm(ConnectCableToDeviceForm): class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/power-outlets/', disabled_indicator='cable', @@ -3497,7 +3498,7 @@ class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm): class ConnectCableToInterfaceForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/interfaces/', disabled_indicator='cable', @@ -3510,7 +3511,7 @@ class ConnectCableToInterfaceForm(ConnectCableToDeviceForm): class ConnectCableToFrontPortForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/front-ports/', disabled_indicator='cable', @@ -3520,7 +3521,7 @@ class ConnectCableToFrontPortForm(ConnectCableToDeviceForm): class ConnectCableToRearPortForm(ConnectCableToDeviceForm): termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/rear-ports/', disabled_indicator='cable', @@ -3531,7 +3532,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm): class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): termination_b_provider = DynamicModelChoiceField( queryset=Provider.objects.all(), - label='Provider', + label=_('Provider'), required=False, widget=APISelect( filter_for={ @@ -3541,7 +3542,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): ) termination_b_site = DynamicModelChoiceField( queryset=Site.objects.all(), - label='Site', + label=_('Site'), required=False, widget=APISelect( filter_for={ @@ -3551,7 +3552,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): ) termination_b_circuit = DynamicModelChoiceField( queryset=Circuit.objects.all(), - label='Circuit', + label=_('Circuit'), widget=APISelect( display_field='cid', filter_for={ @@ -3560,7 +3561,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): ) ) termination_b_id = forms.IntegerField( - label='Side', + label=_('Side'), widget=APISelect( api_url='/api/circuits/circuit-terminations/', disabled_indicator='cable', @@ -3580,7 +3581,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, forms.ModelForm): class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm): termination_b_site = DynamicModelChoiceField( queryset=Site.objects.all(), - label='Site', + label=_('Site'), required=False, widget=APISelect( display_field='cid', @@ -3592,7 +3593,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm): ) termination_b_rackgroup = DynamicModelChoiceField( queryset=RackGroup.objects.all(), - label='Rack Group', + label=_('Rack Group'), required=False, widget=APISelect( display_field='cid', @@ -3603,7 +3604,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm): ) termination_b_powerpanel = DynamicModelChoiceField( queryset=PowerPanel.objects.all(), - label='Power Panel', + label=_('Power Panel'), required=False, widget=APISelect( filter_for={ @@ -3612,7 +3613,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm): ) ) termination_b_id = forms.IntegerField( - label='Name', + label=_('Name'), widget=APISelect( api_url='/api/dcim/power-feeds/', ) @@ -3640,7 +3641,7 @@ class CableForm(BootstrapMixin, forms.ModelForm): } error_messages = { 'length': { - 'max_value': 'Maximum length is 32767 (any unit)' + 'max_value': _('Maximum length is 32767 (any unit)') } } @@ -3650,49 +3651,49 @@ class CableCSVForm(CSVModelForm): side_a_device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', - help_text='Side A device' + help_text=_('Side A device') ) side_a_type = CSVModelChoiceField( queryset=ContentType.objects.all(), limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', - help_text='Side A type' + help_text=_('Side A type') ) side_a_name = forms.CharField( - help_text='Side A component name' + help_text=_('Side A component name') ) # Termination B side_b_device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', - help_text='Side B device' + help_text=_('Side B device') ) side_b_type = CSVModelChoiceField( queryset=ContentType.objects.all(), limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', - help_text='Side B type' + help_text=_('Side B type') ) side_b_name = forms.CharField( - help_text='Side B component name' + help_text=_('Side B component name') ) # Cable attributes status = CSVChoiceField( choices=CableStatusChoices, required=False, - help_text='Connection status' + help_text=_('Connection status') ) type = CSVChoiceField( choices=CableTypeChoices, required=False, - help_text='Physical medium classification' + help_text=_('Physical medium classification') ) length_unit = CSVChoiceField( choices=CableLengthUnitChoices, required=False, - help_text='Length unit' + help_text=_('Length unit') ) class Meta: @@ -3821,7 +3822,7 @@ class CableFilterForm(BootstrapMixin, forms.Form): model = Cable q = forms.CharField( required=False, - label='Search' + label=_('Search') ) site = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -3849,7 +3850,7 @@ class CableFilterForm(BootstrapMixin, forms.Form): rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), required=False, - label='Rack', + label=_('Rack'), widget=APISelectMultiple( null_option=True, filter_for={ @@ -3875,7 +3876,7 @@ class CableFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) @@ -3898,7 +3899,7 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) @@ -3917,7 +3918,7 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) @@ -3936,7 +3937,7 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) @@ -3968,7 +3969,7 @@ class InventoryItemCreateForm(BootstrapMixin, forms.Form): queryset=Device.objects.prefetch_related('device_type__manufacturer') ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), @@ -3977,7 +3978,7 @@ class InventoryItemCreateForm(BootstrapMixin, forms.Form): part_id = forms.CharField( max_length=50, required=False, - label='Part ID' + label=_('Part ID') ) serial = forms.CharField( max_length=50, @@ -4025,7 +4026,7 @@ class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm): part_id = forms.CharField( max_length=50, required=False, - label='Part ID' + label=_('Part ID') ) description = forms.CharField( max_length=100, @@ -4042,7 +4043,7 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form): model = InventoryItem q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -4069,7 +4070,7 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form): device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, - label='Device' + label=_('Device') ) manufacturer = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -4138,8 +4139,8 @@ class DeviceVCMembershipForm(forms.ModelForm): 'vc_position', 'vc_priority', ] labels = { - 'vc_position': 'Position', - 'vc_priority': 'Priority', + 'vc_position': _('Position'), + 'vc_priority': _('Priority'), } def __init__(self, validate_vc_position=False, *args, **kwargs): @@ -4228,7 +4229,7 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm): model = VirtualChassis q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -4302,7 +4303,7 @@ class PowerPanelCSVForm(CSVModelForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Name of parent site' + help_text=_('Name of parent site') ) rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), @@ -4353,7 +4354,7 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm): model = PowerPanel q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -4380,7 +4381,7 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm): rack_group_id = DynamicModelMultipleChoiceField( queryset=RackGroup.objects.all(), required=False, - label='Rack group (ID)', + label=_('Rack group (ID)'), widget=APISelectMultiple( null_option=True, ) @@ -4440,44 +4441,44 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) power_panel = CSVModelChoiceField( queryset=PowerPanel.objects.all(), to_field_name='name', - help_text='Upstream power panel' + help_text=_('Upstream power panel') ) rack_group = CSVModelChoiceField( queryset=RackGroup.objects.all(), to_field_name='name', required=False, - help_text="Rack's group (if any)" + help_text=_("Rack's group (if any)") ) rack = CSVModelChoiceField( queryset=Rack.objects.all(), to_field_name='name', required=False, - help_text='Rack' + help_text=_('Rack') ) status = CSVChoiceField( choices=PowerFeedStatusChoices, required=False, - help_text='Operational status' + help_text=_('Operational status') ) type = CSVChoiceField( choices=PowerFeedTypeChoices, required=False, - help_text='Primary or redundant' + help_text=_('Primary or redundant') ) supply = CSVChoiceField( choices=PowerFeedSupplyChoices, required=False, - help_text='Supply type (AC/DC)' + help_text=_('Supply type (AC/DC)') ) phase = CSVChoiceField( choices=PowerFeedPhaseChoices, required=False, - help_text='Single or three-phase' + help_text=_('Single or three-phase') ) class Meta: @@ -4558,7 +4559,7 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -4571,7 +4572,7 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): model = PowerFeed q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -4599,7 +4600,7 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): power_panel_id = DynamicModelMultipleChoiceField( queryset=PowerPanel.objects.all(), required=False, - label='Power panel', + label=_('Power panel'), widget=APISelectMultiple( null_option=True, ) @@ -4607,7 +4608,7 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), required=False, - label='Rack', + label=_('Rack'), widget=APISelectMultiple( null_option=True, ) diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index ef5b07aca..15b528e6a 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -13,6 +13,7 @@ from django.db import models from django.db.models import Count, F, ProtectedError, Sum from django.urls import reverse from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from mptt.models import MPTTModel, TreeForeignKey from taggit.managers import TaggableManager from timezone_field import TimeZoneField @@ -182,13 +183,13 @@ class Site(ChangeLoggedModel, CustomFieldModel): facility = models.CharField( max_length=50, blank=True, - help_text='Local facility ID or description' + help_text=_('Local facility ID or description') ) asn = ASNField( blank=True, null=True, - verbose_name='ASN', - help_text='32-bit autonomous system number' + verbose_name=_('ASN'), + help_text=_('32-bit autonomous system number') ) time_zone = TimeZoneField( blank=True @@ -210,14 +211,14 @@ class Site(ChangeLoggedModel, CustomFieldModel): decimal_places=6, blank=True, null=True, - help_text='GPS coordinate (latitude)' + help_text=_('GPS coordinate (latitude)') ) longitude = models.DecimalField( max_digits=9, decimal_places=6, blank=True, null=True, - help_text='GPS coordinate (longitude)' + help_text=_('GPS coordinate (longitude)') ) contact_name = models.CharField( max_length=50, @@ -229,7 +230,7 @@ class Site(ChangeLoggedModel, CustomFieldModel): ) contact_email = models.EmailField( blank=True, - verbose_name='Contact E-mail' + verbose_name=_('Contact E-mail') ) comments = models.TextField( blank=True @@ -428,8 +429,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel): max_length=50, blank=True, null=True, - verbose_name='Facility ID', - help_text='Locally-assigned identifier' + verbose_name=_('Facility ID'), + help_text=_('Locally-assigned identifier') ) site = models.ForeignKey( to='dcim.Site', @@ -442,7 +443,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): related_name='racks', blank=True, null=True, - help_text='Assigned group' + help_text=_('Assigned group') ) tenant = models.ForeignKey( to='tenancy.Tenant', @@ -462,53 +463,53 @@ class Rack(ChangeLoggedModel, CustomFieldModel): related_name='racks', blank=True, null=True, - help_text='Functional role' + help_text=_('Functional role') ) serial = models.CharField( max_length=50, blank=True, - verbose_name='Serial number' + verbose_name=_('Serial number') ) asset_tag = models.CharField( max_length=50, blank=True, null=True, unique=True, - verbose_name='Asset tag', - help_text='A unique tag used to identify this rack' + verbose_name=_('Asset tag'), + help_text=_('A unique tag used to identify this rack') ) type = models.CharField( choices=RackTypeChoices, max_length=50, blank=True, - verbose_name='Type' + verbose_name=_('Type') ) width = models.PositiveSmallIntegerField( choices=RackWidthChoices, default=RackWidthChoices.WIDTH_19IN, - verbose_name='Width', - help_text='Rail-to-rail width' + verbose_name=_('Width'), + help_text=_('Rail-to-rail width') ) u_height = models.PositiveSmallIntegerField( default=RACK_U_HEIGHT_DEFAULT, - verbose_name='Height (U)', + verbose_name=_('Height (U)'), validators=[MinValueValidator(1), MaxValueValidator(100)], - help_text='Height in rack units' + help_text=_('Height in rack units') ) desc_units = models.BooleanField( default=False, - verbose_name='Descending units', - help_text='Units are numbered top-to-bottom' + verbose_name=_('Descending units'), + help_text=_('Units are numbered top-to-bottom') ) outer_width = models.PositiveSmallIntegerField( blank=True, null=True, - help_text='Outer dimension of rack (width)' + help_text=_('Outer dimension of rack (width)') ) outer_depth = models.PositiveSmallIntegerField( blank=True, null=True, - help_text='Outer dimension of rack (depth)' + help_text=_('Outer dimension of rack (depth)') ) outer_unit = models.CharField( max_length=50, @@ -949,24 +950,24 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): part_number = models.CharField( max_length=50, blank=True, - help_text='Discrete part number (optional)' + help_text=_('Discrete part number (optional)') ) u_height = models.PositiveSmallIntegerField( default=1, - verbose_name='Height (U)' + verbose_name=_('Height (U)') ) is_full_depth = models.BooleanField( default=True, - verbose_name='Is full depth', - help_text='Device consumes both front and rear rack faces' + verbose_name=_('Is full depth'), + help_text=_('Device consumes both front and rear rack faces') ) subdevice_role = models.CharField( max_length=50, choices=SubdeviceRoleChoices, blank=True, - verbose_name='Parent/child status', - help_text='Parent devices house child devices in device bays. Leave blank ' - 'if this device type is neither a parent nor a child.' + verbose_name=_('Parent/child status'), + help_text=_('Parent devices house child devices in device bays. Leave blank ' + 'if this device type is neither a parent nor a child.') ) front_image = models.ImageField( upload_to='devicetype-images', @@ -1200,8 +1201,8 @@ class DeviceRole(ChangeLoggedModel): ) vm_role = models.BooleanField( default=True, - verbose_name='VM Role', - help_text='Virtual machines may be assigned to this role' + verbose_name=_('VM Role'), + help_text=_('Virtual machines may be assigned to this role') ) description = models.CharField( max_length=200, @@ -1246,19 +1247,19 @@ class Platform(ChangeLoggedModel): related_name='platforms', blank=True, null=True, - help_text='Optionally limit this platform to devices of a certain manufacturer' + help_text=_('Optionally limit this platform to devices of a certain manufacturer') ) napalm_driver = models.CharField( max_length=50, blank=True, - verbose_name='NAPALM driver', - help_text='The name of the NAPALM driver to use when interacting with devices' + verbose_name=_('NAPALM driver'), + help_text=_('The name of the NAPALM driver to use when interacting with devices') ) napalm_args = JSONField( blank=True, null=True, - verbose_name='NAPALM arguments', - help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)' + verbose_name=_('NAPALM arguments'), + help_text=_('Additional arguments to pass when initiating the NAPALM driver (JSON format)') ) description = models.CharField( max_length=200, @@ -1338,15 +1339,15 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): serial = models.CharField( max_length=50, blank=True, - verbose_name='Serial number' + verbose_name=_('Serial number') ) asset_tag = models.CharField( max_length=50, blank=True, null=True, unique=True, - verbose_name='Asset tag', - help_text='A unique tag used to identify this device' + verbose_name=_('Asset tag'), + help_text=_('A unique tag used to identify this device') ) site = models.ForeignKey( to='dcim.Site', @@ -1364,14 +1365,14 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): blank=True, null=True, validators=[MinValueValidator(1)], - verbose_name='Position (U)', - help_text='The lowest-numbered unit occupied by the device' + verbose_name=_('Position (U)'), + help_text=_('The lowest-numbered unit occupied by the device') ) face = models.CharField( max_length=50, blank=True, choices=DeviceFaceChoices, - verbose_name='Rack face' + verbose_name=_('Rack face') ) status = models.CharField( max_length=50, @@ -1384,7 +1385,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): related_name='primary_ip4_for', blank=True, null=True, - verbose_name='Primary IPv4' + verbose_name=_('Primary IPv4') ) primary_ip6 = models.OneToOneField( to='ipam.IPAddress', @@ -1392,7 +1393,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): related_name='primary_ip6_for', blank=True, null=True, - verbose_name='Primary IPv6' + verbose_name=_('Primary IPv6') ) cluster = models.ForeignKey( to='virtualization.Cluster', @@ -1904,7 +1905,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): max_utilization = models.PositiveSmallIntegerField( validators=[MinValueValidator(1), MaxValueValidator(100)], default=POWERFEED_MAX_UTILIZATION_DEFAULT, - help_text="Maximum permissible draw (percentage)" + help_text=_('Maximum permissible draw (percentage)') ) available_power = models.PositiveIntegerField( default=0, diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 164d37d77..4da365c52 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -1,6 +1,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.utils.translation import gettext as _ from dcim.choices import * from dcim.constants import * @@ -154,13 +155,13 @@ class PowerPortTemplate(ComponentTemplateModel): blank=True, null=True, validators=[MinValueValidator(1)], - help_text="Maximum power draw (watts)" + help_text=_('Maximum power draw (watts)') ) allocated_draw = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], - help_text="Allocated power draw (watts)" + help_text=_('Allocated power draw (watts)') ) class Meta: @@ -213,7 +214,7 @@ class PowerOutletTemplate(ComponentTemplateModel): max_length=50, choices=PowerOutletFeedLegChoices, blank=True, - help_text="Phase (for three-phase feeds)" + help_text=_('Phase (for three-phase feeds)') ) class Meta: @@ -269,7 +270,7 @@ class InterfaceTemplate(ComponentTemplateModel): ) mgmt_only = models.BooleanField( default=False, - verbose_name='Management only' + verbose_name=_('Management only') ) class Meta: diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 0143b39d9..3dcd89572 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -6,6 +6,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Sum from django.urls import reverse +from django.utils.translation import gettext as _ from taggit.managers import TaggableManager from dcim.choices import * @@ -252,7 +253,7 @@ class ConsolePort(CableTermination, ComponentModel): max_length=50, choices=ConsolePortTypeChoices, blank=True, - help_text='Physical port type' + help_text=_('Physical port type') ) connected_endpoint = models.OneToOneField( to='dcim.ConsoleServerPort', @@ -311,7 +312,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): max_length=50, choices=ConsolePortTypeChoices, blank=True, - help_text='Physical port type' + help_text=_('Physical port type') ) connection_status = models.NullBooleanField( choices=CONNECTION_STATUS_CHOICES, @@ -363,19 +364,19 @@ class PowerPort(CableTermination, ComponentModel): max_length=50, choices=PowerPortTypeChoices, blank=True, - help_text='Physical port type' + help_text=_('Physical port type') ) maximum_draw = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], - help_text="Maximum power draw (watts)" + help_text=_('Maximum power draw (watts)') ) allocated_draw = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], - help_text="Allocated power draw (watts)" + help_text=_('Allocated power draw (watts)') ) _connected_poweroutlet = models.OneToOneField( to='dcim.PowerOutlet', @@ -523,7 +524,7 @@ class PowerOutlet(CableTermination, ComponentModel): max_length=50, choices=PowerOutletTypeChoices, blank=True, - help_text='Physical port type' + help_text=_('Physical port type') ) power_port = models.ForeignKey( to='dcim.PowerPort', @@ -536,7 +537,7 @@ class PowerOutlet(CableTermination, ComponentModel): max_length=50, choices=PowerOutletFeedLegChoices, blank=True, - help_text="Phase (for three-phase feeds)" + help_text=_('Phase (for three-phase feeds)') ) connection_status = models.NullBooleanField( choices=CONNECTION_STATUS_CHOICES, @@ -629,7 +630,7 @@ class Interface(CableTermination, ComponentModel): related_name='member_interfaces', null=True, blank=True, - verbose_name='Parent LAG' + verbose_name=_('Parent LAG') ) type = models.CharField( max_length=50, @@ -641,18 +642,18 @@ class Interface(CableTermination, ComponentModel): mac_address = MACAddressField( null=True, blank=True, - verbose_name='MAC Address' + verbose_name=_('MAC Address') ) mtu = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1), MaxValueValidator(65536)], - verbose_name='MTU' + verbose_name=_('MTU') ) mgmt_only = models.BooleanField( default=False, - verbose_name='OOB Management', - help_text='This interface is used only for out-of-band management' + verbose_name=_('OOB Management'), + help_text=_('This interface is used only for out-of-band management') ) mode = models.CharField( max_length=50, @@ -665,13 +666,13 @@ class Interface(CableTermination, ComponentModel): related_name='interfaces_as_untagged', null=True, blank=True, - verbose_name='Untagged VLAN' + verbose_name=_('Untagged VLAN') ) tagged_vlans = models.ManyToManyField( to='ipam.VLAN', related_name='interfaces_as_tagged', blank=True, - verbose_name='Tagged VLANs' + verbose_name=_('Tagged VLANs') ) tags = TaggableManager(through=TaggedItem) @@ -976,7 +977,7 @@ class DeviceBay(ComponentModel): ) name = models.CharField( max_length=50, - verbose_name='Name' + verbose_name=_('Name') ) _name = NaturalOrderingField( target_field='name', @@ -1056,7 +1057,7 @@ class InventoryItem(ComponentModel): ) name = models.CharField( max_length=50, - verbose_name='Name' + verbose_name=_('Name') ) _name = NaturalOrderingField( target_field='name', @@ -1072,13 +1073,13 @@ class InventoryItem(ComponentModel): ) part_id = models.CharField( max_length=50, - verbose_name='Part ID', + verbose_name=_('Part ID'), blank=True, - help_text='Manufacturer-assigned part identifier' + help_text=_('Manufacturer-assigned part identifier') ) serial = models.CharField( max_length=50, - verbose_name='Serial number', + verbose_name=_('Serial number'), blank=True ) asset_tag = models.CharField( @@ -1086,12 +1087,12 @@ class InventoryItem(ComponentModel): unique=True, blank=True, null=True, - verbose_name='Asset tag', - help_text='A unique tag used to identify this item' + verbose_name=_('Asset tag'), + help_text=_('A unique tag used to identify this item') ) discovered = models.BooleanField( default=False, - help_text='This item was automatically discovered' + help_text=_('This item was automatically discovered') ) tags = TaggableManager(through=TaggedItem) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 93ef724e0..c0a742116 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables from django_tables2.utils import Accessor +from django.utils.translation import gettext as _ from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn @@ -196,7 +197,7 @@ class RegionTable(BaseTable): orderable=False ) site_count = tables.Column( - verbose_name='Sites' + verbose_name=_('Sites') ) actions = tables.TemplateColumn( template_code=REGION_ACTIONS, @@ -255,10 +256,10 @@ class RackGroupTable(BaseTable): site = tables.LinkColumn( viewname='dcim:site', args=[Accessor('site.slug')], - verbose_name='Site' + verbose_name=_('Site') ) rack_count = tables.Column( - verbose_name='Racks' + verbose_name=_('Racks') ) actions = tables.TemplateColumn( template_code=RACKGROUP_ACTIONS, @@ -315,7 +316,7 @@ class RackTable(BaseTable): role = ColoredLabelColumn() u_height = tables.TemplateColumn( template_code="{{ record.u_height }}U", - verbose_name='Height' + verbose_name=_('Height') ) class Meta(BaseTable.Meta): @@ -330,17 +331,17 @@ class RackTable(BaseTable): class RackDetailTable(RackTable): device_count = tables.TemplateColumn( template_code=RACK_DEVICE_COUNT, - verbose_name='Devices' + verbose_name=_('Devices') ) get_utilization = tables.TemplateColumn( template_code=UTILIZATION_GRAPH, orderable=False, - verbose_name='Space' + verbose_name=_('Space') ) get_power_utilization = tables.TemplateColumn( template_code=UTILIZATION_GRAPH, orderable=False, - verbose_name='Power' + verbose_name=_('Power') ) tags = TagColumn( url_name='dcim:rack_list' @@ -382,7 +383,7 @@ class RackReservationTable(BaseTable): ) unit_list = tables.Column( orderable=False, - verbose_name='Units' + verbose_name=_('Units') ) actions = tables.TemplateColumn( template_code=RACKRESERVATION_ACTIONS, @@ -408,13 +409,13 @@ class ManufacturerTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() devicetype_count = tables.Column( - verbose_name='Device Types' + verbose_name=_('Device Types') ) inventoryitem_count = tables.Column( - verbose_name='Inventory Items' + verbose_name=_('Inventory Items') ) platform_count = tables.Column( - verbose_name='Platforms' + verbose_name=_('Platforms') ) slug = tables.Column() actions = tables.TemplateColumn( @@ -439,14 +440,14 @@ class DeviceTypeTable(BaseTable): model = tables.LinkColumn( viewname='dcim:devicetype', args=[Accessor('pk')], - verbose_name='Device Type' + verbose_name=_('Device Type') ) is_full_depth = BooleanColumn( - verbose_name='Full Depth' + verbose_name=_('Full Depth') ) instance_count = tables.TemplateColumn( template_code=DEVICETYPE_INSTANCES_TEMPLATE, - verbose_name='Instances' + verbose_name=_('Instances') ) tags = TagColumn( url_name='dcim:devicetype_list' @@ -608,7 +609,7 @@ class InterfaceImportTable(BaseTable): virtual_machine = tables.LinkColumn( viewname='virtualization:virtualmachine', args=[Accessor('virtual_machine.pk')], - verbose_name='Virtual Machine' + verbose_name=_('Virtual Machine') ) class Meta(BaseTable.Meta): @@ -626,7 +627,7 @@ class FrontPortTemplateTable(BaseTable): order_by=('_name',) ) rear_port_position = tables.Column( - verbose_name='Position' + verbose_name=_('Position') ) actions = tables.TemplateColumn( template_code=get_component_template_actions('frontporttemplate'), @@ -706,15 +707,15 @@ class DeviceRoleTable(BaseTable): pk = ToggleColumn() device_count = tables.TemplateColumn( template_code=DEVICEROLE_DEVICE_COUNT, - verbose_name='Devices' + verbose_name=_('Devices') ) vm_count = tables.TemplateColumn( template_code=DEVICEROLE_VM_COUNT, - verbose_name='VMs' + verbose_name=_('VMs') ) color = tables.TemplateColumn( template_code=COLOR_LABEL, - verbose_name='Label' + verbose_name=_('Label') ) vm_role = BooleanColumn() actions = tables.TemplateColumn( @@ -737,11 +738,11 @@ class PlatformTable(BaseTable): pk = ToggleColumn() device_count = tables.TemplateColumn( template_code=PLATFORM_DEVICE_COUNT, - verbose_name='Devices' + verbose_name=_('Devices') ) vm_count = tables.TemplateColumn( template_code=PLATFORM_VM_COUNT, - verbose_name='VMs' + verbose_name=_('VMs') ) actions = tables.TemplateColumn( template_code=PLATFORM_ACTIONS, @@ -785,28 +786,28 @@ class DeviceTable(BaseTable): args=[Accessor('rack.pk')] ) device_role = ColoredLabelColumn( - verbose_name='Role' + verbose_name=_('Role') ) device_type = tables.LinkColumn( viewname='dcim:devicetype', args=[Accessor('device_type.pk')], - verbose_name='Type', + verbose_name=_('Type'), text=lambda record: record.device_type.display_name ) primary_ip = tables.TemplateColumn( template_code=DEVICE_PRIMARY_IP, orderable=False, - verbose_name='IP Address' + verbose_name=_('IP Address') ) primary_ip4 = tables.LinkColumn( viewname='ipam:ipaddress', args=[Accessor('primary_ip4.pk')], - verbose_name='IPv4 Address' + verbose_name=_('IPv4 Address') ) primary_ip6 = tables.LinkColumn( viewname='ipam:ipaddress', args=[Accessor('primary_ip6.pk')], - verbose_name='IPv6 Address' + verbose_name=_('IPv6 Address') ) cluster = tables.LinkColumn( viewname='virtualization:cluster', @@ -817,10 +818,10 @@ class DeviceTable(BaseTable): args=[Accessor('virtual_chassis.pk')] ) vc_position = tables.Column( - verbose_name='VC Position' + verbose_name=_('VC Position') ) vc_priority = tables.Column( - verbose_name='VC Priority' + verbose_name=_('VC Priority') ) tags = TagColumn( url_name='dcim:device_list' @@ -857,10 +858,10 @@ class DeviceImportTable(BaseTable): args=[Accessor('rack.pk')] ) device_role = tables.Column( - verbose_name='Role' + verbose_name=_('Role') ) device_type = tables.Column( - verbose_name='Type' + verbose_name=_('Type') ) class Meta(BaseTable.Meta): @@ -1031,29 +1032,29 @@ class CableTable(BaseTable): id = tables.LinkColumn( viewname='dcim:cable', args=[Accessor('pk')], - verbose_name='ID' + verbose_name=_('ID') ) termination_a_parent = tables.TemplateColumn( template_code=CABLE_TERMINATION_PARENT, accessor=Accessor('termination_a'), orderable=False, - verbose_name='Side A' + verbose_name=_('Side A') ) termination_a = tables.LinkColumn( accessor=Accessor('termination_a'), orderable=False, - verbose_name='Termination A' + verbose_name=_('Termination A') ) termination_b_parent = tables.TemplateColumn( template_code=CABLE_TERMINATION_PARENT, accessor=Accessor('termination_b'), orderable=False, - verbose_name='Side B' + verbose_name=_('Side B') ) termination_b = tables.LinkColumn( accessor=Accessor('termination_b'), orderable=False, - verbose_name='Termination B' + verbose_name=_('Termination B') ) status = tables.TemplateColumn( template_code=STATUS_LABEL @@ -1085,17 +1086,17 @@ class ConsoleConnectionTable(BaseTable): viewname='dcim:device', accessor=Accessor('connected_endpoint.device'), args=[Accessor('connected_endpoint.device.pk')], - verbose_name='Console Server' + verbose_name=_('Console Server') ) connected_endpoint = tables.Column( - verbose_name='Port' + verbose_name=_('Port') ) device = tables.LinkColumn( viewname='dcim:device', args=[Accessor('device.pk')] ) name = tables.Column( - verbose_name='Console Port' + verbose_name=_('Console Port') ) class Meta(BaseTable.Meta): @@ -1109,18 +1110,18 @@ class PowerConnectionTable(BaseTable): accessor=Accessor('connected_endpoint.device'), args=[Accessor('connected_endpoint.device.pk')], order_by='_connected_poweroutlet__device', - verbose_name='PDU' + verbose_name=_('PDU') ) outlet = tables.Column( accessor=Accessor('_connected_poweroutlet'), - verbose_name='Outlet' + verbose_name=_('Outlet') ) device = tables.LinkColumn( viewname='dcim:device', args=[Accessor('device.pk')] ) name = tables.Column( - verbose_name='Power Port' + verbose_name=_('Power Port') ) class Meta(BaseTable.Meta): @@ -1133,25 +1134,25 @@ class InterfaceConnectionTable(BaseTable): viewname='dcim:device', accessor=Accessor('device'), args=[Accessor('device.pk')], - verbose_name='Device A' + verbose_name=_('Device A') ) interface_a = tables.LinkColumn( viewname='dcim:interface', accessor=Accessor('name'), args=[Accessor('pk')], - verbose_name='Interface A' + verbose_name=_('Interface A') ) device_b = tables.LinkColumn( viewname='dcim:device', accessor=Accessor('_connected_interface.device'), args=[Accessor('_connected_interface.device.pk')], - verbose_name='Device B' + verbose_name=_('Device B') ) interface_b = tables.LinkColumn( viewname='dcim:interface', accessor=Accessor('_connected_interface'), args=[Accessor('_connected_interface.pk')], - verbose_name='Interface B' + verbose_name=_('Interface B') ) class Meta(BaseTable.Meta): @@ -1195,7 +1196,7 @@ class VirtualChassisTable(BaseTable): linkify=True ) member_count = tables.Column( - verbose_name='Members' + verbose_name=_('Members') ) tags = TagColumn( url_name='dcim:virtualchassis_list' @@ -1220,7 +1221,7 @@ class PowerPanelTable(BaseTable): ) powerfeed_count = tables.TemplateColumn( template_code=POWERPANEL_POWERFEED_COUNT, - verbose_name='Feeds' + verbose_name=_('Feeds') ) class Meta(BaseTable.Meta): @@ -1254,7 +1255,7 @@ class PowerFeedTable(BaseTable): template_code="{{ value }}%" ) available_power = tables.Column( - verbose_name='Available power (VA)' + verbose_name=_('Available power (VA)') ) tags = TagColumn( url_name='dcim:powerfeed_list' diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e193813d2..de796c3cd 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -14,6 +14,7 @@ from django.urls import reverse from django.utils.html import escape from django.utils.http import is_safe_url from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from django.views.generic import View from circuits.models import Circuit @@ -74,7 +75,7 @@ class BulkRenameView(GetReturnURLMixin, View): for obj in selected_objects: obj.name = obj.new_name obj.save() - messages.success(request, "Renamed {} {}".format( + messages.success(request, _('Renamed {} {}').format( len(selected_objects), model._meta.verbose_name_plural )) @@ -119,7 +120,7 @@ class BulkDisconnectView(GetReturnURLMixin, View): obj.cable.delete() count += 1 - messages.success(request, "Disconnected {} {}".format( + messages.success(request, _('Disconnected {} {}').format( count, self.model._meta.verbose_name_plural )) @@ -1853,7 +1854,7 @@ class DeviceBayPopulateView(PermissionRequiredMixin, View): device_bay.installed_device = form.cleaned_data['installed_device'] device_bay.save() - messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay)) + messages.success(request, _('Added {} to {}.').format(device_bay.installed_device, device_bay)) return redirect('dcim:device', pk=device_bay.device.pk) @@ -1888,7 +1889,7 @@ class DeviceBayDepopulateView(PermissionRequiredMixin, View): removed_device = device_bay.installed_device device_bay.installed_device = None device_bay.save() - messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay)) + messages.success(request, _('{} has been removed from {}.').format(removed_device, device_bay)) return redirect('dcim:device', pk=device_bay.device.pk) diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 808d7ce32..b4192fbe2 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -1,5 +1,6 @@ from django import forms from django.contrib import admin +from django.utils.translation import gettext as _ from utilities.forms import LaxURLField from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, ReportResult, Webhook @@ -20,7 +21,7 @@ def order_content_types(field): class WebhookForm(forms.ModelForm): payload_url = LaxURLField( - label='URL' + label=_('URL') ) class Meta: @@ -116,11 +117,11 @@ class CustomLinkForm(forms.ModelForm): 'url': forms.Textarea, } help_texts = { - 'weight': 'A numeric weight to influence the ordering of this link among its peers. Lower weights appear ' - 'first in a list.', - 'text': 'Jinja2 template code for the link text. Reference the object as {{ obj }}. Links ' - 'which render as empty text will not be displayed.', - 'url': 'Jinja2 template code for the link URL. Reference the object as {{ obj }}.', + 'weight': _('A numeric weight to influence the ordering of this link among its peers. Lower weights appear ' + 'first in a list.'), + 'text': _('Jinja2 template code for the link text. Reference the object as {{ obj }}. Links ' + 'which render as empty text will not be displayed.'), + 'url': _('Jinja2 template code for the link URL. Reference the object as {{ obj }}.'), } def __init__(self, *args, **kwargs): diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 7ccdb1d86..1d8cb5f04 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,6 +1,7 @@ import django_filters from django.contrib.contenttypes.models import ContentType from django.db.models import Q +from django.utils.translation import gettext as _ from dcim.models import DeviceRole, Platform, Region, Site from tenancy.models import Tenant, TenantGroup @@ -107,7 +108,7 @@ class ExportTemplateFilterSet(BaseFilterSet): class TagFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) class Meta: @@ -126,95 +127,95 @@ class TagFilterSet(BaseFilterSet): class ConfigContextFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = django_filters.ModelMultipleChoiceFilter( field_name='regions', queryset=Region.objects.all(), - label='Region', + label=_('Region'), ) region = django_filters.ModelMultipleChoiceFilter( field_name='regions__slug', queryset=Region.objects.all(), to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='sites', queryset=Site.objects.all(), - label='Site', + label=_('Site'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='sites__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) role_id = django_filters.ModelMultipleChoiceFilter( field_name='roles', queryset=DeviceRole.objects.all(), - label='Role', + label=_('Role'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='roles__slug', queryset=DeviceRole.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) platform_id = django_filters.ModelMultipleChoiceFilter( field_name='platforms', queryset=Platform.objects.all(), - label='Platform', + label=_('Platform'), ) platform = django_filters.ModelMultipleChoiceFilter( field_name='platforms__slug', queryset=Platform.objects.all(), to_field_name='slug', - label='Platform (slug)', + label=_('Platform (slug)'), ) cluster_group_id = django_filters.ModelMultipleChoiceFilter( field_name='cluster_groups', queryset=ClusterGroup.objects.all(), - label='Cluster group', + label=_('Cluster group'), ) cluster_group = django_filters.ModelMultipleChoiceFilter( field_name='cluster_groups__slug', queryset=ClusterGroup.objects.all(), to_field_name='slug', - label='Cluster group (slug)', + label=_('Cluster group (slug)'), ) cluster_id = django_filters.ModelMultipleChoiceFilter( field_name='clusters', queryset=Cluster.objects.all(), - label='Cluster', + label=_('Cluster'), ) tenant_group_id = django_filters.ModelMultipleChoiceFilter( field_name='tenant_groups', queryset=TenantGroup.objects.all(), - label='Tenant group', + label=_('Tenant group'), ) tenant_group = django_filters.ModelMultipleChoiceFilter( field_name='tenant_groups__slug', queryset=TenantGroup.objects.all(), to_field_name='slug', - label='Tenant group (slug)', + label=_('Tenant group (slug)'), ) tenant_id = django_filters.ModelMultipleChoiceFilter( field_name='tenants', queryset=Tenant.objects.all(), - label='Tenant', + label=_('Tenant'), ) tenant = django_filters.ModelMultipleChoiceFilter( field_name='tenants__slug', queryset=Tenant.objects.all(), to_field_name='slug', - label='Tenant (slug)', + label=_('Tenant (slug)'), ) tag = django_filters.ModelMultipleChoiceFilter( field_name='tags__slug', queryset=Tag.objects.all(), to_field_name='slug', - label='Tag (slug)', + label=_('Tag (slug)'), ) class Meta: @@ -238,7 +239,7 @@ class ConfigContextFilterSet(BaseFilterSet): class LocalConfigContextFilterSet(django_filters.FilterSet): local_context_data = django_filters.BooleanFilter( method='_local_context_data', - label='Has local config context data', + label=_('Has local config context data'), ) def _local_context_data(self, queryset, name, value): @@ -248,7 +249,7 @@ class LocalConfigContextFilterSet(django_filters.FilterSet): class ObjectChangeFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) time = django_filters.DateTimeFromToRangeFilter() diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index bff919005..3b0a4017f 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -1,6 +1,7 @@ from django import forms from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType +from django.utils.translation import gettext as _ from mptt.forms import TreeNodeMultipleChoiceField from taggit.forms import TagField as TagField_ @@ -181,7 +182,7 @@ class TagFilterForm(BootstrapMixin, forms.Form): model = Tag q = forms.CharField( required=False, - label='Search' + label=_('Search') ) @@ -285,7 +286,7 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm): class ConfigContextFilterForm(BootstrapMixin, forms.Form): q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -330,7 +331,7 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form): cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False, - label='Cluster' + label=_('Cluster') ) tenant_group = DynamicModelMultipleChoiceField( queryset=TenantGroup.objects.all(), @@ -365,7 +366,7 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form): class LocalConfigContextFilterForm(forms.Form): local_context_data = forms.NullBooleanField( required=False, - label='Has local config context data', + label=_('Has local config context data'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) @@ -393,15 +394,15 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form): model = ObjectChange q = forms.CharField( required=False, - label='Search' + label=_('Search') ) time_after = forms.DateTimeField( - label='After', + label=_('After'), required=False, widget=DateTimePicker() ) time_before = forms.DateTimeField( - label='Before', + label=_('Before'), required=False, widget=DateTimePicker() ) @@ -420,7 +421,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form): queryset=ContentType.objects.order_by('model'), required=False, widget=ContentTypeSelect(), - label='Object Type' + label=_('Object Type') ) @@ -432,8 +433,8 @@ class ScriptForm(BootstrapMixin, forms.Form): _commit = forms.BooleanField( required=False, initial=True, - label="Commit changes", - help_text="Commit changes to the database (uncheck for a dry-run)" + label=_('Commit changes'), + help_text=_('Commit changes to the database (uncheck for a dry-run)') ) def __init__(self, *args, **kwargs): diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 62e2ca4df..7884a18d9 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -7,6 +7,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.validators import ValidationError from django.db import models +from django.utils.translation import gettext as _ from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice from extras.choices import * @@ -70,9 +71,9 @@ class CustomField(models.Model): obj_type = models.ManyToManyField( to=ContentType, related_name='custom_fields', - verbose_name='Object(s)', + verbose_name=_('Object(s)'), limit_choices_to=FeatureQuery('custom_fields'), - help_text='The object(s) to which this field applies.' + help_text=_('The object(s) to which this field applies.') ) type = models.CharField( max_length=50, @@ -86,8 +87,8 @@ class CustomField(models.Model): label = models.CharField( 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)' + help_text=_('Name of the field as displayed to users (if not provided, ' + 'the field\'s name will be used)') ) description = models.CharField( max_length=200, @@ -95,24 +96,24 @@ class CustomField(models.Model): ) required = models.BooleanField( default=False, - help_text='If true, this field is required when creating new objects ' - 'or editing an existing object.' + help_text=_('If true, this field is required when creating new objects ' + 'or editing an existing object.') ) filter_logic = models.CharField( max_length=50, choices=CustomFieldFilterLogicChoices, default=CustomFieldFilterLogicChoices.FILTER_LOOSE, - help_text='Loose matches any instance of a given string; exact ' - 'matches the entire field.' + help_text=_('Loose matches any instance of a given string; exact ' + 'matches the entire field.') ) default = models.CharField( max_length=100, blank=True, - help_text='Default value for the field. Use "true" or "false" for booleans.' + help_text=_('Default value for the field. Use "true" or "false" for booleans.') ) weight = models.PositiveSmallIntegerField( default=100, - help_text='Fields with higher weights appear lower in a form.' + help_text=_('Fields with higher weights appear lower in a form.') ) objects = CustomFieldManager() @@ -284,7 +285,7 @@ class CustomFieldChoice(models.Model): ) weight = models.PositiveSmallIntegerField( default=100, - help_text='Higher weights appear lower in the list' + help_text=_('Higher weights appear lower in the list') ) class Meta: diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index f98a7b34f..073fe6713 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -10,6 +10,7 @@ from django.db import models from django.http import HttpResponse from django.template import Template, Context from django.urls import reverse +from django.utils.translation import gettext as _ from rest_framework.utils.encoders import JSONEncoder from utilities.utils import deepmerge, render_jinja2 @@ -32,9 +33,9 @@ class Webhook(models.Model): obj_type = models.ManyToManyField( to=ContentType, related_name='webhooks', - verbose_name='Object types', + verbose_name=_('Object types'), limit_choices_to=FeatureQuery('webhooks'), - help_text="The object(s) to which this Webhook applies." + help_text=_('The object(s) to which this Webhook applies.') ) name = models.CharField( max_length=150, @@ -42,20 +43,20 @@ class Webhook(models.Model): ) type_create = models.BooleanField( default=False, - help_text="Call this webhook when a matching object is created." + help_text=_('Call this webhook when a matching object is created.') ) type_update = models.BooleanField( default=False, - help_text="Call this webhook when a matching object is updated." + help_text=_('Call this webhook when a matching object is updated.') ) type_delete = models.BooleanField( default=False, - help_text="Call this webhook when a matching object is deleted." + help_text=_('Call this webhook when a matching object is deleted.') ) payload_url = models.CharField( max_length=500, - verbose_name='URL', - help_text="A POST will be sent to this URL when the webhook is called." + verbose_name=_('URL'), + help_text=_('A POST will be sent to this URL when the webhook is called.') ) enabled = models.BooleanField( default=True @@ -64,47 +65,47 @@ class Webhook(models.Model): 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', - help_text='The complete list of official content types is available ' - 'here.' + verbose_name=_('HTTP content type'), + help_text=_('The complete list of official content types is available ' + 'here.') ) additional_headers = models.TextField( blank=True, - help_text="User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. " + 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 " - "support with the same context as the request body (below)." + "support with the same context as the request body (below).") ) body_template = models.TextField( blank=True, - help_text='Jinja2 template for a custom request body. If blank, a JSON object representing the change will be ' + 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.' + 'timestamp, username, request_id, and data.') ) secret = models.CharField( max_length=255, blank=True, - help_text="When provided, the request will include a 'X-Hook-Signature' " + help_text=_("When provided, the request will include a 'X-Hook-Signature' " "header containing a HMAC hex digest of the payload body using " "the secret as the key. The secret is not transmitted in " - "the request." + "the request.") ) ssl_verification = models.BooleanField( default=True, - verbose_name='SSL verification', - help_text="Enable SSL certificate verification. Disable with caution!" + 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', - help_text='The specific CA certificate file to use for SSL verification. ' - 'Leave blank to use the system defaults.' + verbose_name=_('CA File Path'), + help_text=_('The specific CA certificate file to use for SSL verification. ' + 'Leave blank to use the system defaults.') ) class Meta: @@ -168,12 +169,12 @@ class CustomLink(models.Model): ) text = models.CharField( max_length=500, - help_text="Jinja2 template code for link text" + help_text=_('Jinja2 template code for link text') ) url = models.CharField( max_length=500, - verbose_name='URL', - help_text="Jinja2 template code for link URL" + verbose_name=_('URL'), + help_text=_('Jinja2 template code for link URL') ) weight = models.PositiveSmallIntegerField( default=100 @@ -181,16 +182,16 @@ class CustomLink(models.Model): group_name = models.CharField( max_length=50, blank=True, - help_text="Links with the same group will appear as a dropdown menu" + help_text=_('Links with the same group will appear as a dropdown menu') ) button_class = models.CharField( max_length=30, choices=CustomLinkButtonClassChoices, default=CustomLinkButtonClassChoices.CLASS_DEFAULT, - help_text="The class of the first link in a group will be used for the dropdown button" + help_text=_('The class of the first link in a group will be used for the dropdown button') ) new_window = models.BooleanField( - help_text="Force link to open in a new window" + help_text=_('Force link to open in a new window') ) class Meta: @@ -215,7 +216,7 @@ class Graph(models.Model): ) name = models.CharField( max_length=100, - verbose_name='Name' + verbose_name=_('Name') ) template_language = models.CharField( max_length=50, @@ -224,11 +225,11 @@ class Graph(models.Model): ) source = models.CharField( max_length=500, - verbose_name='Source URL' + verbose_name=_('Source URL') ) link = models.URLField( blank=True, - verbose_name='Link URL' + verbose_name=_('Link URL') ) class Meta: @@ -284,18 +285,18 @@ class ExportTemplate(models.Model): default=TemplateLanguageChoices.LANGUAGE_JINJA2 ) template_code = models.TextField( - help_text='The list of objects being exported is passed as a context variable named queryset.' + help_text=_('The list of objects being exported is passed as a context variable named queryset.') ) mime_type = models.CharField( max_length=50, blank=True, - verbose_name='MIME type', - help_text='Defaults to text/plain' + verbose_name=_('MIME type'), + help_text=_('Defaults to text/plain') ) file_extension = models.CharField( max_length=15, blank=True, - help_text='Extension to append to the rendered filename' + help_text=_('Extension to append to the rendered filename') ) class Meta: diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 7a78d4b19..65eb76273 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables from django_tables2.utils import Accessor +from django.utils.translation import gettext as _ from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn from .models import ConfigContext, ObjectChange, Tag, TaggedItem @@ -84,10 +85,10 @@ class TaggedItemTable(BaseTable): content_object = tables.TemplateColumn( template_code=TAGGED_ITEM, orderable=False, - verbose_name='Object' + verbose_name=_('Object') ) content_type = tables.Column( - verbose_name='Type' + verbose_name=_('Type') ) class Meta(BaseTable.Meta): @@ -99,7 +100,7 @@ class ConfigContextTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() is_active = BooleanColumn( - verbose_name='Active' + verbose_name=_('Active') ) class Meta(BaseTable.Meta): @@ -119,15 +120,15 @@ class ObjectChangeTable(BaseTable): template_code=OBJECTCHANGE_ACTION ) changed_object_type = tables.Column( - verbose_name='Type' + verbose_name=_('Type') ) object_repr = tables.TemplateColumn( template_code=OBJECTCHANGE_OBJECT, - verbose_name='Object' + verbose_name=_('Object') ) request_id = tables.TemplateColumn( template_code=OBJECTCHANGE_REQUEST_ID, - verbose_name='Request ID' + verbose_name=_('Request ID') ) class Meta(BaseTable.Meta): diff --git a/netbox/ipam/apps.py b/netbox/ipam/apps.py index fd4af74b0..7fa8c81ee 100644 --- a/netbox/ipam/apps.py +++ b/netbox/ipam/apps.py @@ -1,6 +1,7 @@ from django.apps import AppConfig +from django.utils.translation import gettext as _ class IPAMConfig(AppConfig): name = "ipam" - verbose_name = "IPAM" + verbose_name = _('IPAM') diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 6641d1c8b..64a89504c 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -2,6 +2,7 @@ import django_filters import netaddr from django.core.exceptions import ValidationError from django.db.models import Q +from django.utils.translation import gettext as _ from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site @@ -32,7 +33,7 @@ __all__ = ( class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) tag = TagFilter() @@ -60,7 +61,7 @@ class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) family = django_filters.NumberFilter( field_name='prefix', @@ -68,17 +69,17 @@ class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt ) prefix = django_filters.CharFilter( method='filter_prefix', - label='Prefix', + label=_('Prefix'), ) rir_id = django_filters.ModelMultipleChoiceFilter( queryset=RIR.objects.all(), - label='RIR (ID)', + label=_('RIR (ID)'), ) rir = django_filters.ModelMultipleChoiceFilter( field_name='rir__slug', queryset=RIR.objects.all(), to_field_name='slug', - label='RIR (slug)', + label=_('RIR (slug)'), ) tag = TagFilter() @@ -110,7 +111,7 @@ class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) class Meta: @@ -121,7 +122,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) family = django_filters.NumberFilter( field_name='prefix', @@ -129,74 +130,74 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Cre ) prefix = django_filters.CharFilter( method='filter_prefix', - label='Prefix', + label=_('Prefix'), ) within = django_filters.CharFilter( method='search_within', - label='Within prefix', + label=_('Within prefix'), ) within_include = django_filters.CharFilter( method='search_within_include', - label='Within and including prefix', + label=_('Within and including prefix'), ) contains = django_filters.CharFilter( method='search_contains', - label='Prefixes which contain this prefix or IP', + label=_('Prefixes which contain this prefix or IP'), ) mask_length = django_filters.NumberFilter( method='filter_mask_length', - label='Mask length', + label=_('Mask length'), ) vrf_id = django_filters.ModelMultipleChoiceFilter( queryset=VRF.objects.all(), - label='VRF', + label=_('VRF'), ) vrf = django_filters.ModelMultipleChoiceFilter( field_name='vrf__rd', queryset=VRF.objects.all(), to_field_name='rd', - label='VRF (RD)', + label=_('VRF (RD)'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) vlan_id = django_filters.ModelMultipleChoiceFilter( queryset=VLAN.objects.all(), - label='VLAN (ID)', + label=_('VLAN (ID)'), ) vlan_vid = django_filters.NumberFilter( field_name='vlan__vid', - label='VLAN number (1-4095)', + label=_('VLAN number (1-4095)'), ) role_id = django_filters.ModelMultipleChoiceFilter( queryset=Role.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='role__slug', queryset=Role.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) status = django_filters.MultipleChoiceFilter( choices=PrefixStatusChoices, @@ -271,7 +272,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Cre class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) family = django_filters.NumberFilter( field_name='address', @@ -279,60 +280,60 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, ) parent = django_filters.CharFilter( method='search_by_parent', - label='Parent prefix', + label=_('Parent prefix'), ) address = MultiValueCharFilter( method='filter_address', - label='Address', + label=_('Address'), ) mask_length = django_filters.NumberFilter( method='filter_mask_length', - label='Mask length', + label=_('Mask length'), ) vrf_id = django_filters.ModelMultipleChoiceFilter( queryset=VRF.objects.all(), - label='VRF', + label=_('VRF'), ) vrf = django_filters.ModelMultipleChoiceFilter( field_name='vrf__rd', queryset=VRF.objects.all(), to_field_name='rd', - label='VRF (RD)', + label=_('VRF (RD)'), ) device = MultiValueCharFilter( method='filter_device', field_name='name', - label='Device (name)', + label=_('Device (name)'), ) device_id = MultiValueNumberFilter( method='filter_device', field_name='pk', - label='Device (ID)', + label=_('Device (ID)'), ) virtual_machine_id = django_filters.ModelMultipleChoiceFilter( field_name='interface__virtual_machine', queryset=VirtualMachine.objects.all(), - label='Virtual machine (ID)', + label=_('Virtual machine (ID)'), ) virtual_machine = django_filters.ModelMultipleChoiceFilter( field_name='interface__virtual_machine__name', queryset=VirtualMachine.objects.all(), to_field_name='name', - label='Virtual machine (name)', + label=_('Virtual machine (name)'), ) interface = django_filters.ModelMultipleChoiceFilter( field_name='interface__name', queryset=Interface.objects.all(), to_field_name='name', - label='Interface (ID)', + label=_('Interface (ID)'), ) interface_id = django_filters.ModelMultipleChoiceFilter( queryset=Interface.objects.all(), - label='Interface (ID)', + label=_('Interface (ID)'), ) assigned_to_interface = django_filters.BooleanFilter( method='_assigned_to_interface', - label='Is assigned to an interface', + label=_('Is assigned to an interface'), ) status = django_filters.MultipleChoiceFilter( choices=IPAddressStatusChoices, @@ -397,24 +398,24 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) class Meta: @@ -425,50 +426,50 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) group_id = django_filters.ModelMultipleChoiceFilter( queryset=VLANGroup.objects.all(), - label='Group (ID)', + label=_('Group (ID)'), ) group = django_filters.ModelMultipleChoiceFilter( field_name='group__slug', queryset=VLANGroup.objects.all(), to_field_name='slug', - label='Group', + label=_('Group'), ) role_id = django_filters.ModelMultipleChoiceFilter( queryset=Role.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='role__slug', queryset=Role.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) status = django_filters.MultipleChoiceFilter( choices=VLANStatusChoices, @@ -494,27 +495,27 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), - label='Device (ID)', + label=_('Device (ID)'), ) device = django_filters.ModelMultipleChoiceFilter( field_name='device__name', queryset=Device.objects.all(), to_field_name='name', - label='Device (name)', + label=_('Device (name)'), ) virtual_machine_id = django_filters.ModelMultipleChoiceFilter( queryset=VirtualMachine.objects.all(), - label='Virtual machine (ID)', + label=_('Virtual machine (ID)'), ) virtual_machine = django_filters.ModelMultipleChoiceFilter( field_name='virtual_machine__name', queryset=VirtualMachine.objects.all(), to_field_name='name', - label='Virtual machine (name)', + label=_('Virtual machine (name)'), ) tag = TagFilter() diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 943d4d30a..85c5b6b0b 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -1,5 +1,6 @@ from django import forms from django.core.validators import MaxValueValidator, MinValueValidator +from django.utils.translation import gettext as _ from dcim.models import Device, Interface, Rack, Region, Site from extras.forms import ( @@ -55,7 +56,7 @@ class VRFCSVForm(CustomFieldModelCSVForm): queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) class Meta: @@ -75,7 +76,7 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm enforce_unique = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect(), - label='Enforce unique space' + label=_('Enforce unique space') ) description = forms.CharField( max_length=100, @@ -93,7 +94,7 @@ class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): field_order = ['q', 'tenant_group', 'tenant'] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) tag = TagFilterField(model) @@ -119,14 +120,14 @@ class RIRCSVForm(CSVModelForm): model = RIR fields = RIR.csv_headers help_texts = { - 'name': 'RIR name', + 'name': _('RIR name'), } class RIRFilterForm(BootstrapMixin, forms.Form): is_private = forms.NullBooleanField( required=False, - label='Private', + label=_('Private'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) @@ -163,7 +164,7 @@ class AggregateCSVForm(CustomFieldModelCSVForm): rir = CSVModelChoiceField( queryset=RIR.objects.all(), to_field_name='name', - help_text='Assigned RIR' + help_text=_('Assigned RIR') ) class Meta: @@ -179,7 +180,7 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd rir = DynamicModelChoiceField( queryset=RIR.objects.all(), required=False, - label='RIR' + label=_('RIR') ) date_added = forms.DateField( required=False @@ -202,19 +203,19 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Aggregate q = forms.CharField( required=False, - label='Search' + label=_('Search') ) family = forms.ChoiceField( required=False, choices=add_blank_choice(IPAddressFamilyChoices), - label='Address family', + label=_('Address family'), widget=StaticSelect2() ) rir = DynamicModelMultipleChoiceField( queryset=RIR.objects.all(), to_field_name='slug', required=False, - label='RIR', + label=_('RIR'), widget=APISelectMultiple( value_field="slug", ) @@ -252,7 +253,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label=_('VRF') ) site = DynamicModelChoiceField( queryset=Site.objects.all(), @@ -270,7 +271,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): vlan_group = DynamicModelChoiceField( queryset=VLANGroup.objects.all(), required=False, - label='VLAN group', + label=_('VLAN group'), widget=APISelect( filter_for={ 'vlan': 'group_id' @@ -283,7 +284,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), required=False, - label='VLAN', + label=_('VLAN'), widget=APISelect( display_field='display_name' ) @@ -323,41 +324,41 @@ class PrefixCSVForm(CustomFieldModelCSVForm): queryset=VRF.objects.all(), to_field_name='name', required=False, - help_text='Assigned VRF' + help_text=_('Assigned VRF') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) vlan_group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', - help_text="VLAN's group (if any)" + help_text=_("VLAN's group (if any)") ) vlan = CSVModelChoiceField( queryset=VLAN.objects.all(), required=False, to_field_name='vid', - help_text="Assigned VLAN" + help_text=_('Assigned VLAN') ) status = CSVChoiceField( choices=PrefixStatusChoices, - help_text='Operational status' + help_text=_('Operational status') ) role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', - help_text='Functional role' + help_text=_('Functional role') ) class Meta: @@ -389,7 +390,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label=_('VRF') ) prefix_length = forms.IntegerField( min_value=PREFIX_LENGTH_MIN, @@ -412,7 +413,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF is_pool = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect(), - label='Is a pool' + label=_('Is a pool') ) description = forms.CharField( max_length=100, @@ -433,7 +434,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) within_include = forms.CharField( required=False, @@ -442,24 +443,24 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) 'placeholder': 'Prefix', } ), - label='Search within' + label=_('Search within') ) family = forms.ChoiceField( required=False, choices=add_blank_choice(IPAddressFamilyChoices), - label='Address family', + label=_('Address family'), widget=StaticSelect2() ) mask_length = forms.ChoiceField( required=False, choices=PREFIX_MASK_LENGTH_CHOICES, - label='Mask length', + label=_('Mask length'), widget=StaticSelect2() ) vrf_id = DynamicModelMultipleChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF', + label=_('VRF'), widget=APISelectMultiple( null_option=True, ) @@ -500,14 +501,14 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ) is_pool = forms.NullBooleanField( required=False, - label='Is a pool', + label=_('Is a pool'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) expand = forms.BooleanField( required=False, - label='Expand prefix hierarchy' + label=_('Expand prefix hierarchy') ) tag = TagFilterField(model) @@ -524,12 +525,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label=_('VRF') ) nat_site = DynamicModelChoiceField( queryset=Site.objects.all(), required=False, - label='Site', + label=_('Site'), widget=APISelect( filter_for={ 'nat_rack': 'site_id', @@ -540,7 +541,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel nat_rack = DynamicModelChoiceField( queryset=Rack.objects.all(), required=False, - label='Rack', + label=_('Rack'), widget=APISelect( display_field='display_name', filter_for={ @@ -554,7 +555,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel nat_device = DynamicModelChoiceField( queryset=Device.objects.all(), required=False, - label='Device', + label=_('Device'), widget=APISelect( display_field='display_name', filter_for={ @@ -565,7 +566,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel nat_vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF', + label=_('VRF'), widget=APISelect( filter_for={ 'nat_inside': 'vrf_id' @@ -575,14 +576,14 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel nat_inside = DynamicModelChoiceField( queryset=IPAddress.objects.all(), required=False, - label='IP Address', + label=_('IP Address'), widget=APISelect( display_field='address' ) ) primary_for_parent = forms.BooleanField( required=False, - label='Make this the primary IP for the device/VM' + label=_('Make this the primary IP for the device/VM') ) tags = TagField( required=False @@ -671,7 +672,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel class IPAddressBulkCreateForm(BootstrapMixin, forms.Form): pattern = ExpandableIPAddressField( - label='Address pattern' + label=_('Address pattern') ) @@ -679,7 +680,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label=_('VRF') ) tags = TagField( required=False @@ -705,43 +706,43 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): queryset=VRF.objects.all(), to_field_name='name', required=False, - help_text='Assigned VRF' + help_text=_('Assigned VRF') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) status = CSVChoiceField( choices=IPAddressStatusChoices, - help_text='Operational status' + help_text=_('Operational status') ) role = CSVChoiceField( choices=IPAddressRoleChoices, required=False, - help_text='Functional role' + help_text=_('Functional role') ) device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', - help_text='Parent device of assigned interface (if any)' + help_text=_('Parent device of assigned interface (if any)') ) virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', - help_text='Parent VM of assigned interface (if any)' + help_text=_('Parent VM of assigned interface (if any)') ) interface = CSVModelChoiceField( queryset=Interface.objects.all(), required=False, to_field_name='name', - help_text='Assigned interface' + help_text=_('Assigned interface') ) is_primary = forms.BooleanField( - help_text='Make this the primary IP for the assigned device', + help_text=_('Make this the primary IP for the assigned device'), required=False ) @@ -779,7 +780,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): # Validate is_primary if is_primary and not device and not virtual_machine: - raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP") + raise forms.ValidationError(_('No device or virtual machine specified; cannot set as primary IP')) def save(self, *args, **kwargs): @@ -805,7 +806,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF' + label=_('VRF') ) mask_length = forms.IntegerField( min_value=IPADDRESS_MASK_LENGTH_MIN, @@ -845,12 +846,12 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): vrf_id = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF', + label=_('VRF'), empty_label='Global' ) q = forms.CharField( required=False, - label='Search', + label=_('Search'), ) @@ -862,7 +863,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) parent = forms.CharField( required=False, @@ -871,24 +872,24 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo 'placeholder': 'Prefix', } ), - label='Parent Prefix' + label=_('Parent Prefix') ) family = forms.ChoiceField( required=False, choices=add_blank_choice(IPAddressFamilyChoices), - label='Address family', + label=_('Address family'), widget=StaticSelect2() ) mask_length = forms.ChoiceField( required=False, choices=IPADDRESS_MASK_LENGTH_CHOICES, - label='Mask length', + label=_('Mask length'), widget=StaticSelect2() ) vrf_id = DynamicModelMultipleChoiceField( queryset=VRF.objects.all(), required=False, - label='VRF', + label=_('VRF'), widget=APISelectMultiple( null_option=True, ) @@ -905,7 +906,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo ) assigned_to_interface = forms.NullBooleanField( required=False, - label='Assigned to an interface', + label=_('Assigned to an interface'), widget=StaticSelect2( choices=BOOLEAN_WITH_BLANK_CHOICES ) @@ -936,7 +937,7 @@ class VLANGroupCSVForm(CSVModelForm): queryset=Site.objects.all(), required=False, to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) slug = SlugField() @@ -1018,37 +1019,37 @@ class VLANCSVForm(CustomFieldModelCSVForm): queryset=Site.objects.all(), required=False, to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', - help_text='Assigned VLAN group' + help_text=_('Assigned VLAN group') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) status = CSVChoiceField( choices=VLANStatusChoices, - help_text='Operational status' + help_text=_('Operational status') ) role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', - help_text='Functional role' + help_text=_('Functional role') ) class Meta: model = VLAN fields = VLAN.csv_headers help_texts = { - 'vid': 'Numeric VLAN ID (1-4095)', - 'name': 'VLAN name', + 'vid': _('Numeric VLAN ID (1-4095)'), + 'name': _('VLAN name'), } def __init__(self, data=None, *args, **kwargs): @@ -1108,7 +1109,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant'] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -1134,7 +1135,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): group_id = DynamicModelMultipleChoiceField( queryset=VLANGroup.objects.all(), required=False, - label='VLAN group', + label=_('VLAN group'), widget=APISelectMultiple( null_option=True, ) @@ -1204,7 +1205,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Service q = forms.CharField( required=False, - label='Search' + label=_('Search') ) protocol = forms.ChoiceField( choices=add_blank_choice(ServiceProtocolChoices), @@ -1222,17 +1223,17 @@ class ServiceCSVForm(CustomFieldModelCSVForm): queryset=Device.objects.all(), required=False, to_field_name='name', - help_text='Required if not assigned to a VM' + help_text=_('Required if not assigned to a VM') ) virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', - help_text='Required if not assigned to a device' + help_text=_('Required if not assigned to a device') ) protocol = CSVChoiceField( choices=ServiceProtocolChoices, - help_text='IP protocol' + help_text=_('IP protocol') ) class Meta: diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index eeb985b7c..6b3bcb097 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -6,6 +6,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import F, Q from django.urls import reverse +from django.utils.translation import gettext as _ from taggit.managers import TaggableManager from dcim.models import Device, Interface @@ -50,8 +51,8 @@ class VRF(ChangeLoggedModel, CustomFieldModel): unique=True, blank=True, null=True, - verbose_name='Route distinguisher', - help_text='Unique route distinguisher (as defined in RFC 4364)' + verbose_name=_('Route distinguisher'), + help_text=_('Unique route distinguisher (as defined in RFC 4364)') ) tenant = models.ForeignKey( to='tenancy.Tenant', @@ -62,8 +63,8 @@ class VRF(ChangeLoggedModel, CustomFieldModel): ) enforce_unique = models.BooleanField( default=True, - verbose_name='Enforce unique space', - help_text='Prevent duplicate prefixes/IP addresses within this VRF' + verbose_name=_('Enforce unique space'), + help_text=_('Prevent duplicate prefixes/IP addresses within this VRF') ) description = models.CharField( max_length=200, @@ -84,7 +85,7 @@ class VRF(ChangeLoggedModel, CustomFieldModel): class Meta: ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique - verbose_name = 'VRF' + verbose_name = _('VRF') verbose_name_plural = 'VRFs' def __str__(self): @@ -123,8 +124,8 @@ class RIR(ChangeLoggedModel): ) is_private = models.BooleanField( default=False, - verbose_name='Private', - help_text='IP space managed by this RIR is considered private' + verbose_name=_('Private'), + help_text=_('IP space managed by this RIR is considered private') ) description = models.CharField( max_length=200, @@ -135,7 +136,7 @@ class RIR(ChangeLoggedModel): class Meta: ordering = ['name'] - verbose_name = 'RIR' + verbose_name = _('RIR') verbose_name_plural = 'RIRs' def __str__(self): @@ -164,7 +165,7 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel): to='ipam.RIR', on_delete=models.PROTECT, related_name='aggregates', - verbose_name='RIR' + verbose_name=_('RIR') ) date_added = models.DateField( blank=True, @@ -299,7 +300,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): assigned to a VLAN where appropriate. """ prefix = IPNetworkField( - help_text='IPv4 or IPv6 network with mask' + help_text=_('IPv4 or IPv6 network with mask') ) site = models.ForeignKey( to='dcim.Site', @@ -314,7 +315,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): related_name='prefixes', blank=True, null=True, - verbose_name='VRF' + verbose_name=_('VRF') ) tenant = models.ForeignKey( to='tenancy.Tenant', @@ -329,14 +330,14 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): related_name='prefixes', blank=True, null=True, - verbose_name='VLAN' + verbose_name=_('VLAN') ) status = models.CharField( max_length=50, choices=PrefixStatusChoices, default=PrefixStatusChoices.STATUS_ACTIVE, - verbose_name='Status', - help_text='Operational status of this prefix' + verbose_name=_('Status'), + help_text=_('Operational status of this prefix') ) role = models.ForeignKey( to='ipam.Role', @@ -344,12 +345,12 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): related_name='prefixes', blank=True, null=True, - help_text='The primary function of this prefix' + help_text=_('The primary function of this prefix') ) is_pool = models.BooleanField( - verbose_name='Is a pool', + verbose_name=_('Is a pool'), default=False, - help_text='All IP addresses within this prefix are considered usable' + help_text=_('All IP addresses within this prefix are considered usable') ) description = models.CharField( max_length=200, @@ -570,7 +571,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP. """ address = IPAddressField( - help_text='IPv4 or IPv6 address (with mask)' + help_text=_('IPv4 or IPv6 address (with mask)') ) vrf = models.ForeignKey( to='ipam.VRF', @@ -578,7 +579,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): related_name='ip_addresses', blank=True, null=True, - verbose_name='VRF' + verbose_name=_('VRF') ) tenant = models.ForeignKey( to='tenancy.Tenant', @@ -591,13 +592,13 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): max_length=50, choices=IPAddressStatusChoices, default=IPAddressStatusChoices.STATUS_ACTIVE, - help_text='The operational status of this IP' + help_text=_('The operational status of this IP') ) role = models.CharField( max_length=50, choices=IPAddressRoleChoices, blank=True, - help_text='The functional role of this IP' + help_text=_('The functional role of this IP') ) interface = models.ForeignKey( to='dcim.Interface', @@ -612,15 +613,15 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): related_name='nat_outside', blank=True, null=True, - verbose_name='NAT (Inside)', - help_text='The IP for which this address is the "outside" IP' + verbose_name=_('NAT (Inside)'), + help_text=_('The IP for which this address is the "outside" IP') ) dns_name = models.CharField( max_length=255, blank=True, validators=[DNSValidator], - verbose_name='DNS Name', - help_text='Hostname or FQDN (not case-sensitive)' + verbose_name=_('DNS Name'), + help_text=_('Hostname or FQDN (not case-sensitive)') ) description = models.CharField( max_length=200, @@ -663,7 +664,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): class Meta: ordering = ('address', 'pk') # address may be non-unique - verbose_name = 'IP address' + verbose_name = _('IP address') verbose_name_plural = 'IP addresses' def __str__(self): @@ -836,7 +837,7 @@ class VLANGroup(ChangeLoggedModel): ['site', 'name'], ['site', 'slug'], ] - verbose_name = 'VLAN group' + verbose_name = _('VLAN group') verbose_name_plural = 'VLAN groups' def __str__(self): @@ -889,7 +890,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel): null=True ) vid = models.PositiveSmallIntegerField( - verbose_name='ID', + verbose_name=_('ID'), validators=[MinValueValidator(1), MaxValueValidator(4094)] ) name = models.CharField( @@ -943,7 +944,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel): ['group', 'vid'], ['group', 'name'], ] - verbose_name = 'VLAN' + verbose_name = _('VLAN') verbose_name_plural = 'VLANs' def __str__(self): @@ -999,7 +1000,7 @@ class Service(ChangeLoggedModel, CustomFieldModel): to='dcim.Device', on_delete=models.CASCADE, related_name='services', - verbose_name='device', + verbose_name=_('device'), null=True, blank=True ) @@ -1022,13 +1023,13 @@ class Service(ChangeLoggedModel, CustomFieldModel): MinValueValidator(SERVICE_PORT_MIN), MaxValueValidator(SERVICE_PORT_MAX) ], - verbose_name='Port number' + verbose_name=_('Port number') ) ipaddresses = models.ManyToManyField( to='ipam.IPAddress', related_name='services', blank=True, - verbose_name='IP addresses' + verbose_name=_('IP addresses') ) description = models.CharField( max_length=200, diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index d624ba134..3bbceb7f2 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables from django_tables2.utils import Accessor +from django.utils.translation import gettext as _ from dcim.models import Interface from tenancy.tables import COL_TENANT @@ -191,13 +192,13 @@ class VRFTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() rd = tables.Column( - verbose_name='RD' + verbose_name=_('RD') ) tenant = tables.TemplateColumn( template_code=COL_TENANT ) enforce_unique = BooleanColumn( - verbose_name='Unique' + verbose_name=_('Unique') ) tags = TagColumn( url_name='ipam:vrf_list' @@ -217,10 +218,10 @@ class RIRTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() is_private = BooleanColumn( - verbose_name='Private' + verbose_name=_('Private') ) aggregate_count = tables.Column( - verbose_name='Aggregates' + verbose_name=_('Aggregates') ) actions = tables.TemplateColumn( template_code=RIR_ACTIONS, @@ -237,32 +238,32 @@ class RIRTable(BaseTable): class RIRDetailTable(RIRTable): stats_total = tables.Column( accessor='stats.total', - verbose_name='Total', + verbose_name=_('Total'), footer=lambda table: sum(r.stats['total'] for r in table.data) ) stats_active = tables.Column( accessor='stats.active', - verbose_name='Active', + verbose_name=_('Active'), footer=lambda table: sum(r.stats['active'] for r in table.data) ) stats_reserved = tables.Column( accessor='stats.reserved', - verbose_name='Reserved', + verbose_name=_('Reserved'), footer=lambda table: sum(r.stats['reserved'] for r in table.data) ) stats_deprecated = tables.Column( accessor='stats.deprecated', - verbose_name='Deprecated', + verbose_name=_('Deprecated'), footer=lambda table: sum(r.stats['deprecated'] for r in table.data) ) stats_available = tables.Column( accessor='stats.available', - verbose_name='Available', + verbose_name=_('Available'), footer=lambda table: sum(r.stats['available'] for r in table.data) ) utilization = tables.TemplateColumn( template_code=RIR_UTILIZATION, - verbose_name='Utilization' + verbose_name=_('Utilization') ) class Meta(RIRTable.Meta): @@ -283,11 +284,11 @@ class RIRDetailTable(RIRTable): class AggregateTable(BaseTable): pk = ToggleColumn() prefix = tables.LinkColumn( - verbose_name='Aggregate' + verbose_name=_('Aggregate') ) date_added = tables.DateColumn( format="Y-m-d", - verbose_name='Added' + verbose_name=_('Added') ) class Meta(BaseTable.Meta): @@ -297,7 +298,7 @@ class AggregateTable(BaseTable): class AggregateDetailTable(AggregateTable): child_count = tables.Column( - verbose_name='Prefixes' + verbose_name=_('Prefixes') ) utilization = tables.TemplateColumn( template_code=UTILIZATION_GRAPH, @@ -320,11 +321,11 @@ class RoleTable(BaseTable): pk = ToggleColumn() prefix_count = tables.TemplateColumn( template_code=ROLE_PREFIX_COUNT, - verbose_name='Prefixes' + verbose_name=_('Prefixes') ) vlan_count = tables.TemplateColumn( template_code=ROLE_VLAN_COUNT, - verbose_name='VLANs' + verbose_name=_('VLANs') ) actions = tables.TemplateColumn( template_code=ROLE_ACTIONS, @@ -353,7 +354,7 @@ class PrefixTable(BaseTable): ) vrf = tables.TemplateColumn( template_code=VRF_LINK, - verbose_name='VRF' + verbose_name=_('VRF') ) tenant = tables.TemplateColumn( template_code=TENANT_LINK @@ -365,13 +366,13 @@ class PrefixTable(BaseTable): vlan = tables.LinkColumn( viewname='ipam:vlan', args=[Accessor('vlan.pk')], - verbose_name='VLAN' + verbose_name=_('VLAN') ) role = tables.TemplateColumn( template_code=PREFIX_ROLE_LINK ) is_pool = BooleanColumn( - verbose_name='Pool' + verbose_name=_('Pool') ) add_prefetch = False @@ -415,11 +416,11 @@ class IPAddressTable(BaseTable): pk = ToggleColumn() address = tables.TemplateColumn( template_code=IPADDRESS_LINK, - verbose_name='IP Address' + verbose_name=_('IP Address') ) vrf = tables.TemplateColumn( template_code=VRF_LINK, - verbose_name='VRF' + verbose_name=_('VRF') ) status = tables.TemplateColumn( template_code=STATUS_LABEL @@ -450,7 +451,7 @@ class IPAddressDetailTable(IPAddressTable): viewname='ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, - verbose_name='NAT (Inside)' + verbose_name=_('NAT (Inside)') ) tenant = tables.TemplateColumn( template_code=COL_TENANT @@ -472,7 +473,7 @@ class IPAddressDetailTable(IPAddressTable): class IPAddressAssignTable(BaseTable): address = tables.TemplateColumn( template_code=IPADDRESS_ASSIGN_LINK, - verbose_name='IP Address' + verbose_name=_('IP Address') ) status = tables.TemplateColumn( template_code=STATUS_LABEL @@ -496,11 +497,11 @@ class InterfaceIPAddressTable(BaseTable): List IP addresses assigned to a specific Interface. """ address = tables.LinkColumn( - verbose_name='IP Address' + verbose_name=_('IP Address') ) vrf = tables.TemplateColumn( template_code=VRF_LINK, - verbose_name='VRF' + verbose_name=_('VRF') ) status = tables.TemplateColumn( template_code=STATUS_LABEL @@ -526,7 +527,7 @@ class VLANGroupTable(BaseTable): args=[Accessor('site.slug')] ) vlan_count = tables.Column( - verbose_name='VLANs' + verbose_name=_('VLANs') ) actions = tables.TemplateColumn( template_code=VLANGROUP_ACTIONS, @@ -548,7 +549,7 @@ class VLANTable(BaseTable): pk = ToggleColumn() vid = tables.TemplateColumn( template_code=VLAN_LINK, - verbose_name='ID' + verbose_name=_('ID') ) site = tables.LinkColumn( viewname='dcim:site', @@ -580,7 +581,7 @@ class VLANDetailTable(VLANTable): prefixes = tables.TemplateColumn( template_code=VLAN_PREFIXES, orderable=False, - verbose_name='Prefixes' + verbose_name=_('Prefixes') ) tenant = tables.TemplateColumn( template_code=COL_TENANT @@ -599,7 +600,7 @@ class VLANMemberTable(BaseTable): order_by=['device', 'virtual_machine'] ) name = tables.LinkColumn( - verbose_name='Interface' + verbose_name=_('Interface') ) untagged = tables.TemplateColumn( template_code=VLAN_MEMBER_UNTAGGED, @@ -623,7 +624,7 @@ class InterfaceVLANTable(BaseTable): vid = tables.LinkColumn( viewname='ipam:vlan', args=[Accessor('pk')], - verbose_name='ID' + verbose_name=_('ID') ) tagged = BooleanColumn() site = tables.LinkColumn( @@ -632,7 +633,7 @@ class InterfaceVLANTable(BaseTable): ) group = tables.Column( accessor=Accessor('group.name'), - verbose_name='Group' + verbose_name=_('Group') ) tenant = tables.TemplateColumn( template_code=COL_TENANT diff --git a/netbox/locale/zh_Hans/LC_MESSAGES/django.po b/netbox/locale/zh_Hans/LC_MESSAGES/django.po new file mode 100644 index 000000000..06e851172 --- /dev/null +++ b/netbox/locale/zh_Hans/LC_MESSAGES/django.po @@ -0,0 +1,1968 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-08-05 19:22+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: circuits/apps.py:7 circuits/tables.py:33 circuits/tables.py:55 +#: netbox/forms.py:8 netbox/forms.py:10 +msgid "Circuits" +msgstr "" + +#: circuits/filters.py:25 circuits/filters.py:79 circuits/filters.py:151 +#: circuits/forms.py:104 circuits/forms.py:268 dcim/filters.py:84 +#: dcim/filters.py:183 dcim/filters.py:262 dcim/filters.py:328 +#: dcim/filters.py:509 dcim/filters.py:707 dcim/filters.py:821 +#: dcim/filters.py:950 dcim/filters.py:1023 dcim/filters.py:1079 +#: dcim/filters.py:1238 dcim/filters.py:1286 dcim/forms.py:69 dcim/forms.py:213 +#: dcim/forms.py:343 dcim/forms.py:659 dcim/forms.py:857 dcim/forms.py:966 +#: dcim/forms.py:2042 dcim/forms.py:3825 dcim/forms.py:4046 dcim/forms.py:4232 +#: dcim/forms.py:4357 dcim/forms.py:4575 extras/filters.py:111 +#: extras/filters.py:130 extras/filters.py:252 extras/forms.py:185 +#: extras/forms.py:289 extras/forms.py:397 ipam/filters.py:36 +#: ipam/filters.py:64 ipam/filters.py:114 ipam/filters.py:125 +#: ipam/filters.py:275 ipam/filters.py:429 ipam/filters.py:498 ipam/forms.py:97 +#: ipam/forms.py:206 ipam/forms.py:437 ipam/forms.py:854 ipam/forms.py:866 +#: ipam/forms.py:1112 ipam/forms.py:1208 netbox/forms.py:44 +#: secrets/filters.py:28 secrets/forms.py:183 tenancy/filters.py:37 +#: tenancy/forms.py:106 utilities/filters.py:278 virtualization/filters.py:41 +#: virtualization/filters.py:110 virtualization/filters.py:208 +#: virtualization/forms.py:489 +msgid "Search" +msgstr "" + +#: circuits/filters.py:31 circuits/filters.py:120 dcim/filters.py:94 +#: dcim/filters.py:138 dcim/filters.py:189 dcim/filters.py:551 +#: dcim/filters.py:713 dcim/filters.py:956 dcim/filters.py:1029 +#: dcim/filters.py:1244 dcim/filters.py:1292 ipam/filters.py:165 +#: ipam/filters.py:401 ipam/filters.py:435 virtualization/filters.py:47 +#: virtualization/filters.py:146 +msgid "Region (ID)" +msgstr "" + +#: circuits/filters.py:38 circuits/filters.py:127 dcim/filters.py:101 +#: dcim/filters.py:145 dcim/filters.py:196 dcim/filters.py:558 +#: dcim/filters.py:720 dcim/filters.py:963 dcim/filters.py:1036 +#: dcim/filters.py:1251 dcim/filters.py:1299 extras/filters.py:141 +#: ipam/filters.py:172 ipam/filters.py:408 ipam/filters.py:442 +#: virtualization/filters.py:54 virtualization/filters.py:153 +msgid "Region (slug)" +msgstr "" + +#: circuits/filters.py:43 dcim/forms.py:3412 dcim/forms.py:3545 +#: dcim/forms.py:3584 dcim/tables.py:259 extras/filters.py:146 +#: ipam/forms.py:533 +msgid "Site" +msgstr "" + +#: circuits/filters.py:49 circuits/filters.py:114 circuits/filters.py:165 +#: dcim/filters.py:155 dcim/filters.py:206 dcim/filters.py:277 +#: dcim/filters.py:1142 dcim/filters.py:1173 dcim/filters.py:1204 +#: extras/filters.py:152 ipam/filters.py:182 ipam/filters.py:418 +#: ipam/filters.py:452 virtualization/filters.py:64 +#: virtualization/filters.py:164 +msgid "Site (slug)" +msgstr "" + +#: circuits/filters.py:83 +msgid "Provider (ID)" +msgstr "" + +#: circuits/filters.py:89 +msgid "Provider (slug)" +msgstr "" + +#: circuits/filters.py:93 +msgid "Circuit type (ID)" +msgstr "" + +#: circuits/filters.py:99 +msgid "Circuit type (slug)" +msgstr "" + +#: circuits/filters.py:108 circuits/filters.py:159 dcim/filters.py:149 +#: dcim/filters.py:200 dcim/filters.py:271 dcim/filters.py:562 +#: dcim/filters.py:725 dcim/filters.py:968 dcim/filters.py:1041 +#: dcim/filters.py:1255 dcim/filters.py:1304 ipam/filters.py:176 +#: ipam/filters.py:412 ipam/filters.py:446 virtualization/filters.py:58 +#: virtualization/filters.py:158 +msgid "Site (ID)" +msgstr "" + +#: circuits/filters.py:155 dcim/forms.py:3555 +msgid "Circuit" +msgstr "" + +#: circuits/forms.py:68 circuits/forms.py:127 circuits/models.py:42 +#: dcim/forms.py:320 dcim/models/__init__.py:191 +msgid "ASN" +msgstr "" + +#: circuits/forms.py:73 circuits/models.py:48 +msgid "Account number" +msgstr "" + +#: circuits/forms.py:77 +msgid "Portal" +msgstr "" + +#: circuits/forms.py:82 circuits/models.py:56 +msgid "NOC contact" +msgstr "" + +#: circuits/forms.py:87 circuits/models.py:60 +msgid "Admin contact" +msgstr "" + +#: circuits/forms.py:91 circuits/forms.py:252 dcim/forms.py:645 +#: dcim/forms.py:4562 virtualization/forms.py:151 virtualization/forms.py:472 +msgid "Comments" +msgstr "" + +#: circuits/forms.py:153 +msgid "Name of circuit type" +msgstr "" + +#: circuits/forms.py:193 +msgid "Assigned provider" +msgstr "" + +#: circuits/forms.py:198 +msgid "Type of circuit" +msgstr "" + +#: circuits/forms.py:203 dcim/forms.py:271 dcim/forms.py:533 dcim/forms.py:1881 +#: dcim/forms.py:4466 ipam/forms.py:355 ipam/forms.py:719 ipam/forms.py:1038 +msgid "Operational status" +msgstr "" + +#: circuits/forms.py:209 dcim/forms.py:283 dcim/forms.py:804 dcim/forms.py:1861 +#: ipam/forms.py:59 ipam/forms.py:333 ipam/forms.py:715 ipam/forms.py:1034 +#: virtualization/forms.py:120 virtualization/forms.py:410 +msgid "Assigned tenant" +msgstr "" + +#: circuits/forms.py:244 circuits/forms.py:313 +msgid "Commit rate (Kbps)" +msgstr "" + +#: circuits/models.py:43 dcim/models/__init__.py:192 +msgid "32-bit autonomous system number" +msgstr "" + +#: circuits/models.py:52 +msgid "Portal URL" +msgstr "" + +#: circuits/models.py:147 +msgid "Circuit ID" +msgstr "" + +#: circuits/models.py:174 +msgid "Date installed" +msgstr "" + +#: circuits/models.py:262 +msgid "Termination" +msgstr "" + +#: circuits/models.py:281 +msgid "Port speed (Kbps)" +msgstr "" + +#: circuits/models.py:286 +msgid "Upstream speed (Kbps)" +msgstr "" + +#: circuits/models.py:287 +msgid "Upstream speed, if different from port speed" +msgstr "" + +#: circuits/models.py:292 +msgid "Cross-connect ID" +msgstr "" + +#: circuits/models.py:297 +msgid "Patch panel/port(s)" +msgstr "" + +#: circuits/tables.py:76 dcim/tables.py:1035 ipam/models.py:893 +#: ipam/tables.py:552 ipam/tables.py:627 +msgid "ID" +msgstr "" + +#: circuits/tables.py:89 +msgid "A Side" +msgstr "" + +#: circuits/tables.py:92 +msgid "Z Side" +msgstr "" + +#: circuits/views.py:254 +msgid "Swapped terminations for circuit {}." +msgstr "" + +#: dcim/apps.py:6 netbox/forms.py:12 +msgid "DCIM" +msgstr "" + +#: dcim/filters.py:67 +msgid "Parent region (ID)" +msgstr "" + +#: dcim/filters.py:73 +msgid "Parent region (slug)" +msgstr "" + +#: dcim/filters.py:159 dcim/filters.py:212 dcim/filters.py:283 +#: dcim/filters.py:574 dcim/filters.py:1267 dcim/forms.py:4384 +msgid "Rack group (ID)" +msgstr "" + +#: dcim/filters.py:165 dcim/filters.py:219 dcim/filters.py:290 +msgid "Rack group (slug)" +msgstr "" + +#: dcim/filters.py:227 dcim/filters.py:529 ipam/filters.py:194 +#: ipam/filters.py:466 secrets/filters.py:32 virtualization/filters.py:168 +msgid "Role (ID)" +msgstr "" + +#: dcim/filters.py:233 dcim/filters.py:535 extras/filters.py:163 +#: ipam/filters.py:200 ipam/filters.py:472 secrets/filters.py:38 +#: virtualization/filters.py:174 +msgid "Role (slug)" +msgstr "" + +#: dcim/filters.py:266 dcim/filters.py:579 dcim/filters.py:1319 +msgid "Rack (ID)" +msgstr "" + +#: dcim/filters.py:294 +msgid "User (ID)" +msgstr "" + +#: dcim/filters.py:300 +msgid "User (name)" +msgstr "" + +#: dcim/filters.py:332 dcim/filters.py:486 dcim/filters.py:514 +#: dcim/filters.py:991 +msgid "Manufacturer (ID)" +msgstr "" + +#: dcim/filters.py:338 dcim/filters.py:492 dcim/filters.py:520 +#: dcim/filters.py:997 +msgid "Manufacturer (slug)" +msgstr "" + +#: dcim/filters.py:342 dcim/filters.py:621 dcim/forms.py:983 dcim/forms.py:2145 +msgid "Has console ports" +msgstr "" + +#: dcim/filters.py:346 dcim/filters.py:625 dcim/forms.py:990 dcim/forms.py:2152 +msgid "Has console server ports" +msgstr "" + +#: dcim/filters.py:350 dcim/filters.py:629 dcim/forms.py:997 dcim/forms.py:2159 +msgid "Has power ports" +msgstr "" + +#: dcim/filters.py:354 dcim/filters.py:633 dcim/forms.py:1004 +#: dcim/forms.py:2166 +msgid "Has power outlets" +msgstr "" + +#: dcim/filters.py:358 dcim/filters.py:637 dcim/forms.py:1011 +#: dcim/forms.py:2173 +msgid "Has interfaces" +msgstr "" + +#: dcim/filters.py:362 dcim/filters.py:641 dcim/forms.py:1018 +#: dcim/forms.py:2180 +msgid "Has pass-through ports" +msgstr "" + +#: dcim/filters.py:366 dcim/filters.py:645 +msgid "Has device bays" +msgstr "" + +#: dcim/filters.py:415 dcim/filters.py:524 +msgid "Device type (ID)" +msgstr "" + +#: dcim/filters.py:539 virtualization/filters.py:178 +msgid "Platform (ID)" +msgstr "" + +#: dcim/filters.py:545 extras/filters.py:174 virtualization/filters.py:184 +msgid "Platform (slug)" +msgstr "" + +#: dcim/filters.py:568 dcim/filters.py:731 dcim/filters.py:974 +#: dcim/filters.py:1047 dcim/filters.py:1261 dcim/filters.py:1310 +msgid "Site name (slug)" +msgstr "" + +#: dcim/filters.py:583 +msgid "VM cluster (ID)" +msgstr "" + +#: dcim/filters.py:589 +msgid "Device model (slug)" +msgstr "" + +#: dcim/filters.py:597 dcim/forms.py:955 dcim/models/__init__.py:961 +msgid "Is full depth" +msgstr "" + +#: dcim/filters.py:601 dcim/forms.py:2127 dcim/forms.py:2677 +#: virtualization/filters.py:188 virtualization/filters.py:222 +#: virtualization/forms.py:562 +msgid "MAC address" +msgstr "" + +#: dcim/filters.py:608 dcim/forms.py:2131 +msgid "Has a primary IP" +msgstr "" + +#: dcim/filters.py:613 +msgid "Virtual chassis (ID)" +msgstr "" + +#: dcim/filters.py:617 +msgid "Is a virtual chassis member" +msgstr "" + +#: dcim/filters.py:735 dcim/filters.py:833 dcim/filters.py:978 +#: ipam/filters.py:311 ipam/filters.py:502 secrets/filters.py:42 +msgid "Device (ID)" +msgstr "" + +#: dcim/filters.py:741 dcim/filters.py:983 ipam/filters.py:306 +#: ipam/filters.py:508 secrets/filters.py:48 +msgid "Device (name)" +msgstr "" + +#: dcim/filters.py:828 dcim/forms.py:96 dcim/forms.py:3436 dcim/forms.py:3879 +#: dcim/forms.py:3902 dcim/forms.py:3921 dcim/forms.py:3940 dcim/forms.py:4073 +#: ipam/forms.py:558 +msgid "Device" +msgstr "" + +#: dcim/filters.py:842 +msgid "Kind of interface" +msgstr "" + +#: dcim/filters.py:847 +msgid "LAG interface (ID)" +msgstr "" + +#: dcim/filters.py:853 ipam/forms.py:351 +msgid "Assigned VLAN" +msgstr "" + +#: dcim/filters.py:857 +msgid "Assigned VID" +msgstr "" + +#: dcim/filters.py:987 +msgid "Parent inventory item (ID)" +msgstr "" + +#: dcim/filters.py:1052 tenancy/filters.py:88 +msgid "Tenant (ID)" +msgstr "" + +#: dcim/filters.py:1058 extras/filters.py:212 tenancy/filters.py:94 +msgid "Tenant (slug)" +msgstr "" + +#: dcim/filters.py:1314 +msgid "Power panel (ID)" +msgstr "" + +#: dcim/forms.py:140 +msgid "Use regular expressions" +msgstr "" + +#: dcim/forms.py:201 +msgid "Name of parent region" +msgstr "" + +#: dcim/forms.py:277 +msgid "Assigned region" +msgstr "" + +#: dcim/forms.py:391 dcim/forms.py:1908 dcim/forms.py:4444 ipam/forms.py:339 +#: ipam/forms.py:940 ipam/forms.py:1022 virtualization/forms.py:114 +msgid "Assigned site" +msgstr "" + +#: dcim/forms.py:397 +msgid "Parent rack group" +msgstr "" + +#: dcim/forms.py:399 +msgid "Rack group not found." +msgstr "" + +#: dcim/forms.py:528 +msgid "Name of assigned tenant" +msgstr "" + +#: dcim/forms.py:539 +msgid "Name of assigned role" +msgstr "" + +#: dcim/forms.py:544 +msgid "Rack type" +msgstr "" + +#: dcim/forms.py:548 +msgid "Rail-to-rail width (in inches)" +msgstr "" + +#: dcim/forms.py:553 +msgid "Unit for outer dimensions" +msgstr "" + +#: dcim/forms.py:605 dcim/forms.py:2025 +msgid "Serial Number" +msgstr "" + +#: dcim/forms.py:623 dcim/models/__init__.py:495 dcim/models/__init__.py:957 +msgid "Height (U)" +msgstr "" + +#: dcim/forms.py:628 dcim/models/__init__.py:501 +msgid "Descending units" +msgstr "" + +#: dcim/forms.py:688 dcim/forms.py:870 dcim/forms.py:2070 +msgid "Rack group" +msgstr "" + +#: dcim/forms.py:718 dcim/forms.py:793 dcim/forms.py:2080 dcim/forms.py:3423 +#: dcim/forms.py:3853 dcim/forms.py:4461 dcim/forms.py:4611 ipam/forms.py:544 +msgid "Rack" +msgstr "" + +#: dcim/forms.py:762 +msgid "" +"Comma-separated list of numeric unit IDs. A range may be specified using a " +"hyphen." +msgstr "" + +#: dcim/forms.py:782 +msgid "Parent site" +msgstr "" + +#: dcim/forms.py:788 dcim/forms.py:1914 dcim/forms.py:4455 +msgid "Rack's group (if any)" +msgstr "" + +#: dcim/forms.py:798 +msgid "Comma-separated list of individual unit numbers" +msgstr "" + +#: dcim/forms.py:1050 dcim/forms.py:2200 dcim/forms.py:2210 dcim/forms.py:3461 +#: dcim/forms.py:3471 dcim/forms.py:3481 dcim/forms.py:3491 dcim/forms.py:3501 +#: dcim/forms.py:3514 dcim/forms.py:3524 dcim/forms.py:3616 dcim/forms.py:3972 +#: dcim/models/device_components.py:980 dcim/models/device_components.py:1060 +#: extras/models/models.py:219 virtualization/forms.py:649 +#: virtualization/forms.py:799 +msgid "Name" +msgstr "" + +#: dcim/forms.py:1142 dcim/forms.py:1164 +#: dcim/models/device_component_templates.py:158 +#: dcim/models/device_components.py:373 +msgid "Maximum power draw (watts)" +msgstr "" + +#: dcim/forms.py:1147 dcim/forms.py:1169 +#: dcim/models/device_component_templates.py:164 +#: dcim/models/device_components.py:379 +msgid "Allocated power draw (watts)" +msgstr "" + +#: dcim/forms.py:1286 dcim/forms.py:1303 dcim/forms.py:2776 +#: dcim/models/device_component_templates.py:273 +msgid "Management only" +msgstr "" + +#: dcim/forms.py:1340 dcim/forms.py:3033 +msgid "Rear ports" +msgstr "" + +#: dcim/forms.py:1341 dcim/forms.py:3034 +msgid "Select one rear port assignment for each front port being created." +msgstr "" + +#: dcim/forms.py:1427 dcim/forms.py:3209 +msgid "The number of front ports which may be mapped to each rear port" +msgstr "" + +#: dcim/forms.py:1645 +msgid "Limit platform assignments to this manufacturer" +msgstr "" + +#: dcim/forms.py:1676 dcim/models/__init__.py:1369 +msgid "The lowest-numbered unit occupied by the device" +msgstr "" + +#: dcim/forms.py:1855 secrets/forms.py:139 +msgid "Assigned role" +msgstr "" + +#: dcim/forms.py:1866 +msgid "Device type manufacturer" +msgstr "" + +#: dcim/forms.py:1871 +msgid "Device type model" +msgstr "" + +#: dcim/forms.py:1877 virtualization/forms.py:416 +msgid "Assigned platform" +msgstr "" + +#: dcim/forms.py:1887 +msgid "Virtualization cluster" +msgstr "" + +#: dcim/forms.py:1920 +msgid "Assigned rack" +msgstr "" + +#: dcim/forms.py:1925 +msgid "Mounted rack face" +msgstr "" + +#: dcim/forms.py:1955 +msgid "Parent device" +msgstr "" + +#: dcim/forms.py:1960 +msgid "Device bay in which this device is installed" +msgstr "" + +#: dcim/forms.py:2096 +msgid "Manufacturer" +msgstr "" + +#: dcim/forms.py:2106 +msgid "Model" +msgstr "" + +#: dcim/forms.py:2138 +msgid "Virtual chassis member" +msgstr "" + +#: dcim/forms.py:2432 +msgid "Maximum draw in watts" +msgstr "" + +#: dcim/forms.py:2437 +msgid "Allocated draw in watts" +msgstr "" + +#: dcim/forms.py:2622 +msgid "Local power port which feeds this outlet" +msgstr "" + +#: dcim/forms.py:2627 +msgid "Electrical phase (for three-phase circuits)" +msgstr "" + +#: dcim/forms.py:2686 dcim/models/device_components.py:669 +msgid "Untagged VLAN" +msgstr "" + +#: dcim/forms.py:2698 dcim/models/device_components.py:675 +msgid "Tagged VLANs" +msgstr "" + +#: dcim/forms.py:2724 virtualization/forms.py:610 +msgid "802.1Q Mode" +msgstr "" + +#: dcim/forms.py:2761 dcim/models/device_components.py:633 +msgid "Parent LAG" +msgstr "" + +#: dcim/forms.py:2768 dcim/models/device_components.py:651 +#: virtualization/forms.py:664 virtualization/forms.py:736 +msgid "MTU" +msgstr "" + +#: dcim/forms.py:2772 dcim/models/device_components.py:645 +#: virtualization/forms.py:668 +msgid "MAC Address" +msgstr "" + +#: dcim/forms.py:2777 dcim/models/device_components.py:656 +msgid "This interface is used only for out-of-band management" +msgstr "" + +#: dcim/forms.py:2942 +msgid "Parent LAG interface" +msgstr "" + +#: dcim/forms.py:2946 +msgid "Physical medium" +msgstr "" + +#: dcim/forms.py:2951 +msgid "IEEE 802.1Q operational mode (for L2 interfaces)" +msgstr "" + +#: dcim/forms.py:3133 +msgid "Corresponding rear port" +msgstr "" + +#: dcim/forms.py:3137 dcim/forms.py:3260 dcim/forms.py:3691 +msgid "Physical medium classification" +msgstr "" + +#: dcim/forms.py:3144 +msgid "Mapped position on corresponding rear port" +msgstr "" + +#: dcim/forms.py:3268 +msgid "Number of front ports which may be mapped" +msgstr "" + +#: dcim/forms.py:3305 +msgid "Child Device" +msgstr "" + +#: dcim/forms.py:3306 +msgid "" +"Child devices must first be created and assigned to the site/rack of the " +"parent device." +msgstr "" + +#: dcim/forms.py:3365 +msgid "Child device installed within this bay" +msgstr "" + +#: dcim/forms.py:3367 +msgid "Child device not found." +msgstr "" + +#: dcim/forms.py:3535 +msgid "Provider" +msgstr "" + +#: dcim/forms.py:3564 +msgid "Side" +msgstr "" + +#: dcim/forms.py:3596 +msgid "Rack Group" +msgstr "" + +#: dcim/forms.py:3607 +msgid "Power Panel" +msgstr "" + +#: dcim/forms.py:3644 +msgid "Maximum length is 32767 (any unit)" +msgstr "" + +#: dcim/forms.py:3654 +msgid "Side A device" +msgstr "" + +#: dcim/forms.py:3660 +msgid "Side A type" +msgstr "" + +#: dcim/forms.py:3663 +msgid "Side A component name" +msgstr "" + +#: dcim/forms.py:3670 +msgid "Side B device" +msgstr "" + +#: dcim/forms.py:3676 +msgid "Side B type" +msgstr "" + +#: dcim/forms.py:3679 +msgid "Side B component name" +msgstr "" + +#: dcim/forms.py:3686 +msgid "Connection status" +msgstr "" + +#: dcim/forms.py:3696 +msgid "Length unit" +msgstr "" + +#: dcim/forms.py:3981 dcim/forms.py:4029 dcim/models/device_components.py:1076 +msgid "Part ID" +msgstr "" + +#: dcim/forms.py:4142 dcim/tables.py:630 +msgid "Position" +msgstr "" + +#: dcim/forms.py:4143 +msgid "Priority" +msgstr "" + +#: dcim/forms.py:4306 +msgid "Name of parent site" +msgstr "" + +#: dcim/forms.py:4449 +msgid "Upstream power panel" +msgstr "" + +#: dcim/forms.py:4471 +msgid "Primary or redundant" +msgstr "" + +#: dcim/forms.py:4476 +msgid "Supply type (AC/DC)" +msgstr "" + +#: dcim/forms.py:4481 +msgid "Single or three-phase" +msgstr "" + +#: dcim/forms.py:4603 +msgid "Power panel" +msgstr "" + +#: dcim/models/__init__.py:186 +msgid "Local facility ID or description" +msgstr "" + +#: dcim/models/__init__.py:214 +msgid "GPS coordinate (latitude)" +msgstr "" + +#: dcim/models/__init__.py:221 +msgid "GPS coordinate (longitude)" +msgstr "" + +#: dcim/models/__init__.py:233 +msgid "Contact E-mail" +msgstr "" + +#: dcim/models/__init__.py:432 +msgid "Facility ID" +msgstr "" + +#: dcim/models/__init__.py:433 +msgid "Locally-assigned identifier" +msgstr "" + +#: dcim/models/__init__.py:446 tenancy/forms.py:78 +msgid "Assigned group" +msgstr "" + +#: dcim/models/__init__.py:466 ipam/forms.py:361 ipam/forms.py:724 +#: ipam/forms.py:1044 virtualization/forms.py:404 +msgid "Functional role" +msgstr "" + +#: dcim/models/__init__.py:471 dcim/models/__init__.py:1342 +#: dcim/models/device_components.py:1082 +msgid "Serial number" +msgstr "" + +#: dcim/models/__init__.py:478 dcim/models/__init__.py:1349 +#: dcim/models/device_components.py:1090 +msgid "Asset tag" +msgstr "" + +#: dcim/models/__init__.py:479 +msgid "A unique tag used to identify this rack" +msgstr "" + +#: dcim/models/__init__.py:485 dcim/tables.py:794 dcim/tables.py:864 +#: extras/tables.py:91 extras/tables.py:123 netbox/forms.py:47 +msgid "Type" +msgstr "" + +#: dcim/models/__init__.py:490 +msgid "Width" +msgstr "" + +#: dcim/models/__init__.py:491 +msgid "Rail-to-rail width" +msgstr "" + +#: dcim/models/__init__.py:497 +msgid "Height in rack units" +msgstr "" + +#: dcim/models/__init__.py:502 +msgid "Units are numbered top-to-bottom" +msgstr "" + +#: dcim/models/__init__.py:507 +msgid "Outer dimension of rack (width)" +msgstr "" + +#: dcim/models/__init__.py:512 +msgid "Outer dimension of rack (depth)" +msgstr "" + +#: dcim/models/__init__.py:953 +msgid "Discrete part number (optional)" +msgstr "" + +#: dcim/models/__init__.py:962 +msgid "Device consumes both front and rear rack faces" +msgstr "" + +#: dcim/models/__init__.py:968 +msgid "Parent/child status" +msgstr "" + +#: dcim/models/__init__.py:969 +msgid "" +"Parent devices house child devices in device bays. Leave blank if this " +"device type is neither a parent nor a child." +msgstr "" + +#: dcim/models/__init__.py:1204 +msgid "VM Role" +msgstr "" + +#: dcim/models/__init__.py:1205 +msgid "Virtual machines may be assigned to this role" +msgstr "" + +#: dcim/models/__init__.py:1250 +msgid "Optionally limit this platform to devices of a certain manufacturer" +msgstr "" + +#: dcim/models/__init__.py:1255 +msgid "NAPALM driver" +msgstr "" + +#: dcim/models/__init__.py:1256 +msgid "The name of the NAPALM driver to use when interacting with devices" +msgstr "" + +#: dcim/models/__init__.py:1261 +msgid "NAPALM arguments" +msgstr "" + +#: dcim/models/__init__.py:1262 +msgid "" +"Additional arguments to pass when initiating the NAPALM driver (JSON format)" +msgstr "" + +#: dcim/models/__init__.py:1350 +msgid "A unique tag used to identify this device" +msgstr "" + +#: dcim/models/__init__.py:1368 +msgid "Position (U)" +msgstr "" + +#: dcim/models/__init__.py:1375 +msgid "Rack face" +msgstr "" + +#: dcim/models/__init__.py:1388 virtualization/models.py:240 +msgid "Primary IPv4" +msgstr "" + +#: dcim/models/__init__.py:1396 virtualization/models.py:248 +msgid "Primary IPv6" +msgstr "" + +#: dcim/models/__init__.py:1908 +msgid "Maximum permissible draw (percentage)" +msgstr "" + +#: dcim/models/device_component_templates.py:217 +#: dcim/models/device_components.py:540 +msgid "Phase (for three-phase feeds)" +msgstr "" + +#: dcim/models/device_components.py:256 dcim/models/device_components.py:315 +#: dcim/models/device_components.py:367 dcim/models/device_components.py:527 +msgid "Physical port type" +msgstr "" + +#: dcim/models/device_components.py:655 +msgid "OOB Management" +msgstr "" + +#: dcim/models/device_components.py:1078 +msgid "Manufacturer-assigned part identifier" +msgstr "" + +#: dcim/models/device_components.py:1091 +msgid "A unique tag used to identify this item" +msgstr "" + +#: dcim/models/device_components.py:1095 +msgid "This item was automatically discovered" +msgstr "" + +#: dcim/tables.py:200 netbox/forms.py:13 +msgid "Sites" +msgstr "" + +#: dcim/tables.py:262 netbox/forms.py:14 +msgid "Racks" +msgstr "" + +#: dcim/tables.py:319 +msgid "Height" +msgstr "" + +#: dcim/tables.py:334 dcim/tables.py:710 dcim/tables.py:741 netbox/forms.py:17 +#: virtualization/tables.py:108 +msgid "Devices" +msgstr "" + +#: dcim/tables.py:339 +msgid "Space" +msgstr "" + +#: dcim/tables.py:344 +msgid "Power" +msgstr "" + +#: dcim/tables.py:386 +msgid "Units" +msgstr "" + +#: dcim/tables.py:412 +msgid "Device Types" +msgstr "" + +#: dcim/tables.py:415 +msgid "Inventory Items" +msgstr "" + +#: dcim/tables.py:418 +msgid "Platforms" +msgstr "" + +#: dcim/tables.py:443 +msgid "Device Type" +msgstr "" + +#: dcim/tables.py:446 +msgid "Full Depth" +msgstr "" + +#: dcim/tables.py:450 +msgid "Instances" +msgstr "" + +#: dcim/tables.py:612 +msgid "Virtual Machine" +msgstr "" + +#: dcim/tables.py:714 dcim/tables.py:745 virtualization/tables.py:112 +msgid "VMs" +msgstr "" + +#: dcim/tables.py:718 +msgid "Label" +msgstr "" + +#: dcim/tables.py:789 dcim/tables.py:861 extras/filters.py:157 +msgid "Role" +msgstr "" + +#: dcim/tables.py:800 ipam/forms.py:579 ipam/tables.py:419 ipam/tables.py:476 +#: ipam/tables.py:500 virtualization/tables.py:161 +msgid "IP Address" +msgstr "" + +#: dcim/tables.py:805 virtualization/tables.py:152 +msgid "IPv4 Address" +msgstr "" + +#: dcim/tables.py:810 virtualization/tables.py:157 +msgid "IPv6 Address" +msgstr "" + +#: dcim/tables.py:821 +msgid "VC Position" +msgstr "" + +#: dcim/tables.py:824 +msgid "VC Priority" +msgstr "" + +#: dcim/tables.py:1041 +msgid "Side A" +msgstr "" + +#: dcim/tables.py:1046 +msgid "Termination A" +msgstr "" + +#: dcim/tables.py:1052 +msgid "Side B" +msgstr "" + +#: dcim/tables.py:1057 +msgid "Termination B" +msgstr "" + +#: dcim/tables.py:1089 +msgid "Console Server" +msgstr "" + +#: dcim/tables.py:1092 +msgid "Port" +msgstr "" + +#: dcim/tables.py:1099 +msgid "Console Port" +msgstr "" + +#: dcim/tables.py:1113 +msgid "PDU" +msgstr "" + +#: dcim/tables.py:1117 +msgid "Outlet" +msgstr "" + +#: dcim/tables.py:1124 +msgid "Power Port" +msgstr "" + +#: dcim/tables.py:1137 +msgid "Device A" +msgstr "" + +#: dcim/tables.py:1143 +msgid "Interface A" +msgstr "" + +#: dcim/tables.py:1149 +msgid "Device B" +msgstr "" + +#: dcim/tables.py:1155 +msgid "Interface B" +msgstr "" + +#: dcim/tables.py:1199 +msgid "Members" +msgstr "" + +#: dcim/tables.py:1224 +msgid "Feeds" +msgstr "" + +#: dcim/tables.py:1258 +msgid "Available power (VA)" +msgstr "" + +#: dcim/views.py:78 +msgid "Renamed {} {}" +msgstr "" + +#: dcim/views.py:123 +msgid "Disconnected {} {}" +msgstr "" + +#: dcim/views.py:1857 +msgid "Added {} to {}." +msgstr "" + +#: dcim/views.py:1892 +msgid "{} has been removed from {}." +msgstr "" + +#: extras/admin.py:24 extras/models/models.py:58 extras/models/models.py:176 +msgid "URL" +msgstr "" + +#: extras/admin.py:120 +msgid "" +"A numeric weight to influence the ordering of this link among its peers. " +"Lower weights appear first in a list." +msgstr "" + +#: extras/admin.py:122 +msgid "" +"Jinja2 template code for the link text. Reference the object as " +"{{ obj }}. Links which render as empty text will not be " +"displayed." +msgstr "" + +#: extras/admin.py:124 +msgid "" +"Jinja2 template code for the link URL. Reference the object as " +"{{ obj }}." +msgstr "" + +#: extras/filters.py:135 +msgid "Region" +msgstr "" + +#: extras/filters.py:168 +msgid "Platform" +msgstr "" + +#: extras/filters.py:179 +msgid "Cluster group" +msgstr "" + +#: extras/filters.py:185 virtualization/filters.py:125 +msgid "Cluster group (slug)" +msgstr "" + +#: extras/filters.py:190 extras/forms.py:334 virtualization/forms.py:512 +msgid "Cluster" +msgstr "" + +#: extras/filters.py:195 +msgid "Tenant group" +msgstr "" + +#: extras/filters.py:201 tenancy/filters.py:50 +msgid "Tenant group (slug)" +msgstr "" + +#: extras/filters.py:206 +msgid "Tenant" +msgstr "" + +#: extras/filters.py:218 +msgid "Tag (slug)" +msgstr "" + +#: extras/filters.py:242 extras/forms.py:369 +msgid "Has local config context data" +msgstr "" + +#: extras/forms.py:400 +msgid "After" +msgstr "" + +#: extras/forms.py:405 +msgid "Before" +msgstr "" + +#: extras/forms.py:424 +msgid "Object Type" +msgstr "" + +#: extras/forms.py:436 +msgid "Commit changes" +msgstr "" + +#: extras/forms.py:437 +msgid "Commit changes to the database (uncheck for a dry-run)" +msgstr "" + +#: extras/models/customfields.py:74 +msgid "Object(s)" +msgstr "" + +#: extras/models/customfields.py:76 +msgid "The object(s) to which this field applies." +msgstr "" + +#: extras/models/customfields.py:90 +msgid "" +"Name of the field as displayed to users (if not provided, the field's name " +"will be used)" +msgstr "" + +#: extras/models/customfields.py:99 +msgid "" +"If true, this field is required when creating new objects or editing an " +"existing object." +msgstr "" + +#: extras/models/customfields.py:106 +msgid "" +"Loose matches any instance of a given string; exact matches the entire field." +msgstr "" + +#: extras/models/customfields.py:112 +msgid "Default value for the field. Use \"true\" or \"false\" for booleans." +msgstr "" + +#: extras/models/customfields.py:116 +msgid "Fields with higher weights appear lower in a form." +msgstr "" + +#: extras/models/customfields.py:288 +msgid "Higher weights appear lower in the list" +msgstr "" + +#: extras/models/models.py:36 +msgid "Object types" +msgstr "" + +#: extras/models/models.py:38 +msgid "The object(s) to which this Webhook applies." +msgstr "" + +#: extras/models/models.py:46 +msgid "Call this webhook when a matching object is created." +msgstr "" + +#: extras/models/models.py:50 +msgid "Call this webhook when a matching object is updated." +msgstr "" + +#: extras/models/models.py:54 +msgid "Call this webhook when a matching object is deleted." +msgstr "" + +#: extras/models/models.py:59 +msgid "A POST will be sent to this URL when the webhook is called." +msgstr "" + +#: extras/models/models.py:68 +msgid "HTTP method" +msgstr "" + +#: extras/models/models.py:73 +msgid "HTTP content type" +msgstr "" + +#: extras/models/models.py:74 +msgid "" +"The complete list of official content types is available here." +msgstr "" + +#: extras/models/models.py:79 +msgid "" +"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 support with the same context as the " +"request body (below)." +msgstr "" + +#: extras/models/models.py:85 +msgid "" +"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." +msgstr "" + +#: extras/models/models.py:92 +msgid "" +"When provided, the request will include a 'X-Hook-Signature' header " +"containing a HMAC hex digest of the payload body using the secret as the " +"key. The secret is not transmitted in the request." +msgstr "" + +#: extras/models/models.py:99 +msgid "SSL verification" +msgstr "" + +#: extras/models/models.py:100 +msgid "Enable SSL certificate verification. Disable with caution!" +msgstr "" + +#: extras/models/models.py:106 +msgid "CA File Path" +msgstr "" + +#: extras/models/models.py:107 +msgid "" +"The specific CA certificate file to use for SSL verification. Leave blank to " +"use the system defaults." +msgstr "" + +#: extras/models/models.py:172 +msgid "Jinja2 template code for link text" +msgstr "" + +#: extras/models/models.py:177 +msgid "Jinja2 template code for link URL" +msgstr "" + +#: extras/models/models.py:185 +msgid "Links with the same group will appear as a dropdown menu" +msgstr "" + +#: extras/models/models.py:191 +msgid "" +"The class of the first link in a group will be used for the dropdown button" +msgstr "" + +#: extras/models/models.py:194 +msgid "Force link to open in a new window" +msgstr "" + +#: extras/models/models.py:228 +msgid "Source URL" +msgstr "" + +#: extras/models/models.py:232 +msgid "Link URL" +msgstr "" + +#: extras/models/models.py:288 +msgid "" +"The list of objects being exported is passed as a context variable named " +"queryset." +msgstr "" + +#: extras/models/models.py:293 +msgid "MIME type" +msgstr "" + +#: extras/models/models.py:294 +msgid "Defaults to text/plain" +msgstr "" + +#: extras/models/models.py:299 +msgid "Extension to append to the rendered filename" +msgstr "" + +#: extras/tables.py:88 extras/tables.py:127 +msgid "Object" +msgstr "" + +#: extras/tables.py:103 ipam/tables.py:246 +msgid "Active" +msgstr "" + +#: extras/tables.py:131 +msgid "Request ID" +msgstr "" + +#: ipam/apps.py:7 netbox/forms.py:22 +msgid "IPAM" +msgstr "" + +#: ipam/filters.py:72 ipam/filters.py:133 +msgid "Prefix" +msgstr "" + +#: ipam/filters.py:76 +msgid "RIR (ID)" +msgstr "" + +#: ipam/filters.py:82 +msgid "RIR (slug)" +msgstr "" + +#: ipam/filters.py:137 +msgid "Within prefix" +msgstr "" + +#: ipam/filters.py:141 +msgid "Within and including prefix" +msgstr "" + +#: ipam/filters.py:145 +msgid "Prefixes which contain this prefix or IP" +msgstr "" + +#: ipam/filters.py:149 ipam/filters.py:291 ipam/forms.py:457 ipam/forms.py:886 +msgid "Mask length" +msgstr "" + +#: ipam/filters.py:153 ipam/filters.py:295 ipam/forms.py:256 ipam/forms.py:393 +#: ipam/forms.py:463 ipam/forms.py:528 ipam/forms.py:569 ipam/forms.py:683 +#: ipam/forms.py:809 ipam/forms.py:849 ipam/forms.py:892 ipam/models.py:88 +#: ipam/models.py:318 ipam/models.py:582 ipam/tables.py:357 ipam/tables.py:423 +#: ipam/tables.py:504 +msgid "VRF" +msgstr "" + +#: ipam/filters.py:159 ipam/filters.py:301 +msgid "VRF (RD)" +msgstr "" + +#: ipam/filters.py:186 +msgid "VLAN (ID)" +msgstr "" + +#: ipam/filters.py:190 +msgid "VLAN number (1-4095)" +msgstr "" + +#: ipam/filters.py:283 +msgid "Parent prefix" +msgstr "" + +#: ipam/filters.py:287 +msgid "Address" +msgstr "" + +#: ipam/filters.py:316 ipam/filters.py:512 virtualization/filters.py:213 +msgid "Virtual machine (ID)" +msgstr "" + +#: ipam/filters.py:322 ipam/filters.py:518 +msgid "Virtual machine (name)" +msgstr "" + +#: ipam/filters.py:328 ipam/filters.py:332 +msgid "Interface (ID)" +msgstr "" + +#: ipam/filters.py:336 +msgid "Is assigned to an interface" +msgstr "" + +#: ipam/filters.py:456 +msgid "Group (ID)" +msgstr "" + +#: ipam/filters.py:462 ipam/tables.py:636 +msgid "Group" +msgstr "" + +#: ipam/forms.py:79 ipam/models.py:66 +msgid "Enforce unique space" +msgstr "" + +#: ipam/forms.py:123 +msgid "RIR name" +msgstr "" + +#: ipam/forms.py:130 ipam/models.py:127 ipam/tables.py:221 +msgid "Private" +msgstr "" + +#: ipam/forms.py:167 +msgid "Assigned RIR" +msgstr "" + +#: ipam/forms.py:183 ipam/forms.py:218 ipam/models.py:139 ipam/models.py:168 +msgid "RIR" +msgstr "" + +#: ipam/forms.py:211 ipam/forms.py:451 ipam/forms.py:880 +msgid "Address family" +msgstr "" + +#: ipam/forms.py:274 ipam/forms.py:1138 ipam/models.py:840 +msgid "VLAN group" +msgstr "" + +#: ipam/forms.py:287 ipam/models.py:333 ipam/models.py:947 ipam/tables.py:369 +msgid "VLAN" +msgstr "" + +#: ipam/forms.py:327 ipam/forms.py:709 +msgid "Assigned VRF" +msgstr "" + +#: ipam/forms.py:345 +msgid "VLAN's group (if any)" +msgstr "" + +#: ipam/forms.py:416 ipam/forms.py:504 ipam/models.py:351 +msgid "Is a pool" +msgstr "" + +#: ipam/forms.py:446 +msgid "Search within" +msgstr "" + +#: ipam/forms.py:511 +msgid "Expand prefix hierarchy" +msgstr "" + +#: ipam/forms.py:586 +msgid "Make this the primary IP for the device/VM" +msgstr "" + +#: ipam/forms.py:675 +msgid "Address pattern" +msgstr "" + +#: ipam/forms.py:730 +msgid "Parent device of assigned interface (if any)" +msgstr "" + +#: ipam/forms.py:736 +msgid "Parent VM of assigned interface (if any)" +msgstr "" + +#: ipam/forms.py:742 +msgid "Assigned interface" +msgstr "" + +#: ipam/forms.py:745 +msgid "Make this the primary IP for the assigned device" +msgstr "" + +#: ipam/forms.py:783 +msgid "No device or virtual machine specified; cannot set as primary IP" +msgstr "" + +#: ipam/forms.py:875 +msgid "Parent Prefix" +msgstr "" + +#: ipam/forms.py:909 +msgid "Assigned to an interface" +msgstr "" + +#: ipam/forms.py:1028 +msgid "Assigned VLAN group" +msgstr "" + +#: ipam/forms.py:1051 +msgid "Numeric VLAN ID (1-4095)" +msgstr "" + +#: ipam/forms.py:1052 +msgid "VLAN name" +msgstr "" + +#: ipam/forms.py:1226 +msgid "Required if not assigned to a VM" +msgstr "" + +#: ipam/forms.py:1232 +msgid "Required if not assigned to a device" +msgstr "" + +#: ipam/forms.py:1236 +msgid "IP protocol" +msgstr "" + +#: ipam/models.py:54 +msgid "Route distinguisher" +msgstr "" + +#: ipam/models.py:55 +msgid "Unique route distinguisher (as defined in RFC 4364)" +msgstr "" + +#: ipam/models.py:67 +msgid "Prevent duplicate prefixes/IP addresses within this VRF" +msgstr "" + +#: ipam/models.py:128 +msgid "IP space managed by this RIR is considered private" +msgstr "" + +#: ipam/models.py:303 +msgid "IPv4 or IPv6 network with mask" +msgstr "" + +#: ipam/models.py:339 virtualization/models.py:224 +msgid "Status" +msgstr "" + +#: ipam/models.py:340 +msgid "Operational status of this prefix" +msgstr "" + +#: ipam/models.py:348 +msgid "The primary function of this prefix" +msgstr "" + +#: ipam/models.py:353 +msgid "All IP addresses within this prefix are considered usable" +msgstr "" + +#: ipam/models.py:574 +msgid "IPv4 or IPv6 address (with mask)" +msgstr "" + +#: ipam/models.py:595 +msgid "The operational status of this IP" +msgstr "" + +#: ipam/models.py:601 +msgid "The functional role of this IP" +msgstr "" + +#: ipam/models.py:616 ipam/tables.py:454 +msgid "NAT (Inside)" +msgstr "" + +#: ipam/models.py:617 +msgid "The IP for which this address is the \"outside\" IP" +msgstr "" + +#: ipam/models.py:623 +msgid "DNS Name" +msgstr "" + +#: ipam/models.py:624 +msgid "Hostname or FQDN (not case-sensitive)" +msgstr "" + +#: ipam/models.py:667 +msgid "IP address" +msgstr "" + +#: ipam/models.py:1003 +msgid "device" +msgstr "" + +#: ipam/models.py:1026 +msgid "Port number" +msgstr "" + +#: ipam/models.py:1032 netbox/forms.py:26 +msgid "IP addresses" +msgstr "" + +#: ipam/tables.py:195 +msgid "RD" +msgstr "" + +#: ipam/tables.py:201 +msgid "Unique" +msgstr "" + +#: ipam/tables.py:224 netbox/forms.py:24 +msgid "Aggregates" +msgstr "" + +#: ipam/tables.py:241 +msgid "Total" +msgstr "" + +#: ipam/tables.py:251 +msgid "Reserved" +msgstr "" + +#: ipam/tables.py:256 +msgid "Deprecated" +msgstr "" + +#: ipam/tables.py:261 +msgid "Available" +msgstr "" + +#: ipam/tables.py:266 +msgid "Utilization" +msgstr "" + +#: ipam/tables.py:287 +msgid "Aggregate" +msgstr "" + +#: ipam/tables.py:291 +msgid "Added" +msgstr "" + +#: ipam/tables.py:301 ipam/tables.py:324 ipam/tables.py:584 netbox/forms.py:25 +msgid "Prefixes" +msgstr "" + +#: ipam/tables.py:328 ipam/tables.py:530 netbox/forms.py:27 +msgid "VLANs" +msgstr "" + +#: ipam/tables.py:375 +msgid "Pool" +msgstr "" + +#: ipam/tables.py:603 +msgid "Interface" +msgstr "" + +#: netbox/forms.py:7 +msgid "All Objects" +msgstr "" + +#: netbox/forms.py:9 +msgid "Providers" +msgstr "" + +#: netbox/forms.py:15 +msgid "Rack Groups" +msgstr "" + +#: netbox/forms.py:16 +msgid "Device types" +msgstr "" + +#: netbox/forms.py:18 +msgid "Virtual Chassis" +msgstr "" + +#: netbox/forms.py:19 +msgid "Cables" +msgstr "" + +#: netbox/forms.py:20 +msgid "Power Feeds" +msgstr "" + +#: netbox/forms.py:23 +msgid "VRFs" +msgstr "" + +#: netbox/forms.py:29 netbox/forms.py:30 secrets/tables.py:25 +msgid "Secrets" +msgstr "" + +#: netbox/forms.py:32 +msgid "Tenancy" +msgstr "" + +#: netbox/forms.py:33 tenancy/tables.py:46 +msgid "Tenants" +msgstr "" + +#: netbox/forms.py:35 +msgid "Virtualization" +msgstr "" + +#: netbox/forms.py:36 virtualization/tables.py:55 virtualization/tables.py:77 +msgid "Clusters" +msgstr "" + +#: netbox/forms.py:37 +msgid "Virtual machines" +msgstr "" + +#: secrets/forms.py:24 +msgid "" +"OpenSSH line format is not supported. Please ensure that your public is in " +"PEM (base64) format." +msgstr "" + +#: secrets/forms.py:28 +msgid "Invalid RSA key. Please ensure that your key is in PEM (base64) format." +msgstr "" + +#: secrets/forms.py:32 +msgid "This looks like a public key. Please provide your private RSA key." +msgstr "" + +#: secrets/forms.py:34 +msgid "This looks like a private key. Please provide your public RSA key." +msgstr "" + +#: secrets/forms.py:38 +msgid "" +"Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP." +msgstr "" + +#: secrets/forms.py:78 +msgid "Plaintext" +msgstr "" + +#: secrets/forms.py:88 +msgid "Plaintext (verify)" +msgstr "" + +#: secrets/forms.py:134 +msgid "Assigned device" +msgstr "" + +#: secrets/forms.py:142 +msgid "Plaintext secret data" +msgstr "" + +#: secrets/forms.py:149 +msgid "Name or username" +msgstr "" + +#: secrets/forms.py:225 +msgid "User Keys" +msgstr "" + +#: secrets/forms.py:233 +msgid "Your private key" +msgstr "" + +#: secrets/models.py:55 +msgid "RSA public key" +msgstr "" + +#: secrets/views.py:121 +msgid "Added new secret: {}." +msgstr "" + +#: secrets/views.py:168 +msgid "Modified secret {}." +msgstr "" + +#: tenancy/filters.py:20 tenancy/filters.py:43 +msgid "Tenant group (ID)" +msgstr "" + +#: tenancy/filters.py:26 +msgid "Tenant group group (slug)" +msgstr "" + +#: tenancy/filters.py:77 +msgid "Tenant Group (ID)" +msgstr "" + +#: tenancy/filters.py:84 +msgid "Tenant Group (slug)" +msgstr "" + +#: tenancy/forms.py:41 +msgid "Parent group" +msgstr "" + +#: users/admin.py:17 +msgid "Preferences" +msgstr "" + +#: users/admin.py:31 users/forms.py:25 +msgid "If no key is provided, one will be generated automatically." +msgstr "" + +#: users/models.py:166 +msgid "Permit create/update/delete operations using this key" +msgstr "" + +#: users/views.py:68 +msgid "Logged in as {}." +msgstr "" + +#: users/views.py:92 +msgid "You have logged out." +msgstr "" + +#: users/views.py:134 utilities/views.py:210 +msgid "Your preferences have been updated." +msgstr "" + +#: users/views.py:160 +msgid "Your password has been changed successfully." +msgstr "" + +#: users/views.py:210 +msgid "Your user key has been saved." +msgstr "" + +#: users/views.py:241 +msgid "Session key deleted" +msgstr "" + +#: users/views.py:308 +msgid "Modified token {}" +msgstr "" + +#: users/views.py:308 +msgid "Created token {}" +msgstr "" + +#: users/views.py:348 +msgid "Token deleted" +msgstr "" + +#: utilities/forms.py:591 +msgid "Tags" +msgstr "" + +#: utilities/forms.py:744 +msgid "" +"Enter object data in JSON or YAML format. Note: Only a single object/" +"document is supported." +msgstr "" + +#: utilities/forms.py:795 +msgid "" +"Use the buttons below to arrange columns in the desired order, then select " +"all columns to display." +msgstr "" + +#: utilities/views.py:536 +msgid "Imported object: {}" +msgstr "" + +#: utilities/views.py:950 +msgid "Added {} {}" +msgstr "" + +#: virtualization/filters.py:68 +msgid "Parent group (ID)" +msgstr "" + +#: virtualization/filters.py:74 +msgid "Parent group (slug)" +msgstr "" + +#: virtualization/filters.py:78 virtualization/filters.py:130 +msgid "Cluster type (ID)" +msgstr "" + +#: virtualization/filters.py:84 virtualization/filters.py:136 +msgid "Cluster type (slug)" +msgstr "" + +#: virtualization/filters.py:119 +msgid "Cluster group (ID)" +msgstr "" + +#: virtualization/filters.py:140 +msgid "Cluster (ID)" +msgstr "" + +#: virtualization/filters.py:219 +msgid "Virtual machine" +msgstr "" + +#: virtualization/forms.py:102 +msgid "Type of cluster" +msgstr "" + +#: virtualization/forms.py:108 +msgid "Assigned cluster group" +msgstr "" + +#: virtualization/forms.py:391 +msgid "Operational status of device" +msgstr "" + +#: virtualization/forms.py:396 +msgid "Assigned cluster" +msgstr "" + +#: virtualization/forms.py:460 virtualization/models.py:253 +msgid "vCPUs" +msgstr "" + +#: virtualization/forms.py:464 virtualization/models.py:258 +msgid "Memory (MB)" +msgstr "" + +#: virtualization/forms.py:468 virtualization/models.py:263 +msgid "Disk (GB)" +msgstr "" + +#: virtualization/views.py:199 +msgid "Added {} devices to cluster {}" +msgstr "" + +#: virtualization/views.py:232 +msgid "Removed {} devices from cluster {}" +msgstr "" diff --git a/netbox/netbox/forms.py b/netbox/netbox/forms.py index 36198a384..78ac4c1e8 100644 --- a/netbox/netbox/forms.py +++ b/netbox/netbox/forms.py @@ -1,47 +1,48 @@ from django import forms +from django.utils.translation import gettext as _ from utilities.forms import BootstrapMixin OBJ_TYPE_CHOICES = ( - ('', 'All Objects'), - ('Circuits', ( - ('provider', 'Providers'), - ('circuit', 'Circuits'), + ('', _('All Objects')), + (_('Circuits'), ( + ('provider', _('Providers')), + ('circuit', _('Circuits')), )), - ('DCIM', ( - ('site', 'Sites'), - ('rack', 'Racks'), - ('rackgroup', 'Rack Groups'), - ('devicetype', 'Device types'), - ('device', 'Devices'), - ('virtualchassis', 'Virtual Chassis'), - ('cable', 'Cables'), - ('powerfeed', 'Power Feeds'), + (_('DCIM'), ( + ('site', _('Sites')), + ('rack', _('Racks')), + ('rackgroup', _('Rack Groups')), + ('devicetype', _('Device types')), + ('device', _('Devices')), + ('virtualchassis', _('Virtual Chassis')), + ('cable', _('Cables')), + ('powerfeed', _('Power Feeds')), )), - ('IPAM', ( - ('vrf', 'VRFs'), - ('aggregate', 'Aggregates'), - ('prefix', 'Prefixes'), - ('ipaddress', 'IP addresses'), - ('vlan', 'VLANs'), + (_('IPAM'), ( + ('vrf', _('VRFs')), + ('aggregate', _('Aggregates')), + ('prefix', _('Prefixes')), + ('ipaddress', _('IP addresses')), + ('vlan', _('VLANs')), )), - ('Secrets', ( - ('secret', 'Secrets'), + (_('Secrets'), ( + ('secret', _('Secrets')), )), - ('Tenancy', ( - ('tenant', 'Tenants'), + (_('Tenancy'), ( + ('tenant', _('Tenants')), )), - ('Virtualization', ( - ('cluster', 'Clusters'), - ('virtualmachine', 'Virtual machines'), + (_('Virtualization'), ( + ('cluster', _('Clusters')), + ('virtualmachine', _('Virtual machines')), )), ) class SearchForm(BootstrapMixin, forms.Form): q = forms.CharField( - label='Search' + label=_('Search') ) obj_type = forms.ChoiceField( - choices=OBJ_TYPE_CHOICES, required=False, label='Type' + choices=OBJ_TYPE_CHOICES, required=False, label=_('Type') ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 61020669f..3cb20a01e 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -681,3 +681,12 @@ for plugin_name in PLUGINS: CACHEOPS.update({ "{}.{}".format(plugin_name, key): value for key, value in plugin_config.caching_config.items() }) + +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale'), +) + +LANGUAGES = ( + ('en', 'English'), + ('zh-Hans', '中文简体'), +) diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index 78f25952a..e0fe9db62 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -1,5 +1,6 @@ import django_filters from django.db.models import Q +from django.utils.translation import gettext as _ from dcim.models import Device from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet @@ -20,30 +21,31 @@ class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): fields = ['id', 'name', 'slug'] -class SecretFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): +class SecretFilterSet( + BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) role_id = django_filters.ModelMultipleChoiceFilter( queryset=SecretRole.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='role__slug', queryset=SecretRole.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), - label='Device (ID)', + label=_('Device (ID)'), ) device = django_filters.ModelMultipleChoiceFilter( field_name='device__name', queryset=Device.objects.all(), to_field_name='name', - label='Device (name)', + label=_('Device (name)'), ) tag = TagFilter() diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index b4fe4eaff..82fc2f8f5 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -1,6 +1,7 @@ from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA from django import forms +from django.utils.translation import gettext as _ from dcim.models import Device from extras.forms import ( @@ -20,21 +21,21 @@ def validate_rsa_key(key, is_secret=True): Validate the format and type of an RSA key. """ if key.startswith('ssh-rsa '): - raise forms.ValidationError("OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.") + raise forms.ValidationError(_('OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.')) try: key = RSA.importKey(key) except ValueError: - raise forms.ValidationError("Invalid RSA key. Please ensure that your key is in PEM (base64) format.") + raise forms.ValidationError(_('Invalid RSA key. Please ensure that your key is in PEM (base64) format.')) except Exception as e: raise forms.ValidationError("Invalid key detected: {}".format(e)) if is_secret and not key.has_private(): - raise forms.ValidationError("This looks like a public key. Please provide your private RSA key.") + raise forms.ValidationError(_('This looks like a public key. Please provide your private RSA key.')) elif not is_secret and key.has_private(): - raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.") + raise forms.ValidationError(_('This looks like a private key. Please provide your public RSA key.')) try: PKCS1_OAEP.new(key) except Exception: - raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.") + raise forms.ValidationError(_('Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.')) # @@ -74,7 +75,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm): plaintext = forms.CharField( max_length=SECRET_PLAINTEXT_MAX_LENGTH, required=False, - label='Plaintext', + label=_('Plaintext'), widget=forms.PasswordInput( attrs={ 'class': 'requires-session-key', @@ -84,7 +85,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm): plaintext2 = forms.CharField( max_length=SECRET_PLAINTEXT_MAX_LENGTH, required=False, - label='Plaintext (verify)', + label=_('Plaintext (verify)'), widget=forms.PasswordInput() ) role = DynamicModelChoiceField( @@ -130,22 +131,22 @@ class SecretCSVForm(CustomFieldModelCSVForm): device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name', - help_text='Assigned device' + help_text=_('Assigned device') ) role = CSVModelChoiceField( queryset=SecretRole.objects.all(), to_field_name='name', - help_text='Assigned role' + help_text=_('Assigned role') ) plaintext = forms.CharField( - help_text='Plaintext secret data' + help_text=_('Plaintext secret data') ) class Meta: model = Secret fields = Secret.csv_headers help_texts = { - 'name': 'Name or username', + 'name': _('Name or username'), } def save(self, *args, **kwargs): @@ -154,7 +155,8 @@ class SecretCSVForm(CustomFieldModelCSVForm): return s -class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class SecretBulkEditForm( + BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput() @@ -178,7 +180,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Secret q = forms.CharField( required=False, - label='Search' + label=_('Search') ) role = DynamicModelMultipleChoiceField( queryset=SecretRole.objects.all(), @@ -220,7 +222,7 @@ class UserKeyForm(BootstrapMixin, forms.ModelForm): class ActivateUserKeyForm(forms.Form): _selected_action = forms.ModelMultipleChoiceField( queryset=UserKey.objects.all(), - label='User Keys' + label=_('User Keys') ) secret_key = forms.CharField( widget=forms.Textarea( @@ -228,5 +230,5 @@ class ActivateUserKeyForm(forms.Form): 'class': 'vLargeTextField', } ), - label='Your private key' + label=_('Your private key') ) diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 830e91096..1af8685ab 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.encoding import force_bytes +from django.utils.translation import gettext as _ from taggit.managers import TaggableManager from dcim.models import Device @@ -51,7 +52,7 @@ class UserKey(models.Model): editable=False ) public_key = models.TextField( - verbose_name='RSA public key' + verbose_name=_('RSA public key') ) master_key_cipher = models.BinaryField( max_length=512, diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index f92c9216b..cad427111 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -1,4 +1,5 @@ import django_tables2 as tables +from django.utils.translation import gettext as _ from utilities.tables import BaseTable, TagColumn, ToggleColumn from .models import SecretRole, Secret @@ -21,7 +22,7 @@ class SecretRoleTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() secret_count = tables.Column( - verbose_name='Secrets' + verbose_name=_('Secrets') ) actions = tables.TemplateColumn( template_code=SECRETROLE_ACTIONS, diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index ed59f4392..168da9fc5 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -6,6 +6,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse +from django.utils.translation import gettext as _ from django.views.generic import View from utilities.views import ( @@ -117,7 +118,7 @@ def secret_add(request): secret.save() form.save_m2m() - messages.success(request, "Added new secret: {}.".format(secret)) + messages.success(request, _("Added new secret: {}.").format(secret)) if '_addanother' in request.POST: return redirect('secrets:secret_add') else: @@ -164,7 +165,7 @@ def secret_edit(request, pk): secret.plaintext = form.cleaned_data['plaintext'] secret.encrypt(master_key) secret.save() - messages.success(request, "Modified secret {}.".format(secret)) + messages.success(request, _("Modified secret {}.").format(secret)) return redirect('secrets:secret', pk=secret.pk) else: form.add_error(None, "Invalid session key. Unable to encrypt secret data.") diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index af5ee0b2c..47f33a5eb 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -1,5 +1,6 @@ import django_filters from django.db.models import Q +from django.utils.translation import gettext as _ from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter @@ -16,13 +17,13 @@ __all__ = ( class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=TenantGroup.objects.all(), - label='Tenant group (ID)', + label=_('Tenant group (ID)'), ) parent = django_filters.ModelMultipleChoiceFilter( field_name='parent__slug', queryset=TenantGroup.objects.all(), to_field_name='slug', - label='Tenant group group (slug)', + label=_('Tenant group group (slug)'), ) class Meta: @@ -33,20 +34,20 @@ class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class TenantFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) group_id = TreeNodeMultipleChoiceFilter( queryset=TenantGroup.objects.all(), field_name='group', lookup_expr='in', - label='Tenant group (ID)', + label=_('Tenant group (ID)'), ) group = TreeNodeMultipleChoiceFilter( queryset=TenantGroup.objects.all(), field_name='group', lookup_expr='in', to_field_name='slug', - label='Tenant group (slug)', + label=_('Tenant group (slug)'), ) tag = TagFilter() @@ -73,22 +74,22 @@ class TenancyFilterSet(django_filters.FilterSet): queryset=TenantGroup.objects.all(), field_name='tenant__group', lookup_expr='in', - label='Tenant Group (ID)', + label=_('Tenant Group (ID)'), ) tenant_group = TreeNodeMultipleChoiceFilter( queryset=TenantGroup.objects.all(), field_name='tenant__group', to_field_name='slug', lookup_expr='in', - label='Tenant Group (slug)', + label=_('Tenant Group (slug)'), ) tenant_id = django_filters.ModelMultipleChoiceFilter( queryset=Tenant.objects.all(), - label='Tenant (ID)', + label=_('Tenant (ID)'), ) tenant = django_filters.ModelMultipleChoiceFilter( queryset=Tenant.objects.all(), field_name='tenant__slug', to_field_name='slug', - label='Tenant (slug)', + label=_('Tenant (slug)'), ) diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index bf100f43a..cb17aade7 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import gettext as _ from extras.forms import ( AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm, @@ -37,7 +38,7 @@ class TenantGroupCSVForm(CSVModelForm): queryset=TenantGroup.objects.all(), required=False, to_field_name='name', - help_text='Parent group' + help_text=_('Parent group') ) slug = SlugField() @@ -74,7 +75,7 @@ class TenantCSVForm(CustomFieldModelCSVForm): queryset=TenantGroup.objects.all(), required=False, to_field_name='name', - help_text='Assigned group' + help_text=_('Assigned group') ) class Meta: @@ -102,7 +103,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Tenant q = forms.CharField( required=False, - label='Search' + label=_('Search') ) group = DynamicModelMultipleChoiceField( queryset=TenantGroup.objects.all(), diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 147a20707..1b122a979 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -1,4 +1,5 @@ import django_tables2 as tables +from django.utils.translation import gettext as _ from utilities.tables import BaseTable, TagColumn, ToggleColumn from .models import Tenant, TenantGroup @@ -42,7 +43,7 @@ class TenantGroupTable(BaseTable): orderable=False ) tenant_count = tables.Column( - verbose_name='Tenants' + verbose_name=_('Tenants') ) actions = tables.TemplateColumn( template_code=TENANTGROUP_ACTIONS, diff --git a/netbox/users/admin.py b/netbox/users/admin.py index 42e651712..ae03771c7 100644 --- a/netbox/users/admin.py +++ b/netbox/users/admin.py @@ -2,6 +2,7 @@ from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.models import User +from django.utils.translation import gettext as _ from .models import Token, UserConfig @@ -13,7 +14,7 @@ class UserConfigInline(admin.TabularInline): model = UserConfig readonly_fields = ('data',) can_delete = False - verbose_name = 'Preferences' + verbose_name = _('Preferences') @admin.register(User) @@ -27,7 +28,7 @@ class UserAdmin(UserAdmin_): class TokenAdminForm(forms.ModelForm): key = forms.CharField( required=False, - help_text="If no key is provided, one will be generated automatically." + help_text=_('If no key is provided, one will be generated automatically.') ) class Meta: diff --git a/netbox/users/forms.py b/netbox/users/forms.py index 495332d2c..4c7445b7e 100644 --- a/netbox/users/forms.py +++ b/netbox/users/forms.py @@ -1,5 +1,6 @@ from django import forms from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm +from django.utils.translation import gettext as _ from utilities.forms import BootstrapMixin, DateTimePicker from .models import Token @@ -21,7 +22,7 @@ class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm): class TokenForm(BootstrapMixin, forms.ModelForm): key = forms.CharField( required=False, - help_text="If no key is provided, one will be generated automatically." + help_text=_('If no key is provided, one will be generated automatically.') ) class Meta: diff --git a/netbox/users/models.py b/netbox/users/models.py index ea5762232..45b086c27 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -8,6 +8,7 @@ from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone +from django.utils.translation import gettext as _ from utilities.utils import flatten_dict @@ -162,7 +163,7 @@ class Token(models.Model): ) write_enabled = models.BooleanField( default=True, - help_text='Permit create/update/delete operations using this key' + help_text=_('Permit create/update/delete operations using this key') ) description = models.CharField( max_length=200, diff --git a/netbox/users/views.py b/netbox/users/views.py index 9053d7b70..25a980311 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -11,6 +11,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.http import is_safe_url +from django.utils.translation import gettext as _ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic import View @@ -64,7 +65,7 @@ class LoginView(View): # Authenticate user auth_login(request, form.get_user()) logger.info(f"User {request.user} successfully authenticated") - messages.info(request, "Logged in as {}.".format(request.user)) + messages.info(request, _("Logged in as {}.").format(request.user)) logger.debug(f"Redirecting user to {redirect_to}") return HttpResponseRedirect(redirect_to) @@ -88,7 +89,7 @@ class LogoutView(View): username = request.user auth_logout(request) logger.info(f"User {username} has logged out") - messages.info(request, "You have logged out.") + messages.info(request, _('You have logged out.')) # Delete session key cookie (if set) upon logout response = HttpResponseRedirect(reverse('home')) @@ -130,7 +131,7 @@ class UserConfigView(LoginRequiredMixin, View): if key in data: userconfig.clear(key) userconfig.save() - messages.success(request, "Your preferences have been updated.") + messages.success(request, _('Your preferences have been updated.')) return redirect('user:preferences') @@ -156,7 +157,7 @@ class ChangePasswordView(LoginRequiredMixin, View): if form.is_valid(): form.save() update_session_auth_hash(request, form.user) - messages.success(request, "Your password has been changed successfully.") + messages.success(request, _('Your password has been changed successfully.')) return redirect('user:profile') return render(request, self.template_name, { @@ -206,7 +207,7 @@ class UserKeyEditView(LoginRequiredMixin, View): uk = form.save(commit=False) uk.user = request.user uk.save() - messages.success(request, "Your user key has been saved.") + messages.success(request, _('Your user key has been saved.')) return redirect('user:userkey') return render(request, self.template_name, { @@ -237,7 +238,7 @@ class SessionKeyDeleteView(LoginRequiredMixin, View): # Delete session key sessionkey.delete() - messages.success(request, "Session key deleted") + messages.success(request, _('Session key deleted')) # Delete cookie response = redirect('user:userkey') @@ -304,7 +305,7 @@ class TokenEditView(LoginRequiredMixin, View): token.user = request.user token.save() - msg = "Modified token {}".format(token) if pk else "Created token {}".format(token) + msg = _("Modified token {}").format(token) if pk else _("Created token {}").format(token) messages.success(request, msg) if '_addanother' in request.POST: @@ -344,7 +345,7 @@ class TokenDeleteView(PermissionRequiredMixin, View): form = ConfirmationForm(request.POST) if form.is_valid(): token.delete() - messages.success(request, "Token deleted") + messages.success(request, _('Token deleted')) return redirect('user:token_list') return render(request, 'utilities/obj_delete.html', { diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index f628ca917..0f9f3064e 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -5,6 +5,7 @@ from django import forms from django.conf import settings from django.db import models from django_filters.utils import get_model_field, resolve_field +from django.utils.translation import gettext as _ from extras.models import Tag from utilities.constants import ( @@ -274,7 +275,7 @@ class NameSlugSearchFilterSet(django_filters.FilterSet): """ q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) def search(self, queryset, name, value): diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 539347aaa..c60cb3816 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -14,6 +14,7 @@ from django.db.models import Count from django.forms import BoundField from django.forms.models import fields_for_model from django.urls import reverse +from django.utils.translation import gettext as _ from .choices import ColorChoices, unpack_grouped_choices from .validators import EnhancedURLValidator @@ -587,7 +588,7 @@ class TagFilterField(forms.MultipleChoiceField): return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] # Choices are fetched each time the form is initialized - super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs) + super().__init__(label=_('Tags'), choices=get_choices, required=False, *args, **kwargs) class DynamicModelChoiceMixin: @@ -740,7 +741,7 @@ class ImportForm(BootstrapMixin, forms.Form): """ data = forms.CharField( widget=forms.Textarea, - help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported." + help_text=_('Enter object data in JSON or YAML format. Note: Only a single object/document is supported.') ) format = forms.ChoiceField( choices=( @@ -791,7 +792,7 @@ class TableConfigForm(BootstrapMixin, forms.Form): widget=forms.SelectMultiple( attrs={'size': 10} ), - help_text="Use the buttons below to arrange columns in the desired order, then select all columns to display." + help_text=_('Use the buttons below to arrange columns in the desired order, then select all columns to display.') ) def __init__(self, table, *args, **kwargs): diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index ef073309f..de3d81bf1 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -18,6 +18,7 @@ from django.utils.decorators import method_decorator from django.utils.html import escape from django.utils.http import is_safe_url from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from django.views.decorators.csrf import requires_csrf_token from django.views.defaults import ERROR_500_TEMPLATE_NAME from django.views.generic import View @@ -206,7 +207,7 @@ class ObjectListView(View): request.user.config.set(preference_name, form.cleaned_data['columns'], commit=True) elif 'clear' in request.POST: request.user.config.clear(preference_name, commit=True) - messages.success(request, "Your preferences have been updated.") + messages.success(request, _('Your preferences have been updated.')) return redirect(request.get_full_path()) @@ -532,7 +533,7 @@ class ObjectImportView(GetReturnURLMixin, View): if not model_form.errors: logger.info(f"Import object {obj} (PK: {obj.pk})") - messages.success(request, mark_safe('Imported object: {}'.format( + messages.success(request, mark_safe(_('Imported object: {}').format( obj.get_absolute_url(), obj ))) @@ -946,7 +947,7 @@ class ComponentCreateView(GetReturnURLMixin, View): for component_form in new_components: component_form.save() - messages.success(request, "Added {} {}".format( + messages.success(request, _('Added {} {}').format( len(new_components), self.model._meta.verbose_name_plural )) if '_addanother' in request.POST: diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 5c9b64aa3..e3a239d4b 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -1,5 +1,6 @@ import django_filters from django.db.models import Q +from django.utils.translation import gettext as _ from dcim.models import DeviceRole, Interface, Platform, Region, Site from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet @@ -37,50 +38,50 @@ class ClusterGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) group_id = django_filters.ModelMultipleChoiceFilter( queryset=ClusterGroup.objects.all(), - label='Parent group (ID)', + label=_('Parent group (ID)'), ) group = django_filters.ModelMultipleChoiceFilter( field_name='group__slug', queryset=ClusterGroup.objects.all(), to_field_name='slug', - label='Parent group (slug)', + label=_('Parent group (slug)'), ) type_id = django_filters.ModelMultipleChoiceFilter( queryset=ClusterType.objects.all(), - label='Cluster type (ID)', + label=_('Cluster type (ID)'), ) type = django_filters.ModelMultipleChoiceFilter( field_name='type__slug', queryset=ClusterType.objects.all(), to_field_name='slug', - label='Cluster type (slug)', + label=_('Cluster type (slug)'), ) tag = TagFilter() @@ -106,7 +107,7 @@ class VirtualMachineFilterSet( ): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) status = django_filters.MultipleChoiceFilter( choices=VirtualMachineStatusChoices, @@ -115,76 +116,76 @@ class VirtualMachineFilterSet( cluster_group_id = django_filters.ModelMultipleChoiceFilter( field_name='cluster__group', queryset=ClusterGroup.objects.all(), - label='Cluster group (ID)', + label=_('Cluster group (ID)'), ) cluster_group = django_filters.ModelMultipleChoiceFilter( field_name='cluster__group__slug', queryset=ClusterGroup.objects.all(), to_field_name='slug', - label='Cluster group (slug)', + label=_('Cluster group (slug)'), ) cluster_type_id = django_filters.ModelMultipleChoiceFilter( field_name='cluster__type', queryset=ClusterType.objects.all(), - label='Cluster type (ID)', + label=_('Cluster type (ID)'), ) cluster_type = django_filters.ModelMultipleChoiceFilter( field_name='cluster__type__slug', queryset=ClusterType.objects.all(), to_field_name='slug', - label='Cluster type (slug)', + label=_('Cluster type (slug)'), ) cluster_id = django_filters.ModelMultipleChoiceFilter( queryset=Cluster.objects.all(), - label='Cluster (ID)', + label=_('Cluster (ID)'), ) region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='cluster__site__region', lookup_expr='in', - label='Region (ID)', + label=_('Region (ID)'), ) region = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='cluster__site__region', lookup_expr='in', to_field_name='slug', - label='Region (slug)', + label=_('Region (slug)'), ) site_id = django_filters.ModelMultipleChoiceFilter( field_name='cluster__site', queryset=Site.objects.all(), - label='Site (ID)', + label=_('Site (ID)'), ) site = django_filters.ModelMultipleChoiceFilter( field_name='cluster__site__slug', queryset=Site.objects.all(), to_field_name='slug', - label='Site (slug)', + label=_('Site (slug)'), ) role_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceRole.objects.all(), - label='Role (ID)', + label=_('Role (ID)'), ) role = django_filters.ModelMultipleChoiceFilter( field_name='role__slug', queryset=DeviceRole.objects.all(), to_field_name='slug', - label='Role (slug)', + label=_('Role (slug)'), ) platform_id = django_filters.ModelMultipleChoiceFilter( queryset=Platform.objects.all(), - label='Platform (ID)', + label=_('Platform (ID)'), ) platform = django_filters.ModelMultipleChoiceFilter( field_name='platform__slug', queryset=Platform.objects.all(), to_field_name='slug', - label='Platform (slug)', + label=_('Platform (slug)'), ) mac_address = MultiValueMACAddressFilter( field_name='interfaces__mac_address', - label='MAC address', + label=_('MAC address'), ) tag = TagFilter() @@ -204,21 +205,21 @@ class VirtualMachineFilterSet( class InterfaceFilterSet(BaseFilterSet): q = django_filters.CharFilter( method='search', - label='Search', + label=_('Search'), ) virtual_machine_id = django_filters.ModelMultipleChoiceFilter( field_name='virtual_machine', queryset=VirtualMachine.objects.all(), - label='Virtual machine (ID)', + label=_('Virtual machine (ID)'), ) virtual_machine = django_filters.ModelMultipleChoiceFilter( field_name='virtual_machine__name', queryset=VirtualMachine.objects.all(), to_field_name='name', - label='Virtual machine', + label=_('Virtual machine'), ) mac_address = MultiValueMACAddressFilter( - label='MAC address', + label=_('MAC address'), ) tag = TagFilter() diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 2f2ee4950..de7769784 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -1,5 +1,6 @@ from django import forms from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ from dcim.choices import InterfaceModeChoices from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN @@ -98,25 +99,25 @@ class ClusterCSVForm(CustomFieldModelCSVForm): type = CSVModelChoiceField( queryset=ClusterType.objects.all(), to_field_name='name', - help_text='Type of cluster' + help_text=_('Type of cluster') ) group = CSVModelChoiceField( queryset=ClusterGroup.objects.all(), to_field_name='name', required=False, - help_text='Assigned cluster group' + help_text=_('Assigned cluster group') ) site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', required=False, - help_text='Assigned site' + help_text=_('Assigned site') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) class Meta: @@ -147,7 +148,7 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -387,12 +388,12 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): status = CSVChoiceField( choices=VirtualMachineStatusChoices, required=False, - help_text='Operational status of device' + help_text=_('Operational status of device') ) cluster = CSVModelChoiceField( queryset=Cluster.objects.all(), to_field_name='name', - help_text='Assigned cluster' + help_text=_('Assigned cluster') ) role = CSVModelChoiceField( queryset=DeviceRole.objects.filter( @@ -400,19 +401,19 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm): ), required=False, to_field_name='name', - help_text='Functional role' + help_text=_('Functional role') ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) platform = CSVModelChoiceField( queryset=Platform.objects.all(), required=False, to_field_name='name', - help_text='Assigned platform' + help_text=_('Assigned platform') ) class Meta: @@ -456,19 +457,19 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB ) vcpus = forms.IntegerField( required=False, - label='vCPUs' + label=_('vCPUs') ) memory = forms.IntegerField( required=False, - label='Memory (MB)' + label=_('Memory (MB)') ) disk = forms.IntegerField( required=False, - label='Disk (GB)' + label=_('Disk (GB)') ) comments = CommentField( widget=SmallTextarea, - label='Comments' + label=_('Comments') ) class Meta: @@ -485,7 +486,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil ] q = forms.CharField( required=False, - label='Search' + label=_('Search') ) cluster_group = DynamicModelMultipleChoiceField( queryset=ClusterGroup.objects.all(), @@ -508,7 +509,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False, - label='Cluster' + label=_('Cluster') ) region = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -558,7 +559,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil ) mac_address = forms.CharField( required=False, - label='MAC address' + label=_('MAC address') ) tag = TagFilterField(model) @@ -606,7 +607,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm): 'mode': StaticSelect2() } labels = { - 'mode': '802.1Q Mode', + 'mode': _('802.1Q Mode'), } help_texts = { 'mode': INTERFACE_MODE_HELP_TEXT, @@ -645,7 +646,7 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form): widget=forms.HiddenInput() ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) type = forms.ChoiceField( choices=VMInterfaceTypeChoices, @@ -660,11 +661,11 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form): required=False, min_value=INTERFACE_MTU_MIN, max_value=INTERFACE_MTU_MAX, - label='MTU' + label=_('MTU') ) mac_address = forms.CharField( required=False, - label='MAC Address' + label=_('MAC Address') ) description = forms.CharField( max_length=100, @@ -732,7 +733,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): required=False, min_value=INTERFACE_MTU_MIN, max_value=INTERFACE_MTU_MAX, - label='MTU' + label=_('MTU') ) description = forms.CharField( max_length=100, @@ -795,7 +796,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form): widget=forms.MultipleHiddenInput() ) name_pattern = ExpandableNameField( - label='Name' + label=_('Name') ) def clean_tags(self): diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 3daeff013..d79fbc3a0 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse +from django.utils.translation import gettext as _ from taggit.managers import TaggableManager from dcim.models import Device @@ -220,7 +221,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): max_length=50, choices=VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE, - verbose_name='Status' + verbose_name=_('Status') ) role = models.ForeignKey( to='dcim.DeviceRole', @@ -236,7 +237,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): related_name='+', blank=True, null=True, - verbose_name='Primary IPv4' + verbose_name=_('Primary IPv4') ) primary_ip6 = models.OneToOneField( to='ipam.IPAddress', @@ -244,22 +245,22 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): related_name='+', blank=True, null=True, - verbose_name='Primary IPv6' + verbose_name=_('Primary IPv6') ) vcpus = models.PositiveSmallIntegerField( blank=True, null=True, - verbose_name='vCPUs' + verbose_name=_('vCPUs') ) memory = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Memory (MB)' + verbose_name=_('Memory (MB)') ) disk = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Disk (GB)' + verbose_name=_('Disk (GB)') ) comments = models.TextField( blank=True diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 6972637d0..d19274c2e 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -1,5 +1,6 @@ import django_tables2 as tables from django_tables2.utils import Accessor +from django.utils.translation import gettext as _ from dcim.models import Interface from tenancy.tables import COL_TENANT @@ -51,7 +52,7 @@ class ClusterTypeTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() cluster_count = tables.Column( - verbose_name='Clusters' + verbose_name=_('Clusters') ) actions = tables.TemplateColumn( template_code=CLUSTERTYPE_ACTIONS, @@ -73,7 +74,7 @@ class ClusterGroupTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() cluster_count = tables.Column( - verbose_name='Clusters' + verbose_name=_('Clusters') ) actions = tables.TemplateColumn( template_code=CLUSTERGROUP_ACTIONS, @@ -104,11 +105,11 @@ class ClusterTable(BaseTable): ) device_count = tables.TemplateColumn( template_code=CLUSTER_DEVICE_COUNT, - verbose_name='Devices' + verbose_name=_('Devices') ) vm_count = tables.TemplateColumn( template_code=CLUSTER_VM_COUNT, - verbose_name='VMs' + verbose_name=_('VMs') ) tags = TagColumn( url_name='virtualization:cluster_list' @@ -148,16 +149,16 @@ class VirtualMachineDetailTable(VirtualMachineTable): primary_ip4 = tables.LinkColumn( viewname='ipam:ipaddress', args=[Accessor('primary_ip4.pk')], - verbose_name='IPv4 Address' + verbose_name=_('IPv4 Address') ) primary_ip6 = tables.LinkColumn( viewname='ipam:ipaddress', args=[Accessor('primary_ip6.pk')], - verbose_name='IPv6 Address' + verbose_name=_('IPv6 Address') ) primary_ip = tables.TemplateColumn( orderable=False, - verbose_name='IP Address', + verbose_name=_('IP Address'), template_code=VIRTUALMACHINE_PRIMARY_IP ) tags = TagColumn( diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 4da7ee313..fc383578e 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -4,6 +4,7 @@ from django.db import transaction from django.db.models import Count from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse +from django.utils.translation import gettext as _ from django.views.generic import View from dcim.models import Device, Interface @@ -195,7 +196,7 @@ class ClusterAddDevicesView(PermissionRequiredMixin, View): device.cluster = cluster device.save() - messages.success(request, "Added {} devices to cluster {}".format( + messages.success(request, _('Added {} devices to cluster {}').format( len(device_pks), cluster )) return redirect(cluster.get_absolute_url()) @@ -228,7 +229,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View): device.cluster = None device.save() - messages.success(request, "Removed {} devices from cluster {}".format( + messages.success(request, _('Removed {} devices from cluster {}').format( len(device_pks), cluster )) return redirect(cluster.get_absolute_url())