Просмотр исходного кода

Closes #13149: Wrap form field labels with gettext_lazy()

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson 2 лет назад
Родитель
Сommit
b7a9649269
51 измененных файлов с 1350 добавлено и 589 удалено
  1. 22 16
      netbox/circuits/forms/bulk_edit.py
  2. 10 1
      netbox/circuits/forms/bulk_import.py
  3. 15 10
      netbox/circuits/forms/filtersets.py
  4. 14 7
      netbox/circuits/forms/model_forms.py
  5. 6 4
      netbox/core/forms/bulk_edit.py
  6. 17 5
      netbox/core/forms/filtersets.py
  7. 1 1
      netbox/core/forms/mixins.py
  8. 5 4
      netbox/core/forms/model_forms.py
  9. 3 1
      netbox/dcim/forms/bulk_create.py
  10. 166 48
      netbox/dcim/forms/bulk_edit.py
  11. 119 2
      netbox/dcim/forms/bulk_import.py
  12. 15 8
      netbox/dcim/forms/common.py
  13. 1 1
      netbox/dcim/forms/connections.py
  14. 194 129
      netbox/dcim/forms/filtersets.py
  15. 4 1
      netbox/dcim/forms/formsets.py
  16. 101 43
      netbox/dcim/forms/model_forms.py
  17. 19 7
      netbox/dcim/forms/object_create.py
  18. 9 1
      netbox/dcim/forms/object_import.py
  19. 33 1
      netbox/extras/forms/bulk_edit.py
  20. 11 1
      netbox/extras/forms/bulk_import.py
  21. 38 18
      netbox/extras/forms/filtersets.py
  22. 2 0
      netbox/extras/forms/misc.py
  23. 70 44
      netbox/extras/forms/model_forms.py
  24. 2 2
      netbox/extras/forms/reports.py
  25. 2 2
      netbox/extras/forms/scripts.py
  26. 1 1
      netbox/ipam/forms/bulk_create.py
  27. 69 39
      netbox/ipam/forms/bulk_edit.py
  28. 52 8
      netbox/ipam/forms/bulk_import.py
  29. 49 37
      netbox/ipam/forms/filtersets.py
  30. 59 34
      netbox/ipam/forms/model_forms.py
  31. 7 2
      netbox/netbox/forms/base.py
  32. 18 3
      netbox/tenancy/forms/bulk_edit.py
  33. 6 1
      netbox/tenancy/forms/bulk_import.py
  34. 3 2
      netbox/tenancy/forms/filtersets.py
  35. 3 1
      netbox/tenancy/forms/forms.py
  36. 13 5
      netbox/tenancy/forms/model_forms.py
  37. 0 1
      netbox/users/forms/model_forms.py
  38. 4 2
      netbox/utilities/forms/fields/array.py
  39. 7 6
      netbox/utilities/forms/fields/csv.py
  40. 5 5
      netbox/utilities/forms/fields/expandable.py
  41. 13 12
      netbox/utilities/forms/fields/fields.py
  42. 1 1
      netbox/virtualization/forms/bulk_create.py
  43. 31 11
      netbox/virtualization/forms/bulk_edit.py
  44. 18 1
      netbox/virtualization/forms/bulk_import.py
  45. 18 15
      netbox/virtualization/forms/filtersets.py
  46. 31 17
      netbox/virtualization/forms/model_forms.py
  47. 4 1
      netbox/virtualization/forms/object_create.py
  48. 18 9
      netbox/wireless/forms/bulk_edit.py
  49. 14 1
      netbox/wireless/forms/bulk_import.py
  50. 15 7
      netbox/wireless/forms/filtersets.py
  51. 12 10
      netbox/wireless/forms/model_forms.py

+ 22 - 16
netbox/circuits/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
 from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
 from circuits.models import *
 from circuits.models import *
@@ -26,12 +26,11 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = Provider
     model = Provider
     fieldsets = (
     fieldsets = (
@@ -44,16 +43,16 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
 class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = ProviderAccount
     model = ProviderAccount
     fieldsets = (
     fieldsets = (
@@ -66,6 +65,7 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
 class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         required=False
         required=False
     )
     )
@@ -75,12 +75,11 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Service ID')
         label=_('Service ID')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = ProviderNetwork
     model = ProviderNetwork
     fieldsets = (
     fieldsets = (
@@ -93,6 +92,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
 class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -106,14 +106,17 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class CircuitBulkEditForm(NetBoxModelBulkEditForm):
 class CircuitBulkEditForm(NetBoxModelBulkEditForm):
     type = DynamicModelChoiceField(
     type = DynamicModelChoiceField(
+        label=_('Type'),
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
         required=False
         required=False
     )
     )
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         required=False
         required=False
     )
     )
     provider_account = DynamicModelChoiceField(
     provider_account = DynamicModelChoiceField(
+        label=_('Provider account'),
         queryset=ProviderAccount.objects.all(),
         queryset=ProviderAccount.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -121,19 +124,23 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(CircuitStatusChoices),
         choices=add_blank_choice(CircuitStatusChoices),
         required=False,
         required=False,
         initial=''
         initial=''
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     install_date = forms.DateField(
     install_date = forms.DateField(
+        label=_('Install date'),
         required=False,
         required=False,
         widget=DatePicker()
         widget=DatePicker()
     )
     )
     termination_date = forms.DateField(
     termination_date = forms.DateField(
+        label=_('Termination date'),
         required=False,
         required=False,
         widget=DatePicker()
         widget=DatePicker()
     )
     )
@@ -145,18 +152,17 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
         )
         )
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = Circuit
     model = Circuit
     fieldsets = (
     fieldsets = (
-        ('Circuit', ('provider', 'type', 'status', 'description')),
-        ('Service Parameters', ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
-        ('Tenancy', ('tenant',)),
+        (_('Circuit'), ('provider', 'type', 'status', 'description')),
+        (_('Service Parameters'), ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
+        (_('Tenancy'), ('tenant',)),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'tenant', 'commit_rate', 'description', 'comments',
         'tenant', 'commit_rate', 'description', 'comments',

+ 10 - 1
netbox/circuits/forms/bulk_import.py

@@ -3,7 +3,7 @@ from django import forms
 from circuits.choices import CircuitStatusChoices
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
 from circuits.models import *
 from dcim.models import Site
 from dcim.models import Site
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 from netbox.forms import NetBoxModelImportForm
 from netbox.forms import NetBoxModelImportForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.forms import BootstrapMixin
 from utilities.forms import BootstrapMixin
@@ -31,6 +31,7 @@ class ProviderImportForm(NetBoxModelImportForm):
 
 
 class ProviderAccountImportForm(NetBoxModelImportForm):
 class ProviderAccountImportForm(NetBoxModelImportForm):
     provider = CSVModelChoiceField(
     provider = CSVModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned provider')
         help_text=_('Assigned provider')
@@ -45,6 +46,7 @@ class ProviderAccountImportForm(NetBoxModelImportForm):
 
 
 class ProviderNetworkImportForm(NetBoxModelImportForm):
 class ProviderNetworkImportForm(NetBoxModelImportForm):
     provider = CSVModelChoiceField(
     provider = CSVModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned provider')
         help_text=_('Assigned provider')
@@ -67,26 +69,31 @@ class CircuitTypeImportForm(NetBoxModelImportForm):
 
 
 class CircuitImportForm(NetBoxModelImportForm):
 class CircuitImportForm(NetBoxModelImportForm):
     provider = CSVModelChoiceField(
     provider = CSVModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned provider')
         help_text=_('Assigned provider')
     )
     )
     provider_account = CSVModelChoiceField(
     provider_account = CSVModelChoiceField(
+        label=_('Provider account'),
         queryset=ProviderAccount.objects.all(),
         queryset=ProviderAccount.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned provider account'),
         help_text=_('Assigned provider account'),
         required=False
         required=False
     )
     )
     type = CSVModelChoiceField(
     type = CSVModelChoiceField(
+        label=_('Type'),
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Type of circuit')
         help_text=_('Type of circuit')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=CircuitStatusChoices,
         choices=CircuitStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -103,11 +110,13 @@ class CircuitImportForm(NetBoxModelImportForm):
 
 
 class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
 class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False
     )
     )
     provider_network = CSVModelChoiceField(
     provider_network = CSVModelChoiceField(
+        label=_('Provider network'),
         queryset=ProviderNetwork.objects.all(),
         queryset=ProviderNetwork.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False

+ 15 - 10
netbox/circuits/forms/filtersets.py

@@ -23,9 +23,9 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     model = Provider
     model = Provider
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('ASN', ('asn',)),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('ASN'), ('asn',)),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -62,7 +62,7 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
     model = ProviderAccount
     model = ProviderAccount
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('provider_id', 'account')),
+        (_('Attributes'), ('provider_id', 'account')),
     )
     )
     provider_id = DynamicModelMultipleChoiceField(
     provider_id = DynamicModelMultipleChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
@@ -70,6 +70,7 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
         label=_('Provider')
         label=_('Provider')
     )
     )
     account = forms.CharField(
     account = forms.CharField(
+        label=_('Account'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -79,7 +80,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
     model = ProviderNetwork
     model = ProviderNetwork
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('provider_id', 'service_id')),
+        (_('Attributes'), ('provider_id', 'service_id')),
     )
     )
     provider_id = DynamicModelMultipleChoiceField(
     provider_id = DynamicModelMultipleChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
@@ -87,6 +88,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
         label=_('Provider')
         label=_('Provider')
     )
     )
     service_id = forms.CharField(
     service_id = forms.CharField(
+        label=_('Service id'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
@@ -102,11 +104,11 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
     model = Circuit
     model = Circuit
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Provider', ('provider_id', 'provider_account_id', 'provider_network_id')),
-        ('Attributes', ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Provider'), ('provider_id', 'provider_account_id', 'provider_network_id')),
+        (_('Attributes'), ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     type_id = DynamicModelMultipleChoiceField(
     type_id = DynamicModelMultipleChoiceField(
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
@@ -135,6 +137,7 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
         label=_('Provider network')
         label=_('Provider network')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=CircuitStatusChoices,
         choices=CircuitStatusChoices,
         required=False
         required=False
     )
     )
@@ -158,10 +161,12 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
         label=_('Site')
         label=_('Site')
     )
     )
     install_date = forms.DateField(
     install_date = forms.DateField(
+        label=_('Install date'),
         required=False,
         required=False,
         widget=DatePicker
         widget=DatePicker
     )
     )
     termination_date = forms.DateField(
     termination_date = forms.DateField(
+        label=_('Termination date'),
         required=False,
         required=False,
         widget=DatePicker
         widget=DatePicker
     )
     )

+ 14 - 7
netbox/circuits/forms/model_forms.py

@@ -1,4 +1,4 @@
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
 from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
 from circuits.models import *
 from circuits.models import *
@@ -29,7 +29,7 @@ class ProviderForm(NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Provider', ('name', 'slug', 'asns', 'description', 'tags')),
+        (_('Provider'), ('name', 'slug', 'asns', 'description', 'tags')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -41,6 +41,7 @@ class ProviderForm(NetBoxModelForm):
 
 
 class ProviderAccountForm(NetBoxModelForm):
 class ProviderAccountForm(NetBoxModelForm):
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all()
         queryset=Provider.objects.all()
     )
     )
     comments = CommentField()
     comments = CommentField()
@@ -54,12 +55,13 @@ class ProviderAccountForm(NetBoxModelForm):
 
 
 class ProviderNetworkForm(NetBoxModelForm):
 class ProviderNetworkForm(NetBoxModelForm):
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all()
         queryset=Provider.objects.all()
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
+        (_('Provider Network'), ('provider', 'name', 'service_id', 'description', 'tags')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -73,7 +75,7 @@ class CircuitTypeForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Circuit Type', (
+        (_('Circuit Type'), (
             'name', 'slug', 'description', 'tags',
             'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -87,10 +89,12 @@ class CircuitTypeForm(NetBoxModelForm):
 
 
 class CircuitForm(TenancyForm, NetBoxModelForm):
 class CircuitForm(TenancyForm, NetBoxModelForm):
     provider = DynamicModelChoiceField(
     provider = DynamicModelChoiceField(
+        label=_('Provider'),
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         selector=True
         selector=True
     )
     )
     provider_account = DynamicModelChoiceField(
     provider_account = DynamicModelChoiceField(
+        label=_('Provider account'),
         queryset=ProviderAccount.objects.all(),
         queryset=ProviderAccount.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -103,9 +107,9 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Circuit', ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
-        ('Service Parameters', ('install_date', 'termination_date', 'commit_rate')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Circuit'), ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
+        (_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -125,15 +129,18 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
 
 
 class CircuitTerminationForm(NetBoxModelForm):
 class CircuitTerminationForm(NetBoxModelForm):
     circuit = DynamicModelChoiceField(
     circuit = DynamicModelChoiceField(
+        label=_('Circuit'),
         queryset=Circuit.objects.all(),
         queryset=Circuit.objects.all(),
         selector=True
         selector=True
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
     )
     )
     provider_network = DynamicModelChoiceField(
     provider_network = DynamicModelChoiceField(
+        label=_('Provider network'),
         queryset=ProviderNetwork.objects.all(),
         queryset=ProviderNetwork.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True

+ 6 - 4
netbox/core/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from core.choices import DataSourceTypeChoices
 from core.choices import DataSourceTypeChoices
 from core.models import *
 from core.models import *
@@ -15,6 +15,7 @@ __all__ = (
 
 
 class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
 class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=add_blank_choice(DataSourceTypeChoices),
         choices=add_blank_choice(DataSourceTypeChoices),
         required=False,
         required=False,
         initial=''
         initial=''
@@ -25,16 +26,17 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Enforce unique space')
         label=_('Enforce unique space')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
     parameters = forms.JSONField(
     parameters = forms.JSONField(
+        label=_('Parameters'),
         required=False
         required=False
     )
     )
     ignore_rules = forms.CharField(
     ignore_rules = forms.CharField(
+        label=_('Ignore rules'),
         required=False,
         required=False,
         widget=forms.Textarea()
         widget=forms.Textarea()
     )
     )

+ 17 - 5
netbox/core/forms/filtersets.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from core.choices import *
 from core.choices import *
 from core.models import *
 from core.models import *
@@ -23,17 +23,20 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm):
     model = DataSource
     model = DataSource
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Data Source', ('type', 'status')),
+        (_('Data Source'), ('type', 'status')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=DataSourceTypeChoices,
         choices=DataSourceTypeChoices,
         required=False
         required=False
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=DataSourceStatusChoices,
         choices=DataSourceStatusChoices,
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -45,7 +48,7 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
     model = DataFile
     model = DataFile
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('File', ('source_id',)),
+        (_('File'), ('source_id',)),
     )
     )
     source_id = DynamicModelMultipleChoiceField(
     source_id = DynamicModelMultipleChoiceField(
         queryset=DataSource.objects.all(),
         queryset=DataSource.objects.all(),
@@ -57,8 +60,8 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
 class JobFilterForm(SavedFiltersMixin, FilterForm):
 class JobFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Attributes', ('object_type', 'status')),
-        ('Creation', (
+        (_('Attributes'), ('object_type', 'status')),
+        (_('Creation'), (
             'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
             'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
             'started__after', 'completed__before', 'completed__after', 'user',
             'started__after', 'completed__before', 'completed__after', 'user',
         )),
         )),
@@ -69,38 +72,47 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
         required=False,
         required=False,
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=JobStatusChoices,
         choices=JobStatusChoices,
         required=False
         required=False
     )
     )
     created__after = forms.DateTimeField(
     created__after = forms.DateTimeField(
+        label=_('Created after'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     created__before = forms.DateTimeField(
     created__before = forms.DateTimeField(
+        label=_('Created before'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     scheduled__after = forms.DateTimeField(
     scheduled__after = forms.DateTimeField(
+        label=_('Scheduled after'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     scheduled__before = forms.DateTimeField(
     scheduled__before = forms.DateTimeField(
+        label=_('Scheduled before'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     started__after = forms.DateTimeField(
     started__after = forms.DateTimeField(
+        label=_('Started after'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     started__before = forms.DateTimeField(
     started__before = forms.DateTimeField(
+        label=_('Started before'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     completed__after = forms.DateTimeField(
     completed__after = forms.DateTimeField(
+        label=_('Completed after'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     completed__before = forms.DateTimeField(
     completed__before = forms.DateTimeField(
+        label=_('Completed before'),
         required=False,
         required=False,
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )

+ 1 - 1
netbox/core/forms/mixins.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from core.models import DataFile, DataSource
 from core.models import DataFile, DataSource
 from utilities.forms.fields import DynamicModelChoiceField
 from utilities.forms.fields import DynamicModelChoiceField

+ 5 - 4
netbox/core/forms/model_forms.py

@@ -1,6 +1,7 @@
 import copy
 import copy
 
 
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 from core.forms.mixins import SyncedDataMixin
 from core.forms.mixins import SyncedDataMixin
 from core.models import *
 from core.models import *
@@ -38,11 +39,11 @@ class DataSourceForm(NetBoxModelForm):
     @property
     @property
     def fieldsets(self):
     def fieldsets(self):
         fieldsets = [
         fieldsets = [
-            ('Source', ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
+            (_('Source'), ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
         ]
         ]
         if self.backend_fields:
         if self.backend_fields:
             fieldsets.append(
             fieldsets.append(
-                ('Backend Parameters', self.backend_fields)
+                (_('Backend Parameters'), self.backend_fields)
             )
             )
 
 
         return fieldsets
         return fieldsets
@@ -79,8 +80,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('File Upload', ('upload_file',)),
-        ('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
+        (_('File Upload'), ('upload_file',)),
+        (_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
     )
     )
 
 
     class Meta:
     class Meta:

+ 3 - 1
netbox/dcim/forms/bulk_create.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 
 
 from dcim.models import *
 from dcim.models import *
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 from extras.forms import CustomFieldsMixin
 from extras.forms import CustomFieldsMixin
 from extras.models import Tag
 from extras.models import Tag
 from utilities.forms import BootstrapMixin, form_from_model
 from utilities.forms import BootstrapMixin, form_from_model
@@ -32,10 +32,12 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCre
         widget=forms.MultipleHiddenInput()
         widget=forms.MultipleHiddenInput()
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
+        label=_('Tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
     )
     )

Разница между файлами не показана из-за своего большого размера
+ 166 - 48
netbox/dcim/forms/bulk_edit.py


+ 119 - 2
netbox/dcim/forms/bulk_import.py

@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms.array import SimpleArrayField
 from django.contrib.postgres.forms.array import SimpleArrayField
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -56,6 +56,7 @@ __all__ = (
 
 
 class RegionImportForm(NetBoxModelImportForm):
 class RegionImportForm(NetBoxModelImportForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -69,6 +70,7 @@ class RegionImportForm(NetBoxModelImportForm):
 
 
 class SiteGroupImportForm(NetBoxModelImportForm):
 class SiteGroupImportForm(NetBoxModelImportForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -82,22 +84,26 @@ class SiteGroupImportForm(NetBoxModelImportForm):
 
 
 class SiteImportForm(NetBoxModelImportForm):
 class SiteImportForm(NetBoxModelImportForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=SiteStatusChoices,
         choices=SiteStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     region = CSVModelChoiceField(
     region = CSVModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned region')
         help_text=_('Assigned region')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned group')
         help_text=_('Assigned group')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -119,11 +125,13 @@ class SiteImportForm(NetBoxModelImportForm):
 
 
 class LocationImportForm(NetBoxModelImportForm):
 class LocationImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -133,10 +141,12 @@ class LocationImportForm(NetBoxModelImportForm):
         }
         }
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=LocationStatusChoices,
         choices=LocationStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -161,45 +171,54 @@ class RackRoleImportForm(NetBoxModelImportForm):
 
 
 class RackImportForm(NetBoxModelImportForm):
 class RackImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         to_field_name='name'
         to_field_name='name'
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Name of assigned tenant')
         help_text=_('Name of assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=RackStatusChoices,
         choices=RackStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=RackRole.objects.all(),
         queryset=RackRole.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Name of assigned role')
         help_text=_('Name of assigned role')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=RackTypeChoices,
         choices=RackTypeChoices,
         required=False,
         required=False,
         help_text=_('Rack type')
         help_text=_('Rack type')
     )
     )
     width = forms.ChoiceField(
     width = forms.ChoiceField(
+        label=_('Width'),
         choices=RackWidthChoices,
         choices=RackWidthChoices,
         help_text=_('Rail-to-rail width (in inches)')
         help_text=_('Rail-to-rail width (in inches)')
     )
     )
     outer_unit = CSVChoiceField(
     outer_unit = CSVChoiceField(
+        label=_('Outer unit'),
         choices=RackDimensionUnitChoices,
         choices=RackDimensionUnitChoices,
         required=False,
         required=False,
         help_text=_('Unit for outer dimensions')
         help_text=_('Unit for outer dimensions')
     )
     )
     weight_unit = CSVChoiceField(
     weight_unit = CSVChoiceField(
+        label=_('Weight unit'),
         choices=WeightUnitChoices,
         choices=WeightUnitChoices,
         required=False,
         required=False,
         help_text=_('Unit for rack weights')
         help_text=_('Unit for rack weights')
@@ -225,27 +244,32 @@ class RackImportForm(NetBoxModelImportForm):
 
 
 class RackReservationImportForm(NetBoxModelImportForm):
 class RackReservationImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent site')
         help_text=_('Parent site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_("Rack's location (if any)")
         help_text=_("Rack's location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Rack')
         help_text=_('Rack')
     )
     )
     units = SimpleArrayField(
     units = SimpleArrayField(
+        label=_('Units'),
         base_field=forms.IntegerField(),
         base_field=forms.IntegerField(),
         required=True,
         required=True,
         help_text=_('Comma-separated list of individual unit numbers')
         help_text=_('Comma-separated list of individual unit numbers')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -282,21 +306,25 @@ class ManufacturerImportForm(NetBoxModelImportForm):
 
 
 class DeviceTypeImportForm(NetBoxModelImportForm):
 class DeviceTypeImportForm(NetBoxModelImportForm):
     manufacturer = forms.ModelChoiceField(
     manufacturer = forms.ModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('The manufacturer which produces this device type')
         help_text=_('The manufacturer which produces this device type')
     )
     )
     default_platform = forms.ModelChoiceField(
     default_platform = forms.ModelChoiceField(
+        label=_('Default platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('The default platform for devices of this type (optional)')
         help_text=_('The default platform for devices of this type (optional)')
     )
     )
     weight = forms.DecimalField(
     weight = forms.DecimalField(
+        label=_('Weight'),
         required=False,
         required=False,
         help_text=_('Device weight'),
         help_text=_('Device weight'),
     )
     )
     weight_unit = CSVChoiceField(
     weight_unit = CSVChoiceField(
+        label=_('Weight unit'),
         choices=WeightUnitChoices,
         choices=WeightUnitChoices,
         required=False,
         required=False,
         help_text=_('Unit for device weight')
         help_text=_('Unit for device weight')
@@ -312,14 +340,17 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
 
 
 class ModuleTypeImportForm(NetBoxModelImportForm):
 class ModuleTypeImportForm(NetBoxModelImportForm):
     manufacturer = forms.ModelChoiceField(
     manufacturer = forms.ModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     weight = forms.DecimalField(
     weight = forms.DecimalField(
+        label=_('Weight'),
         required=False,
         required=False,
         help_text=_('Module weight'),
         help_text=_('Module weight'),
     )
     )
     weight_unit = CSVChoiceField(
     weight_unit = CSVChoiceField(
+        label=_('Weight unit'),
         choices=WeightUnitChoices,
         choices=WeightUnitChoices,
         required=False,
         required=False,
         help_text=_('Unit for module weight')
         help_text=_('Unit for module weight')
@@ -332,6 +363,7 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
 
 
 class DeviceRoleImportForm(NetBoxModelImportForm):
 class DeviceRoleImportForm(NetBoxModelImportForm):
     config_template = CSVModelChoiceField(
     config_template = CSVModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -350,12 +382,14 @@ class DeviceRoleImportForm(NetBoxModelImportForm):
 class PlatformImportForm(NetBoxModelImportForm):
 class PlatformImportForm(NetBoxModelImportForm):
     slug = SlugField()
     slug = SlugField()
     manufacturer = CSVModelChoiceField(
     manufacturer = CSVModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Limit platform assignments to this manufacturer')
         help_text=_('Limit platform assignments to this manufacturer')
     )
     )
     config_template = CSVModelChoiceField(
     config_template = CSVModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -371,43 +405,51 @@ class PlatformImportForm(NetBoxModelImportForm):
 
 
 class BaseDeviceImportForm(NetBoxModelImportForm):
 class BaseDeviceImportForm(NetBoxModelImportForm):
     device_role = CSVModelChoiceField(
     device_role = CSVModelChoiceField(
+        label=_('Device role'),
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned role')
         help_text=_('Assigned role')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     manufacturer = CSVModelChoiceField(
     manufacturer = CSVModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Device type manufacturer')
         help_text=_('Device type manufacturer')
     )
     )
     device_type = CSVModelChoiceField(
     device_type = CSVModelChoiceField(
+        label=_('Device type'),
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
         to_field_name='model',
         to_field_name='model',
         help_text=_('Device type model')
         help_text=_('Device type model')
     )
     )
     platform = CSVModelChoiceField(
     platform = CSVModelChoiceField(
+        label=_('Platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned platform')
         help_text=_('Assigned platform')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=DeviceStatusChoices,
         choices=DeviceStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     virtual_chassis = CSVModelChoiceField(
     virtual_chassis = CSVModelChoiceField(
+        label=_('Virtual chassis'),
         queryset=VirtualChassis.objects.all(),
         queryset=VirtualChassis.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Virtual chassis')
         help_text=_('Virtual chassis')
     )
     )
     cluster = CSVModelChoiceField(
     cluster = CSVModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -430,45 +472,53 @@ class BaseDeviceImportForm(NetBoxModelImportForm):
 
 
 class DeviceImportForm(BaseDeviceImportForm):
 class DeviceImportForm(BaseDeviceImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_("Assigned location (if any)")
         help_text=_("Assigned location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_("Assigned rack (if any)")
         help_text=_("Assigned rack (if any)")
     )
     )
     face = CSVChoiceField(
     face = CSVChoiceField(
+        label=_('Face'),
         choices=DeviceFaceChoices,
         choices=DeviceFaceChoices,
         required=False,
         required=False,
         help_text=_('Mounted rack face')
         help_text=_('Mounted rack face')
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Parent device (for child devices)')
         help_text=_('Parent device (for child devices)')
     )
     )
     device_bay = CSVModelChoiceField(
     device_bay = CSVModelChoiceField(
+        label=_('Device bay'),
         queryset=DeviceBay.objects.all(),
         queryset=DeviceBay.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Device bay in which this device is installed (for child devices)')
         help_text=_('Device bay in which this device is installed (for child devices)')
     )
     )
     airflow = CSVChoiceField(
     airflow = CSVChoiceField(
+        label=_('Airflow'),
         choices=DeviceAirflowChoices,
         choices=DeviceAirflowChoices,
         required=False,
         required=False,
         help_text=_('Airflow direction')
         help_text=_('Airflow direction')
     )
     )
     config_template = CSVModelChoiceField(
     config_template = CSVModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -523,29 +573,35 @@ class DeviceImportForm(BaseDeviceImportForm):
 
 
 class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
 class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('The device in which this module is installed')
         help_text=_('The device in which this module is installed')
     )
     )
     module_bay = CSVModelChoiceField(
     module_bay = CSVModelChoiceField(
+        label=_('Module bay'),
         queryset=ModuleBay.objects.all(),
         queryset=ModuleBay.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('The module bay in which this module is installed')
         help_text=_('The module bay in which this module is installed')
     )
     )
     module_type = CSVModelChoiceField(
     module_type = CSVModelChoiceField(
+        label=_('Module type'),
         queryset=ModuleType.objects.all(),
         queryset=ModuleType.objects.all(),
         to_field_name='model',
         to_field_name='model',
         help_text=_('The type of module')
         help_text=_('The type of module')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=ModuleStatusChoices,
         choices=ModuleStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     replicate_components = forms.BooleanField(
     replicate_components = forms.BooleanField(
+        label=_('Replicate components'),
         required=False,
         required=False,
         help_text=_('Automatically populate components associated with this module type (enabled by default)')
         help_text=_('Automatically populate components associated with this module type (enabled by default)')
     )
     )
     adopt_components = forms.BooleanField(
     adopt_components = forms.BooleanField(
+        label=_('Adopt components'),
         required=False,
         required=False,
         help_text=_('Adopt already existing components')
         help_text=_('Adopt already existing components')
     )
     )
@@ -579,15 +635,18 @@ class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
 
 
 class ConsolePortImportForm(NetBoxModelImportForm):
 class ConsolePortImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False,
         required=False,
         help_text=_('Port type')
         help_text=_('Port type')
     )
     )
     speed = CSVTypedChoiceField(
     speed = CSVTypedChoiceField(
+        label=_('Speed'),
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         coerce=int,
         coerce=int,
         empty_value=None,
         empty_value=None,
@@ -602,15 +661,18 @@ class ConsolePortImportForm(NetBoxModelImportForm):
 
 
 class ConsoleServerPortImportForm(NetBoxModelImportForm):
 class ConsoleServerPortImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False,
         required=False,
         help_text=_('Port type')
         help_text=_('Port type')
     )
     )
     speed = CSVTypedChoiceField(
     speed = CSVTypedChoiceField(
+        label=_('Speed'),
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         coerce=int,
         coerce=int,
         empty_value=None,
         empty_value=None,
@@ -625,10 +687,12 @@ class ConsoleServerPortImportForm(NetBoxModelImportForm):
 
 
 class PowerPortImportForm(NetBoxModelImportForm):
 class PowerPortImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=PowerPortTypeChoices,
         choices=PowerPortTypeChoices,
         required=False,
         required=False,
         help_text=_('Port type')
         help_text=_('Port type')
@@ -643,21 +707,25 @@ class PowerPortImportForm(NetBoxModelImportForm):
 
 
 class PowerOutletImportForm(NetBoxModelImportForm):
 class PowerOutletImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=PowerOutletTypeChoices,
         choices=PowerOutletTypeChoices,
         required=False,
         required=False,
         help_text=_('Outlet type')
         help_text=_('Outlet type')
     )
     )
     power_port = CSVModelChoiceField(
     power_port = CSVModelChoiceField(
+        label=_('Power port'),
         queryset=PowerPort.objects.all(),
         queryset=PowerPort.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         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(
     feed_leg = CSVChoiceField(
+        label=_('Feed lag'),
         choices=PowerOutletFeedLegChoices,
         choices=PowerOutletFeedLegChoices,
         required=False,
         required=False,
         help_text=_('Electrical phase (for three-phase circuits)')
         help_text=_('Electrical phase (for three-phase circuits)')
@@ -692,63 +760,75 @@ class PowerOutletImportForm(NetBoxModelImportForm):
 
 
 class InterfaceImportForm(NetBoxModelImportForm):
 class InterfaceImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent interface')
         help_text=_('Parent interface')
     )
     )
     bridge = CSVModelChoiceField(
     bridge = CSVModelChoiceField(
+        label=_('Bridge'),
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Bridged interface')
         help_text=_('Bridged interface')
     )
     )
     lag = CSVModelChoiceField(
     lag = CSVModelChoiceField(
+        label=_('Lag'),
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent LAG interface')
         help_text=_('Parent LAG interface')
     )
     )
     vdcs = CSVModelMultipleChoiceField(
     vdcs = CSVModelMultipleChoiceField(
+        label=_('Vdcs'),
         queryset=VirtualDeviceContext.objects.all(),
         queryset=VirtualDeviceContext.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
-        help_text='VDC names separated by commas, encased with double quotes (e.g. "vdc1, vdc2, vdc3")'
+        help_text=_('VDC names separated by commas, encased with double quotes (e.g. "vdc1, vdc2, vdc3")')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         help_text=_('Physical medium')
         help_text=_('Physical medium')
     )
     )
     duplex = CSVChoiceField(
     duplex = CSVChoiceField(
+        label=_('Duplex'),
         choices=InterfaceDuplexChoices,
         choices=InterfaceDuplexChoices,
         required=False
         required=False
     )
     )
     poe_mode = CSVChoiceField(
     poe_mode = CSVChoiceField(
+        label=_('Poe mode'),
         choices=InterfacePoEModeChoices,
         choices=InterfacePoEModeChoices,
         required=False,
         required=False,
         help_text=_('PoE mode')
         help_text=_('PoE mode')
     )
     )
     poe_type = CSVChoiceField(
     poe_type = CSVChoiceField(
+        label=_('Poe type'),
         choices=InterfacePoETypeChoices,
         choices=InterfacePoETypeChoices,
         required=False,
         required=False,
         help_text=_('PoE type')
         help_text=_('PoE type')
     )
     )
     mode = CSVChoiceField(
     mode = CSVChoiceField(
+        label=_('Mode'),
         choices=InterfaceModeChoices,
         choices=InterfaceModeChoices,
         required=False,
         required=False,
         help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
         help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
     )
     )
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
+        label=_('VRF'),
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         to_field_name='rd',
         to_field_name='rd',
         help_text=_('Assigned VRF')
         help_text=_('Assigned VRF')
     )
     )
     rf_role = CSVChoiceField(
     rf_role = CSVChoiceField(
+        label=_('Rf role'),
         choices=WirelessRoleChoices,
         choices=WirelessRoleChoices,
         required=False,
         required=False,
         help_text=_('Wireless role (AP/station)')
         help_text=_('Wireless role (AP/station)')
@@ -792,15 +872,18 @@ class InterfaceImportForm(NetBoxModelImportForm):
 
 
 class FrontPortImportForm(NetBoxModelImportForm):
 class FrontPortImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     rear_port = CSVModelChoiceField(
     rear_port = CSVModelChoiceField(
+        label=_('Rear port'),
         queryset=RearPort.objects.all(),
         queryset=RearPort.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Corresponding rear port')
         help_text=_('Corresponding rear port')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         help_text=_('Physical medium classification')
         help_text=_('Physical medium classification')
     )
     )
@@ -837,10 +920,12 @@ class FrontPortImportForm(NetBoxModelImportForm):
 
 
 class RearPortImportForm(NetBoxModelImportForm):
 class RearPortImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         help_text=_('Physical medium classification'),
         help_text=_('Physical medium classification'),
         choices=PortTypeChoices,
         choices=PortTypeChoices,
     )
     )
@@ -852,6 +937,7 @@ class RearPortImportForm(NetBoxModelImportForm):
 
 
 class ModuleBayImportForm(NetBoxModelImportForm):
 class ModuleBayImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
@@ -863,10 +949,12 @@ class ModuleBayImportForm(NetBoxModelImportForm):
 
 
 class DeviceBayImportForm(NetBoxModelImportForm):
 class DeviceBayImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     installed_device = CSVModelChoiceField(
     installed_device = CSVModelChoiceField(
+        label=_('Installed device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -909,32 +997,38 @@ class DeviceBayImportForm(NetBoxModelImportForm):
 
 
 class InventoryItemImportForm(NetBoxModelImportForm):
 class InventoryItemImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=InventoryItemRole.objects.all(),
         queryset=InventoryItemRole.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False
     )
     )
     manufacturer = CSVModelChoiceField(
     manufacturer = CSVModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Parent inventory item')
         help_text=_('Parent inventory item')
     )
     )
     component_type = CSVContentTypeField(
     component_type = CSVContentTypeField(
+        label=_('Component type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=MODULAR_COMPONENT_MODELS,
         limit_choices_to=MODULAR_COMPONENT_MODELS,
         required=False,
         required=False,
         help_text=_('Component Type')
         help_text=_('Component Type')
     )
     )
     component_name = forms.CharField(
     component_name = forms.CharField(
+        label=_('Compnent name'),
         required=False,
         required=False,
         help_text=_('Component Name')
         help_text=_('Component Name')
     )
     )
@@ -1002,52 +1096,62 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
 class CableImportForm(NetBoxModelImportForm):
 class CableImportForm(NetBoxModelImportForm):
     # Termination A
     # Termination A
     side_a_device = CSVModelChoiceField(
     side_a_device = CSVModelChoiceField(
+        label=_('Side a device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Side A device')
         help_text=_('Side A device')
     )
     )
     side_a_type = CSVContentTypeField(
     side_a_type = CSVContentTypeField(
+        label=_('Side a type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=CABLE_TERMINATION_MODELS,
         limit_choices_to=CABLE_TERMINATION_MODELS,
         help_text=_('Side A type')
         help_text=_('Side A type')
     )
     )
     side_a_name = forms.CharField(
     side_a_name = forms.CharField(
+        label=_('Side a name'),
         help_text=_('Side A component name')
         help_text=_('Side A component name')
     )
     )
 
 
     # Termination B
     # Termination B
     side_b_device = CSVModelChoiceField(
     side_b_device = CSVModelChoiceField(
+        label=_('Side b device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Side B device')
         help_text=_('Side B device')
     )
     )
     side_b_type = CSVContentTypeField(
     side_b_type = CSVContentTypeField(
+        label=_('Side b type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=CABLE_TERMINATION_MODELS,
         limit_choices_to=CABLE_TERMINATION_MODELS,
         help_text=_('Side B type')
         help_text=_('Side B type')
     )
     )
     side_b_name = forms.CharField(
     side_b_name = forms.CharField(
+        label=_('Side b name'),
         help_text=_('Side B component name')
         help_text=_('Side B component name')
     )
     )
 
 
     # Cable attributes
     # Cable attributes
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
         required=False,
         required=False,
         help_text=_('Connection status')
         help_text=_('Connection status')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=CableTypeChoices,
         choices=CableTypeChoices,
         required=False,
         required=False,
         help_text=_('Physical medium classification')
         help_text=_('Physical medium classification')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     length_unit = CSVChoiceField(
     length_unit = CSVChoiceField(
+        label=_('Length unit'),
         choices=CableLengthUnitChoices,
         choices=CableLengthUnitChoices,
         required=False,
         required=False,
         help_text=_('Length unit')
         help_text=_('Length unit')
@@ -1110,6 +1214,7 @@ class CableImportForm(NetBoxModelImportForm):
 
 
 class VirtualChassisImportForm(NetBoxModelImportForm):
 class VirtualChassisImportForm(NetBoxModelImportForm):
     master = CSVModelChoiceField(
     master = CSVModelChoiceField(
+        label=_('Master'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -1127,11 +1232,13 @@ class VirtualChassisImportForm(NetBoxModelImportForm):
 
 
 class PowerPanelImportForm(NetBoxModelImportForm):
 class PowerPanelImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Name of parent site')
         help_text=_('Name of parent site')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         to_field_name='name'
         to_field_name='name'
@@ -1153,22 +1260,26 @@ class PowerPanelImportForm(NetBoxModelImportForm):
 
 
 class PowerFeedImportForm(NetBoxModelImportForm):
 class PowerFeedImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     power_panel = CSVModelChoiceField(
     power_panel = CSVModelChoiceField(
+        label=_('Power panel'),
         queryset=PowerPanel.objects.all(),
         queryset=PowerPanel.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Upstream power panel')
         help_text=_('Upstream power panel')
     )
     )
     location = CSVModelChoiceField(
     location = CSVModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_("Rack's location (if any)")
         help_text=_("Rack's location (if any)")
     )
     )
     rack = CSVModelChoiceField(
     rack = CSVModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -1181,18 +1292,22 @@ class PowerFeedImportForm(NetBoxModelImportForm):
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=PowerFeedStatusChoices,
         choices=PowerFeedStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=PowerFeedTypeChoices,
         choices=PowerFeedTypeChoices,
         help_text=_('Primary or redundant')
         help_text=_('Primary or redundant')
     )
     )
     supply = CSVChoiceField(
     supply = CSVChoiceField(
+        label=_('Supply'),
         choices=PowerFeedSupplyChoices,
         choices=PowerFeedSupplyChoices,
         help_text=_('Supply type (AC/DC)')
         help_text=_('Supply type (AC/DC)')
     )
     )
     phase = CSVChoiceField(
     phase = CSVChoiceField(
+        label=_('Phase'),
         choices=PowerFeedPhaseChoices,
         choices=PowerFeedPhaseChoices,
         help_text=_('Single or three-phase')
         help_text=_('Single or three-phase')
     )
     )
@@ -1228,11 +1343,13 @@ class PowerFeedImportForm(NetBoxModelImportForm):
 class VirtualDeviceContextImportForm(NetBoxModelImportForm):
 class VirtualDeviceContextImportForm(NetBoxModelImportForm):
 
 
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text='Assigned role'
         help_text='Assigned role'
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',

+ 15 - 8
netbox/dcim/forms/common.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -47,7 +47,7 @@ class InterfaceCommonForm(forms.Form):
         # Untagged interfaces cannot be assigned tagged VLANs
         # Untagged interfaces cannot be assigned tagged VLANs
         if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
         if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
             raise forms.ValidationError({
             raise forms.ValidationError({
-                'mode': "An access interface cannot have tagged VLANs assigned."
+                'mode': _("An access interface cannot have tagged VLANs assigned.")
             })
             })
 
 
         # Remove all tagged VLAN assignments from "tagged all" interfaces
         # Remove all tagged VLAN assignments from "tagged all" interfaces
@@ -61,8 +61,10 @@ class InterfaceCommonForm(forms.Form):
 
 
             if invalid_vlans:
             if invalid_vlans:
                 raise forms.ValidationError({
                 raise forms.ValidationError({
-                    'tagged_vlans': f"The tagged VLANs ({', '.join(invalid_vlans)}) must belong to the same site as "
-                                    f"the interface's parent device/VM, or they must be global"
+                    'tagged_vlans': _(
+                        "The tagged VLANs ({vlans}) must belong to the same site as the interface's parent device/VM, "
+                        "or they must be global"
+                    ).format(vlans=', '.join(invalid_vlans))
                 })
                 })
 
 
 
 
@@ -105,7 +107,7 @@ class ModuleCommonForm(forms.Form):
                 # Installing modules with placeholders require that the bay has a position value
                 # Installing modules with placeholders require that the bay has a position value
                 if MODULE_TOKEN in template.name and not module_bay.position:
                 if MODULE_TOKEN in template.name and not module_bay.position:
                     raise forms.ValidationError(
                     raise forms.ValidationError(
-                        "Cannot install module with placeholder values in a module bay with no position defined"
+                        _("Cannot install module with placeholder values in a module bay with no position defined.")
                     )
                     )
 
 
                 resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
                 resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
@@ -114,12 +116,17 @@ class ModuleCommonForm(forms.Form):
                 # It is not possible to adopt components already belonging to a module
                 # It is not possible to adopt components already belonging to a module
                 if adopt_components and existing_item and existing_item.module:
                 if adopt_components and existing_item and existing_item.module:
                     raise forms.ValidationError(
                     raise forms.ValidationError(
-                        f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs "
-                        f"to a module"
+                        _("Cannot adopt {name} '{resolved_name}' as it already belongs to a module").format(
+                            name=template.component_model.__name__,
+                            resolved_name=resolved_name
+                        )
                     )
                     )
 
 
                 # If we are not adopting components we error if the component exists
                 # If we are not adopting components we error if the component exists
                 if not adopt_components and resolved_name in installed_components:
                 if not adopt_components and resolved_name in installed_components:
                     raise forms.ValidationError(
                     raise forms.ValidationError(
-                        f"{template.component_model.__name__} - {resolved_name} already exists"
+                        _("{name} - {resolved_name} already exists").format(
+                            name=template.component_model.__name__,
+                            resolved_name=resolved_name
+                        )
                     )
                     )

+ 1 - 1
netbox/dcim/forms/connections.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from circuits.models import Circuit, CircuitTermination
 from circuits.models import Circuit, CircuitTermination
 from dcim.models import *
 from dcim.models import *

+ 194 - 129
netbox/dcim/forms/filtersets.py

@@ -1,6 +1,6 @@
 from django import forms
 from django import forms
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
@@ -56,9 +56,11 @@ __all__ = (
 
 
 class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
 class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
     name = forms.CharField(
     name = forms.CharField(
+        label=_('Name'),
         required=False
         required=False
     )
     )
     label = forms.CharField(
     label = forms.CharField(
+        label=_('Label'),
         required=False
         required=False
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
@@ -130,7 +132,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     model = Region
     model = Region
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag', 'parent_id')),
         (None, ('q', 'filter_id', 'tag', 'parent_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group'))
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group'))
     )
     )
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -144,7 +146,7 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     model = SiteGroup
     model = SiteGroup
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag', 'parent_id')),
         (None, ('q', 'filter_id', 'tag', 'parent_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group'))
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group'))
     )
     )
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
@@ -158,11 +160,12 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
     model = Site
     model = Site
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Attributes'), ('status', 'region_id', 'group_id', 'asn_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=SiteStatusChoices,
         choices=SiteStatusChoices,
         required=False
         required=False
     )
     )
@@ -188,9 +191,9 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
     model = Location
     model = Location
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Attributes'), ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -221,6 +224,7 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
         label=_('Parent')
         label=_('Parent')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=LocationStatusChoices,
         choices=LocationStatusChoices,
         required=False
         required=False
     )
     )
@@ -236,12 +240,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
     model = Rack
     model = Rack
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
-        ('Function', ('status', 'role_id')),
-        ('Hardware', ('type', 'width', 'serial', 'asset_tag')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
-        ('Weight', ('weight', 'max_weight', 'weight_unit')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
+        (_('Function'), ('status', 'role_id')),
+        (_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
+        (_('Weight'), ('weight', 'max_weight', 'weight_unit')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -271,14 +275,17 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
         label=_('Location')
         label=_('Location')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=RackStatusChoices,
         choices=RackStatusChoices,
         required=False
         required=False
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=RackTypeChoices,
         choices=RackTypeChoices,
         required=False
         required=False
     )
     )
     width = forms.MultipleChoiceField(
     width = forms.MultipleChoiceField(
+        label=_('Width'),
         choices=RackWidthChoices,
         choices=RackWidthChoices,
         required=False
         required=False
     )
     )
@@ -289,21 +296,26 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
         label=_('Role')
         label=_('Role')
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
+        label=_('Serial'),
         required=False
         required=False
     )
     )
     asset_tag = forms.CharField(
     asset_tag = forms.CharField(
+        label=_('Asset tag'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     weight = forms.DecimalField(
     weight = forms.DecimalField(
+        label=_('Weight'),
         required=False,
         required=False,
         min_value=1
         min_value=1
     )
     )
     max_weight = forms.IntegerField(
     max_weight = forms.IntegerField(
+        label=_('Max weight'),
         required=False,
         required=False,
         min_value=1
         min_value=1
     )
     )
     weight_unit = forms.ChoiceField(
     weight_unit = forms.ChoiceField(
+        label=_('Weight unit'),
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),
         required=False
         required=False
     )
     )
@@ -312,12 +324,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
 class RackElevationFilterForm(RackFilterForm):
 class RackElevationFilterForm(RackFilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
-        ('Function', ('status', 'role_id')),
-        ('Hardware', ('type', 'width', 'serial', 'asset_tag')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
-        ('Weight', ('weight', 'max_weight', 'weight_unit')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
+        (_('Function'), ('status', 'role_id')),
+        (_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
+        (_('Weight'), ('weight', 'max_weight', 'weight_unit')),
     )
     )
     id = DynamicModelMultipleChoiceField(
     id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
@@ -334,9 +346,9 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RackReservation
     model = RackReservation
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('User', ('user_id',)),
-        ('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('User'), ('user_id',)),
+        (_('Rack'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -390,7 +402,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     model = Manufacturer
     model = Manufacturer
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group'))
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group'))
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -399,13 +411,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
     model = DeviceType
     model = DeviceType
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Hardware', ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
-        ('Images', ('has_front_image', 'has_rear_image')),
-        ('Components', (
+        (_('Hardware'), ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
+        (_('Images'), ('has_front_image', 'has_rear_image')),
+        (_('Components'), (
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
             'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
         )),
         )),
-        ('Weight', ('weight', 'weight_unit')),
+        (_('Weight'), ('weight', 'weight_unit')),
     )
     )
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
@@ -418,98 +430,103 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
         label=_('Default platform')
         label=_('Default platform')
     )
     )
     part_number = forms.CharField(
     part_number = forms.CharField(
+        label=_('Part number'),
         required=False
         required=False
     )
     )
     subdevice_role = forms.MultipleChoiceField(
     subdevice_role = forms.MultipleChoiceField(
+        label=_('Subdevice role'),
         choices=add_blank_choice(SubdeviceRoleChoices),
         choices=add_blank_choice(SubdeviceRoleChoices),
         required=False
         required=False
     )
     )
     airflow = forms.MultipleChoiceField(
     airflow = forms.MultipleChoiceField(
+        label=_('Airflow'),
         choices=add_blank_choice(DeviceAirflowChoices),
         choices=add_blank_choice(DeviceAirflowChoices),
         required=False
         required=False
     )
     )
     has_front_image = forms.NullBooleanField(
     has_front_image = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has a front image',
+        label=_('Has a front image'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     has_rear_image = forms.NullBooleanField(
     has_rear_image = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has a rear image',
+        label=_('Has a rear image'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     console_ports = forms.NullBooleanField(
     console_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console ports',
+        label=_('Has console ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     console_server_ports = forms.NullBooleanField(
     console_server_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console server ports',
+        label=_('Has console server ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_ports = forms.NullBooleanField(
     power_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power ports',
+        label=_('Has power ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_outlets = forms.NullBooleanField(
     power_outlets = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power outlets',
+        label=_('Has power outlets'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     interfaces = forms.NullBooleanField(
     interfaces = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has interfaces',
+        label=_('Has interfaces'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     pass_through_ports = forms.NullBooleanField(
     pass_through_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has pass-through ports',
+        label=_('Has pass-through ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     device_bays = forms.NullBooleanField(
     device_bays = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has device bays',
+        label=_('Has device bays'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     module_bays = forms.NullBooleanField(
     module_bays = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has module bays',
+        label=_('Has module bays'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     inventory_items = forms.NullBooleanField(
     inventory_items = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has inventory items',
+        label=_('Has inventory items'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     weight = forms.DecimalField(
     weight = forms.DecimalField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     weight_unit = forms.ChoiceField(
     weight_unit = forms.ChoiceField(
+        label=_('Weight unit'),
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),
         required=False
         required=False
     )
     )
@@ -519,12 +536,12 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
     model = ModuleType
     model = ModuleType
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Hardware', ('manufacturer_id', 'part_number')),
-        ('Components', (
+        (_('Hardware'), ('manufacturer_id', 'part_number')),
+        (_('Components'), (
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
             'pass_through_ports',
             'pass_through_ports',
         )),
         )),
-        ('Weight', ('weight', 'weight_unit')),
+        (_('Weight'), ('weight', 'weight_unit')),
     )
     )
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
@@ -533,55 +550,58 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
         fetch_trigger='open'
         fetch_trigger='open'
     )
     )
     part_number = forms.CharField(
     part_number = forms.CharField(
+        label=_('Part number'),
         required=False
         required=False
     )
     )
     console_ports = forms.NullBooleanField(
     console_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console ports',
+        label=_('Has console ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     console_server_ports = forms.NullBooleanField(
     console_server_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console server ports',
+        label=_('Has console server ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_ports = forms.NullBooleanField(
     power_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power ports',
+        label=_('Has power ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_outlets = forms.NullBooleanField(
     power_outlets = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power outlets',
+        label=_('Has power outlets'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     interfaces = forms.NullBooleanField(
     interfaces = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has interfaces',
+        label=_('Has interfaces'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     pass_through_ports = forms.NullBooleanField(
     pass_through_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has pass-through ports',
+        label=_('Has pass-through ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     weight = forms.DecimalField(
     weight = forms.DecimalField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     weight_unit = forms.ChoiceField(
     weight_unit = forms.ChoiceField(
+        label=_('Weight unit'),
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),
         required=False
         required=False
     )
     )
@@ -621,15 +641,17 @@ class DeviceFilterForm(
     model = Device
     model = Device
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
-        ('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
-        ('Components', (
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Operation'), ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
+        (_('Hardware'), ('manufacturer_id', 'device_type_id', 'platform_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
+        (_('Components'), (
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
         )),
         )),
-        ('Miscellaneous', ('has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data'))
+        (_('Miscellaneous'), (
+            'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
+        ))
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -694,22 +716,26 @@ class DeviceFilterForm(
         label=_('Platform')
         label=_('Platform')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=DeviceStatusChoices,
         choices=DeviceStatusChoices,
         required=False
         required=False
     )
     )
     airflow = forms.MultipleChoiceField(
     airflow = forms.MultipleChoiceField(
+        label=_('Airflow'),
         choices=add_blank_choice(DeviceAirflowChoices),
         choices=add_blank_choice(DeviceAirflowChoices),
         required=False
         required=False
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
+        label=_('Serial'),
         required=False
         required=False
     )
     )
     asset_tag = forms.CharField(
     asset_tag = forms.CharField(
+        label=_('Asset tag'),
         required=False
         required=False
     )
     )
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         required=False,
         required=False,
-        label='MAC address'
+        label=_('MAC address')
     )
     )
     config_template_id = DynamicModelMultipleChoiceField(
     config_template_id = DynamicModelMultipleChoiceField(
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
@@ -718,7 +744,7 @@ class DeviceFilterForm(
     )
     )
     has_primary_ip = forms.NullBooleanField(
     has_primary_ip = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has a primary IP',
+        label=_('Has a primary IP'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
@@ -732,49 +758,49 @@ class DeviceFilterForm(
     )
     )
     virtual_chassis_member = forms.NullBooleanField(
     virtual_chassis_member = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Virtual chassis member',
+        label=_('Virtual chassis member'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     console_ports = forms.NullBooleanField(
     console_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console ports',
+        label=_('Has console ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     console_server_ports = forms.NullBooleanField(
     console_server_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has console server ports',
+        label=_('Has console server ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_ports = forms.NullBooleanField(
     power_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power ports',
+        label=_('Has power ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     power_outlets = forms.NullBooleanField(
     power_outlets = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has power outlets',
+        label=_('Has power outlets'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     interfaces = forms.NullBooleanField(
     interfaces = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has interfaces',
+        label=_('Has interfaces'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     pass_through_ports = forms.NullBooleanField(
     pass_through_ports = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has pass-through ports',
+        label=_('Has pass-through ports'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
@@ -789,8 +815,8 @@ class VirtualDeviceContextFilterForm(
     model = VirtualDeviceContext
     model = VirtualDeviceContext
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('device', 'status', 'has_primary_ip')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Attributes'), ('device', 'status', 'has_primary_ip')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     device = DynamicModelMultipleChoiceField(
     device = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -799,12 +825,13 @@ class VirtualDeviceContextFilterForm(
         fetch_trigger='open'
         fetch_trigger='open'
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         required=False,
         required=False,
         choices=add_blank_choice(VirtualDeviceContextStatusChoices)
         choices=add_blank_choice(VirtualDeviceContextStatusChoices)
     )
     )
     has_primary_ip = forms.NullBooleanField(
     has_primary_ip = forms.NullBooleanField(
         required=False,
         required=False,
-        label='Has a primary IP',
+        label=_('Has a primary IP'),
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
@@ -816,7 +843,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
     model = Module
     model = Module
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Hardware', ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
+        (_('Hardware'), ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
     )
     )
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
@@ -834,13 +861,16 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
         fetch_trigger='open'
         fetch_trigger='open'
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=ModuleStatusChoices,
         choices=ModuleStatusChoices,
         required=False
         required=False
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
+        label=_('Serial'),
         required=False
         required=False
     )
     )
     asset_tag = forms.CharField(
     asset_tag = forms.CharField(
+        label=_('Asset tag'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -850,8 +880,8 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VirtualChassis
     model = VirtualChassis
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -879,9 +909,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Cable
     model = Cable
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('site_id', 'location_id', 'rack_id', 'device_id')),
-        ('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')),
+        (_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -927,20 +957,25 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Device')
         label=_('Device')
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=add_blank_choice(CableTypeChoices),
         choices=add_blank_choice(CableTypeChoices),
         required=False
         required=False
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         required=False,
         required=False,
         choices=add_blank_choice(LinkStatusChoices)
         choices=add_blank_choice(LinkStatusChoices)
     )
     )
     color = ColorField(
     color = ColorField(
+        label=_('Color'),
         required=False
         required=False
     )
     )
     length = forms.IntegerField(
     length = forms.IntegerField(
+        label=_('Length'),
         required=False
         required=False
     )
     )
     length_unit = forms.ChoiceField(
     length_unit = forms.ChoiceField(
+        label=_('Length unit'),
         choices=add_blank_choice(CableLengthUnitChoices),
         choices=add_blank_choice(CableLengthUnitChoices),
         required=False
         required=False
     )
     )
@@ -951,8 +986,8 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     model = PowerPanel
     model = PowerPanel
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -989,9 +1024,9 @@ class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = PowerFeed
     model = PowerFeed
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Attributes'), ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -1030,28 +1065,35 @@ class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Rack')
         label=_('Rack')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=PowerFeedStatusChoices,
         choices=PowerFeedStatusChoices,
         required=False
         required=False
     )
     )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=add_blank_choice(PowerFeedTypeChoices),
         choices=add_blank_choice(PowerFeedTypeChoices),
         required=False
         required=False
     )
     )
     supply = forms.ChoiceField(
     supply = forms.ChoiceField(
+        label=_('Supply'),
         choices=add_blank_choice(PowerFeedSupplyChoices),
         choices=add_blank_choice(PowerFeedSupplyChoices),
         required=False
         required=False
     )
     )
     phase = forms.ChoiceField(
     phase = forms.ChoiceField(
+        label=_('Phase'),
         choices=add_blank_choice(PowerFeedPhaseChoices),
         choices=add_blank_choice(PowerFeedPhaseChoices),
         required=False
         required=False
     )
     )
     voltage = forms.IntegerField(
     voltage = forms.IntegerField(
+        label=_('Voltage'),
         required=False
         required=False
     )
     )
     amperage = forms.IntegerField(
     amperage = forms.IntegerField(
+        label=_('Amperage'),
         required=False
         required=False
     )
     )
     max_utilization = forms.IntegerField(
     max_utilization = forms.IntegerField(
+        label=_('Max utilization'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -1063,12 +1105,14 @@ class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
 
 
 class CabledFilterForm(forms.Form):
 class CabledFilterForm(forms.Form):
     cabled = forms.NullBooleanField(
     cabled = forms.NullBooleanField(
+        label=_('Cabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     occupied = forms.NullBooleanField(
     occupied = forms.NullBooleanField(
+        label=_('Occupied'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1078,6 +1122,7 @@ class CabledFilterForm(forms.Form):
 
 
 class PathEndpointFilterForm(CabledFilterForm):
 class PathEndpointFilterForm(CabledFilterForm):
     connected = forms.NullBooleanField(
     connected = forms.NullBooleanField(
+        label=_('Connected'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1089,16 +1134,18 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
     model = ConsolePort
     model = ConsolePort
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type', 'speed')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Connection', ('cabled', 'connected', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type', 'speed')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Connection'), ('cabled', 'connected', 'occupied')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False
         required=False
     )
     )
     speed = forms.MultipleChoiceField(
     speed = forms.MultipleChoiceField(
+        label=_('Speed'),
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         required=False
         required=False
     )
     )
@@ -1109,16 +1156,18 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
     model = ConsoleServerPort
     model = ConsoleServerPort
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type', 'speed')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Connection', ('cabled', 'connected', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type', 'speed')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Connection'), ('cabled', 'connected', 'occupied')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=ConsolePortTypeChoices,
         choices=ConsolePortTypeChoices,
         required=False
         required=False
     )
     )
     speed = forms.MultipleChoiceField(
     speed = forms.MultipleChoiceField(
+        label=_('Speed'),
         choices=ConsolePortSpeedChoices,
         choices=ConsolePortSpeedChoices,
         required=False
         required=False
     )
     )
@@ -1129,12 +1178,13 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
     model = PowerPort
     model = PowerPort
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Connection', ('cabled', 'connected', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Connection'), ('cabled', 'connected', 'occupied')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=PowerPortTypeChoices,
         choices=PowerPortTypeChoices,
         required=False
         required=False
     )
     )
@@ -1145,12 +1195,13 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
     model = PowerOutlet
     model = PowerOutlet
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Connection', ('cabled', 'connected', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Connection'), ('cabled', 'connected', 'occupied')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=PowerOutletTypeChoices,
         choices=PowerOutletTypeChoices,
         required=False
         required=False
     )
     )
@@ -1161,13 +1212,13 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
     model = Interface
     model = Interface
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
-        ('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
-        ('PoE', ('poe_mode', 'poe_type')),
-        ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
-        ('Connection', ('cabled', 'connected', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
+        (_('Addressing'), ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
+        (_('PoE'), ('poe_mode', 'poe_type')),
+        (_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
+        (_('Connection'), ('cabled', 'connected', 'occupied')),
     )
     )
     vdc_id = DynamicModelMultipleChoiceField(
     vdc_id = DynamicModelMultipleChoiceField(
         queryset=VirtualDeviceContext.objects.all(),
         queryset=VirtualDeviceContext.objects.all(),
@@ -1178,30 +1229,36 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
         label=_('Virtual Device Context')
         label=_('Virtual Device Context')
     )
     )
     kind = forms.MultipleChoiceField(
     kind = forms.MultipleChoiceField(
+        label=_('Kind'),
         choices=InterfaceKindChoices,
         choices=InterfaceKindChoices,
         required=False
         required=False
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         required=False
         required=False
     )
     )
     speed = forms.IntegerField(
     speed = forms.IntegerField(
+        label=_('Speed'),
         required=False,
         required=False,
         widget=NumberWithOptions(
         widget=NumberWithOptions(
             options=InterfaceSpeedChoices
             options=InterfaceSpeedChoices
         )
         )
     )
     )
     duplex = forms.MultipleChoiceField(
     duplex = forms.MultipleChoiceField(
+        label=_('Duplex'),
         choices=InterfaceDuplexChoices,
         choices=InterfaceDuplexChoices,
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     mgmt_only = forms.NullBooleanField(
     mgmt_only = forms.NullBooleanField(
+        label=_('Mgmt only'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1209,50 +1266,50 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
     )
     )
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         required=False,
         required=False,
-        label='MAC address'
+        label=_('MAC address')
     )
     )
     wwn = forms.CharField(
     wwn = forms.CharField(
         required=False,
         required=False,
-        label='WWN'
+        label=_('WWN')
     )
     )
     poe_mode = forms.MultipleChoiceField(
     poe_mode = forms.MultipleChoiceField(
         choices=InterfacePoEModeChoices,
         choices=InterfacePoEModeChoices,
         required=False,
         required=False,
-        label='PoE mode'
+        label=_('PoE mode')
     )
     )
     poe_type = forms.MultipleChoiceField(
     poe_type = forms.MultipleChoiceField(
         choices=InterfacePoETypeChoices,
         choices=InterfacePoETypeChoices,
         required=False,
         required=False,
-        label='PoE type'
+        label=_('PoE type')
     )
     )
     rf_role = forms.MultipleChoiceField(
     rf_role = forms.MultipleChoiceField(
         choices=WirelessRoleChoices,
         choices=WirelessRoleChoices,
         required=False,
         required=False,
-        label='Wireless role'
+        label=_('Wireless role')
     )
     )
     rf_channel = forms.MultipleChoiceField(
     rf_channel = forms.MultipleChoiceField(
         choices=WirelessChannelChoices,
         choices=WirelessChannelChoices,
         required=False,
         required=False,
-        label='Wireless channel'
+        label=_('Wireless channel')
     )
     )
     rf_channel_frequency = forms.IntegerField(
     rf_channel_frequency = forms.IntegerField(
         required=False,
         required=False,
-        label='Channel frequency (MHz)'
+        label=_('Channel frequency (MHz)')
     )
     )
     rf_channel_width = forms.IntegerField(
     rf_channel_width = forms.IntegerField(
         required=False,
         required=False,
-        label='Channel width (MHz)'
+        label=_('Channel width (MHz)')
     )
     )
     tx_power = forms.IntegerField(
     tx_power = forms.IntegerField(
         required=False,
         required=False,
-        label='Transmit power (dBm)',
+        label=_('Transmit power (dBm)'),
         min_value=0,
         min_value=0,
         max_value=127
         max_value=127
     )
     )
     vrf_id = DynamicModelMultipleChoiceField(
     vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     l2vpn_id = DynamicModelMultipleChoiceField(
     l2vpn_id = DynamicModelMultipleChoiceField(
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
@@ -1265,17 +1322,19 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
 class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
 class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type', 'color')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Cable', ('cabled', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type', 'color')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Cable'), ('cabled', 'occupied')),
     )
     )
     model = FrontPort
     model = FrontPort
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         required=False
         required=False
     )
     )
     color = ColorField(
     color = ColorField(
+        label=_('Color'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -1285,16 +1344,18 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
     model = RearPort
     model = RearPort
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'type', 'color')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
-        ('Cable', ('cabled', 'occupied')),
+        (_('Attributes'), ('name', 'label', 'type', 'color')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Cable'), ('cabled', 'occupied')),
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
+        label=_('Type'),
         choices=PortTypeChoices,
         choices=PortTypeChoices,
         required=False
         required=False
     )
     )
     color = ColorField(
     color = ColorField(
+        label=_('Color'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -1304,12 +1365,13 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
     model = ModuleBay
     model = ModuleBay
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'position')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Attributes'), ('name', 'label', 'position')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     position = forms.CharField(
     position = forms.CharField(
+        label=_('Position'),
         required=False
         required=False
     )
     )
 
 
@@ -1318,9 +1380,9 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
     model = DeviceBay
     model = DeviceBay
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Attributes'), ('name', 'label')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -1329,9 +1391,9 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
     model = InventoryItem
     model = InventoryItem
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
-        ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
+        (_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
+        (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
     )
     )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=InventoryItemRole.objects.all(),
         queryset=InventoryItemRole.objects.all(),
@@ -1345,12 +1407,15 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
         label=_('Manufacturer')
         label=_('Manufacturer')
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
+        label=_('Serial'),
         required=False
         required=False
     )
     )
     asset_tag = forms.CharField(
     asset_tag = forms.CharField(
+        label=_('Asset tag'),
         required=False
         required=False
     )
     )
     discovered = forms.NullBooleanField(
     discovered = forms.NullBooleanField(
+        label=_('Discovered'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES

+ 4 - 1
netbox/dcim/forms/formsets.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 __all__ = (
 __all__ = (
     'BaseVCMemberFormSet',
     'BaseVCMemberFormSet',
@@ -16,6 +17,8 @@ class BaseVCMemberFormSet(forms.BaseModelFormSet):
             vc_position = form.cleaned_data.get('vc_position')
             vc_position = form.cleaned_data.get('vc_position')
             if vc_position:
             if vc_position:
                 if vc_position in vc_position_list:
                 if vc_position in vc_position_list:
-                    error_msg = f"A virtual chassis member already exists in position {vc_position}."
+                    error_msg = _("A virtual chassis member already exists in position {vc_position}.").format(
+                        vc_position=vc_position
+                    )
                     form.add_error('vc_position', error_msg)
                     form.add_error('vc_position', error_msg)
                 vc_position_list.append(vc_position)
                 vc_position_list.append(vc_position)

+ 101 - 43
netbox/dcim/forms/model_forms.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 from timezone_field import TimeZoneFormField
 from timezone_field import TimeZoneFormField
 
 
 from dcim.choices import *
 from dcim.choices import *
@@ -70,13 +70,14 @@ __all__ = (
 
 
 class RegionForm(NetBoxModelForm):
 class RegionForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Region', (
+        (_('Region'), (
             'parent', 'name', 'slug', 'description', 'tags',
             'parent', 'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -90,13 +91,14 @@ class RegionForm(NetBoxModelForm):
 
 
 class SiteGroupForm(NetBoxModelForm):
 class SiteGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Site Group', (
+        (_('Site Group'), (
             'parent', 'name', 'slug', 'description', 'tags',
             'parent', 'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -110,10 +112,12 @@ class SiteGroupForm(NetBoxModelForm):
 
 
 class SiteForm(TenancyForm, NetBoxModelForm):
 class SiteForm(TenancyForm, NetBoxModelForm):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False
         required=False
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False
         required=False
     )
     )
@@ -124,17 +128,18 @@ class SiteForm(TenancyForm, NetBoxModelForm):
     )
     )
     slug = SlugField()
     slug = SlugField()
     time_zone = TimeZoneFormField(
     time_zone = TimeZoneFormField(
+        label=_('Time zone'),
         choices=add_blank_choice(TimeZoneFormField().choices),
         choices=add_blank_choice(TimeZoneFormField().choices),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Site', (
+        (_('Site'), (
             'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
             'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
         )),
         )),
-        ('Tenancy', ('tenant_group', 'tenant')),
-        ('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
+        (_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -159,10 +164,12 @@ class SiteForm(TenancyForm, NetBoxModelForm):
 
 
 class LocationForm(TenancyForm, NetBoxModelForm):
 class LocationForm(TenancyForm, NetBoxModelForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         selector=True
         selector=True
     )
     )
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -172,8 +179,8 @@ class LocationForm(TenancyForm, NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Location', ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -187,7 +194,7 @@ class RackRoleForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Rack Role', (
+        (_('Rack Role'), (
             'name', 'slug', 'color', 'description', 'tags',
             'name', 'slug', 'color', 'description', 'tags',
         )),
         )),
     )
     )
@@ -201,10 +208,12 @@ class RackRoleForm(NetBoxModelForm):
 
 
 class RackForm(TenancyForm, NetBoxModelForm):
 class RackForm(TenancyForm, NetBoxModelForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         selector=True
         selector=True
     )
     )
     location = DynamicModelChoiceField(
     location = DynamicModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -212,6 +221,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
         }
         }
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=RackRole.objects.all(),
         queryset=RackRole.objects.all(),
         required=False
         required=False
     )
     )
@@ -228,14 +238,17 @@ class RackForm(TenancyForm, NetBoxModelForm):
 
 
 class RackReservationForm(TenancyForm, NetBoxModelForm):
 class RackReservationForm(TenancyForm, NetBoxModelForm):
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         selector=True
         selector=True
     )
     )
     units = NumericArrayField(
     units = NumericArrayField(
+        label=_('Units'),
         base_field=forms.IntegerField(),
         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(
     user = forms.ModelChoiceField(
+        label=_('User'),
         queryset=get_user_model().objects.order_by(
         queryset=get_user_model().objects.order_by(
             'username'
             'username'
         )
         )
@@ -243,8 +256,8 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Reservation', ('rack', 'units', 'user', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -258,7 +271,7 @@ class ManufacturerForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Manufacturer', (
+        (_('Manufacturer'), (
             'name', 'slug', 'description', 'tags',
             'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -272,23 +285,26 @@ class ManufacturerForm(NetBoxModelForm):
 
 
 class DeviceTypeForm(NetBoxModelForm):
 class DeviceTypeForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
     manufacturer = DynamicModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all()
         queryset=Manufacturer.objects.all()
     )
     )
     default_platform = DynamicModelChoiceField(
     default_platform = DynamicModelChoiceField(
+        label=_('Default platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField(
     slug = SlugField(
+        label=_('Slug'),
         slug_source='model'
         slug_source='model'
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Device Type', ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
-        ('Chassis', (
+        (_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
+        (_('Chassis'), (
             'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
             'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
         )),
         )),
-        ('Images', ('front_image', 'rear_image')),
+        (_('Images'), ('front_image', 'rear_image')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -310,13 +326,14 @@ class DeviceTypeForm(NetBoxModelForm):
 
 
 class ModuleTypeForm(NetBoxModelForm):
 class ModuleTypeForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
     manufacturer = DynamicModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all()
         queryset=Manufacturer.objects.all()
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Module Type', ('manufacturer', 'model', 'part_number', 'description', 'tags')),
-        ('Weight', ('weight', 'weight_unit'))
+        (_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')),
+        (_('Weight'), ('weight', 'weight_unit'))
     )
     )
 
 
     class Meta:
     class Meta:
@@ -328,13 +345,14 @@ class ModuleTypeForm(NetBoxModelForm):
 
 
 class DeviceRoleForm(NetBoxModelForm):
 class DeviceRoleForm(NetBoxModelForm):
     config_template = DynamicModelChoiceField(
     config_template = DynamicModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Device Role', (
+        (_('Device Role'), (
             'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
             'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
         )),
         )),
     )
     )
@@ -348,19 +366,22 @@ class DeviceRoleForm(NetBoxModelForm):
 
 
 class PlatformForm(NetBoxModelForm):
 class PlatformForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
     manufacturer = DynamicModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False
         required=False
     )
     )
     config_template = DynamicModelChoiceField(
     config_template = DynamicModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField(
     slug = SlugField(
+        label=_('Slug'),
         max_length=64
         max_length=64
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Platform', ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
+        (_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -372,10 +393,12 @@ class PlatformForm(NetBoxModelForm):
 
 
 class DeviceForm(TenancyForm, NetBoxModelForm):
 class DeviceForm(TenancyForm, NetBoxModelForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         selector=True
         selector=True
     )
     )
     location = DynamicModelChoiceField(
     location = DynamicModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -386,6 +409,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
         }
         }
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -394,6 +418,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
         }
         }
     )
     )
     position = forms.DecimalField(
     position = forms.DecimalField(
+        label=_('Position'),
         required=False,
         required=False,
         help_text=_("The lowest-numbered unit occupied by the device"),
         help_text=_("The lowest-numbered unit occupied by the device"),
         widget=APISelect(
         widget=APISelect(
@@ -405,17 +430,21 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
         )
         )
     )
     )
     device_type = DynamicModelChoiceField(
     device_type = DynamicModelChoiceField(
+        label=_('Device type'),
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
         selector=True
         selector=True
     )
     )
     device_role = DynamicModelChoiceField(
     device_role = DynamicModelChoiceField(
+        label=_('Device role'),
         queryset=DeviceRole.objects.all()
         queryset=DeviceRole.objects.all()
     )
     )
     platform = DynamicModelChoiceField(
     platform = DynamicModelChoiceField(
+        label=_('Platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False
         required=False
     )
     )
     cluster = DynamicModelChoiceField(
     cluster = DynamicModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
@@ -426,6 +455,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
         label=''
         label=''
     )
     )
     virtual_chassis = DynamicModelChoiceField(
     virtual_chassis = DynamicModelChoiceField(
+        label=_('Virtual chassis'),
         queryset=VirtualChassis.objects.all(),
         queryset=VirtualChassis.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
@@ -441,6 +471,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
         help_text=_("The priority of the device in the virtual chassis")
         help_text=_("The priority of the device in the virtual chassis")
     )
     )
     config_template = DynamicModelChoiceField(
     config_template = DynamicModelChoiceField(
+        label=_('Config template'),
         queryset=ConfigTemplate.objects.all(),
         queryset=ConfigTemplate.objects.all(),
         required=False
         required=False
     )
     )
@@ -518,36 +549,41 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
 
 
 class ModuleForm(ModuleCommonForm, NetBoxModelForm):
 class ModuleForm(ModuleCommonForm, NetBoxModelForm):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         initial_params={
         initial_params={
             'modulebays': '$module_bay'
             'modulebays': '$module_bay'
         }
         }
     )
     )
     module_bay = DynamicModelChoiceField(
     module_bay = DynamicModelChoiceField(
+        label=_('Module bay'),
         queryset=ModuleBay.objects.all(),
         queryset=ModuleBay.objects.all(),
         query_params={
         query_params={
             'device_id': '$device'
             'device_id': '$device'
         }
         }
     )
     )
     module_type = DynamicModelChoiceField(
     module_type = DynamicModelChoiceField(
+        label=_('Module type'),
         queryset=ModuleType.objects.all(),
         queryset=ModuleType.objects.all(),
         selector=True
         selector=True
     )
     )
     comments = CommentField()
     comments = CommentField()
     replicate_components = forms.BooleanField(
     replicate_components = forms.BooleanField(
+        label=_('Replicate components'),
         required=False,
         required=False,
         initial=True,
         initial=True,
         help_text=_("Automatically populate components associated with this module type")
         help_text=_("Automatically populate components associated with this module type")
     )
     )
     adopt_components = forms.BooleanField(
     adopt_components = forms.BooleanField(
+        label=_('Adopt components'),
         required=False,
         required=False,
         initial=False,
         initial=False,
         help_text=_("Adopt already existing components")
         help_text=_("Adopt already existing components")
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Module', ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
-        ('Hardware', (
+        (_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
+        (_('Hardware'), (
             'serial', 'asset_tag', 'replicate_components', 'adopt_components',
             'serial', 'asset_tag', 'replicate_components', 'adopt_components',
         )),
         )),
     )
     )
@@ -581,17 +617,19 @@ class CableForm(TenancyForm, NetBoxModelForm):
         ]
         ]
         error_messages = {
         error_messages = {
             'length': {
             'length': {
-                'max_value': 'Maximum length is 32767 (any unit)'
+                'max_value': _('Maximum length is 32767 (any unit)')
             }
             }
         }
         }
 
 
 
 
 class PowerPanelForm(NetBoxModelForm):
 class PowerPanelForm(NetBoxModelForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         selector=True
         selector=True
     )
     )
     location = DynamicModelChoiceField(
     location = DynamicModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -613,10 +651,12 @@ class PowerPanelForm(NetBoxModelForm):
 
 
 class PowerFeedForm(TenancyForm, NetBoxModelForm):
 class PowerFeedForm(TenancyForm, NetBoxModelForm):
     power_panel = DynamicModelChoiceField(
     power_panel = DynamicModelChoiceField(
+        label=_('Power panel'),
         queryset=PowerPanel.objects.all(),
         queryset=PowerPanel.objects.all(),
         selector=True
         selector=True
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
@@ -624,9 +664,9 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Power Feed', ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
-        ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
+        (_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -643,6 +683,7 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
 
 
 class VirtualChassisForm(NetBoxModelForm):
 class VirtualChassisForm(NetBoxModelForm):
     master = forms.ModelChoiceField(
     master = forms.ModelChoiceField(
+        label=_('Master'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
     )
     )
@@ -706,6 +747,7 @@ class DeviceVCMembershipForm(forms.ModelForm):
 
 
 class VCMemberSelectForm(BootstrapMixin, forms.Form):
 class VCMemberSelectForm(BootstrapMixin, forms.Form):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         query_params={
         query_params={
             'virtual_chassis_id': 'null',
             'virtual_chassis_id': 'null',
@@ -728,6 +770,7 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
 
 
 class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
 class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
     device_type = DynamicModelChoiceField(
     device_type = DynamicModelChoiceField(
+        label=_('Device type'),
         queryset=DeviceType.objects.all()
         queryset=DeviceType.objects.all()
     )
     )
 
 
@@ -741,10 +784,12 @@ class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
 
 
 class ModularComponentTemplateForm(ComponentTemplateForm):
 class ModularComponentTemplateForm(ComponentTemplateForm):
     device_type = DynamicModelChoiceField(
     device_type = DynamicModelChoiceField(
+        label=_('Device type'),
         queryset=DeviceType.objects.all().all(),
         queryset=DeviceType.objects.all().all(),
         required=False
         required=False
     )
     )
     module_type = DynamicModelChoiceField(
     module_type = DynamicModelChoiceField(
+        label=_('Module type'),
         queryset=ModuleType.objects.all(),
         queryset=ModuleType.objects.all(),
         required=False
         required=False
     )
     )
@@ -797,6 +842,7 @@ class PowerPortTemplateForm(ModularComponentTemplateForm):
 
 
 class PowerOutletTemplateForm(ModularComponentTemplateForm):
 class PowerOutletTemplateForm(ModularComponentTemplateForm):
     power_port = DynamicModelChoiceField(
     power_port = DynamicModelChoiceField(
+        label=_('Power port'),
         queryset=PowerPortTemplate.objects.all(),
         queryset=PowerPortTemplate.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -817,6 +863,7 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
 
 
 class InterfaceTemplateForm(ModularComponentTemplateForm):
 class InterfaceTemplateForm(ModularComponentTemplateForm):
     bridge = DynamicModelChoiceField(
     bridge = DynamicModelChoiceField(
+        label=_('Bridge'),
         queryset=InterfaceTemplate.objects.all(),
         queryset=InterfaceTemplate.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -827,8 +874,8 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
 
 
     fieldsets = (
     fieldsets = (
         (None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
         (None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
-        ('PoE', ('poe_mode', 'poe_type')),
-        ('Wireless', ('rf_role',))
+        (_('PoE'), ('poe_mode', 'poe_type')),
+        (_('Wireless'), ('rf_role',)),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -840,6 +887,7 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
 
 
 class FrontPortTemplateForm(ModularComponentTemplateForm):
 class FrontPortTemplateForm(ModularComponentTemplateForm):
     rear_port = DynamicModelChoiceField(
     rear_port = DynamicModelChoiceField(
+        label=_('Rear port'),
         queryset=RearPortTemplate.objects.all(),
         queryset=RearPortTemplate.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -901,6 +949,7 @@ class DeviceBayTemplateForm(ComponentTemplateForm):
 
 
 class InventoryItemTemplateForm(ComponentTemplateForm):
 class InventoryItemTemplateForm(ComponentTemplateForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=InventoryItemTemplate.objects.all(),
         queryset=InventoryItemTemplate.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -908,10 +957,12 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
         }
         }
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=InventoryItemRole.objects.all(),
         queryset=InventoryItemRole.objects.all(),
         required=False
         required=False
     )
     )
     manufacturer = DynamicModelChoiceField(
     manufacturer = DynamicModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False
         required=False
     )
     )
@@ -947,6 +998,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
 
 
 class DeviceComponentForm(NetBoxModelForm):
 class DeviceComponentForm(NetBoxModelForm):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         selector=True
         selector=True
     )
     )
@@ -961,6 +1013,7 @@ class DeviceComponentForm(NetBoxModelForm):
 
 
 class ModularDeviceComponentForm(DeviceComponentForm):
 class ModularDeviceComponentForm(DeviceComponentForm):
     module = DynamicModelChoiceField(
     module = DynamicModelChoiceField(
+        label=_('Module'),
         queryset=Module.objects.all(),
         queryset=Module.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -1017,6 +1070,7 @@ class PowerPortForm(ModularDeviceComponentForm):
 
 
 class PowerOutletForm(ModularDeviceComponentForm):
 class PowerOutletForm(ModularDeviceComponentForm):
     power_port = DynamicModelChoiceField(
     power_port = DynamicModelChoiceField(
+        label=_('Power port'),
         queryset=PowerPort.objects.all(),
         queryset=PowerPort.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -1043,7 +1097,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     vdcs = DynamicModelMultipleChoiceField(
     vdcs = DynamicModelMultipleChoiceField(
         queryset=VirtualDeviceContext.objects.all(),
         queryset=VirtualDeviceContext.objects.all(),
         required=False,
         required=False,
-        label='Virtual Device Contexts',
+        label=_('Virtual device contexts'),
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
         }
         }
@@ -1121,13 +1175,13 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Interface', ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
-        ('Addressing', ('vrf', 'mac_address', 'wwn')),
-        ('Operation', ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
-        ('Related Interfaces', ('parent', 'bridge', 'lag')),
-        ('PoE', ('poe_mode', 'poe_type')),
-        ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
-        ('Wireless', (
+        (_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
+        (_('Addressing'), ('vrf', 'mac_address', 'wwn')),
+        (_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
+        (_('Related Interfaces'), ('parent', 'bridge', 'lag')),
+        (_('PoE'), ('poe_mode', 'poe_type')),
+        (_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
+        (_('Wireless'), (
             'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
             'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
         )),
         )),
     )
     )
@@ -1233,6 +1287,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
 
 
 class InventoryItemForm(DeviceComponentForm):
 class InventoryItemForm(DeviceComponentForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=InventoryItem.objects.all(),
         queryset=InventoryItem.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -1240,10 +1295,12 @@ class InventoryItemForm(DeviceComponentForm):
         }
         }
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=InventoryItemRole.objects.all(),
         queryset=InventoryItemRole.objects.all(),
         required=False
         required=False
     )
     )
     manufacturer = DynamicModelChoiceField(
     manufacturer = DynamicModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False
         required=False
     )
     )
@@ -1307,8 +1364,8 @@ class InventoryItemForm(DeviceComponentForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
-        ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
+        (_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
+        (_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -1359,7 +1416,7 @@ class InventoryItemForm(DeviceComponentForm):
             ) if self.cleaned_data[field]
             ) if self.cleaned_data[field]
         ]
         ]
         if len(selected_objects) > 1:
         if len(selected_objects) > 1:
-            raise forms.ValidationError("An InventoryItem can only be assigned to a single component.")
+            raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
         elif selected_objects:
         elif selected_objects:
             self.instance.component = self.cleaned_data[selected_objects[0]]
             self.instance.component = self.cleaned_data[selected_objects[0]]
         else:
         else:
@@ -1373,7 +1430,7 @@ class InventoryItemRoleForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Inventory Item Role', (
+        (_('Inventory Item Role'), (
             'name', 'slug', 'color', 'description', 'tags',
             'name', 'slug', 'color', 'description', 'tags',
         )),
         )),
     )
     )
@@ -1387,12 +1444,13 @@ class InventoryItemRoleForm(NetBoxModelForm):
 
 
 class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
 class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         selector=True
         selector=True
     )
     )
     primary_ip4 = DynamicModelChoiceField(
     primary_ip4 = DynamicModelChoiceField(
         queryset=IPAddress.objects.all(),
         queryset=IPAddress.objects.all(),
-        label='Primary IPv4',
+        label=_('Primary IPv4'),
         required=False,
         required=False,
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
@@ -1401,7 +1459,7 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
     )
     )
     primary_ip6 = DynamicModelChoiceField(
     primary_ip6 = DynamicModelChoiceField(
         queryset=IPAddress.objects.all(),
         queryset=IPAddress.objects.all(),
-        label='Primary IPv6',
+        label=_('Primary IPv6'),
         required=False,
         required=False,
         query_params={
         query_params={
             'device_id': '$device',
             'device_id': '$device',
@@ -1410,8 +1468,8 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Virtual Device Context', ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant'))
+        (_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant'))
     )
     )
 
 
     class Meta:
     class Meta:

+ 19 - 7
netbox/dcim/forms/object_create.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import *
 from dcim.models import *
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
@@ -38,8 +38,11 @@ class ComponentCreateForm(forms.Form):
     Subclass this form when facilitating the creation of one or more component or component template objects based on
     Subclass this form when facilitating the creation of one or more component or component template objects based on
     a name pattern.
     a name pattern.
     """
     """
-    name = ExpandableNameField()
+    name = ExpandableNameField(
+        label=_('Name'),
+    )
     label = ExpandableNameField(
     label = ExpandableNameField(
+        label=_('Label'),
         required=False,
         required=False,
         help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
         help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
     )
     )
@@ -57,8 +60,9 @@ class ComponentCreateForm(forms.Form):
             value_count = len(self.cleaned_data[field_name])
             value_count = len(self.cleaned_data[field_name])
             if self.cleaned_data[field_name] and value_count != pattern_count:
             if self.cleaned_data[field_name] and value_count != pattern_count:
                 raise forms.ValidationError({
                 raise forms.ValidationError({
-                    field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
-                                f'expected.'
+                    field_name: _(
+                        "The provided pattern specifies {value_count} values, but {pattern_count} are expected."
+                    ).format(value_count=value_count, pattern_count=pattern_count)
                 }, code='label_pattern_mismatch')
                 }, code='label_pattern_mismatch')
 
 
 
 
@@ -222,12 +226,14 @@ class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
 
 
         if 'module' in self.fields:
         if 'module' in self.fields:
-            self.fields['name'].help_text += ' The string <code>{module}</code> will be replaced with the position ' \
-                                             'of the assigned module, if any'
+            self.fields['name'].help_text += _(
+                "The string <code>{module}</code> will be replaced with the position of the assigned module, if any."
+            )
 
 
 
 
 class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
 class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         selector=True,
         selector=True,
         widget=APISelect(
         widget=APISelect(
@@ -329,6 +335,7 @@ class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm
 
 
 class VirtualChassisCreateForm(NetBoxModelForm):
 class VirtualChassisCreateForm(NetBoxModelForm):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -336,6 +343,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
         }
         }
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
+        label=_('Site group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -343,6 +351,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
         }
         }
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -351,6 +360,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
         }
         }
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
@@ -359,6 +369,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
         }
         }
     )
     )
     members = DynamicModelMultipleChoiceField(
     members = DynamicModelMultipleChoiceField(
+        label=_('Members'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -367,6 +378,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
         }
         }
     )
     )
     initial_position = forms.IntegerField(
     initial_position = forms.IntegerField(
+        label=_('Initial position'),
         initial=1,
         initial=1,
         required=False,
         required=False,
         help_text=_('Position of the first member device. Increases by one for each additional member.')
         help_text=_('Position of the first member device. Increases by one for each additional member.')
@@ -383,7 +395,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
 
 
         if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
         if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
             raise forms.ValidationError({
             raise forms.ValidationError({
-                'initial_position': "A position must be specified for the first VC member."
+                'initial_position': _("A position must be specified for the first VC member.")
             })
             })
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):

+ 9 - 1
netbox/dcim/forms/object_import.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.models import *
 from dcim.models import *
@@ -57,6 +57,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
 
 
 class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
 class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
     power_port = forms.ModelChoiceField(
     power_port = forms.ModelChoiceField(
+        label=_('Power port'),
         queryset=PowerPortTemplate.objects.all(),
         queryset=PowerPortTemplate.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False
@@ -85,6 +86,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
 
 
 class InterfaceTemplateImportForm(ComponentTemplateImportForm):
 class InterfaceTemplateImportForm(ComponentTemplateImportForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=InterfaceTypeChoices.CHOICES
         choices=InterfaceTypeChoices.CHOICES
     )
     )
     poe_mode = forms.ChoiceField(
     poe_mode = forms.ChoiceField(
@@ -113,9 +115,11 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
 
 
 class FrontPortTemplateImportForm(ComponentTemplateImportForm):
 class FrontPortTemplateImportForm(ComponentTemplateImportForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=PortTypeChoices.CHOICES
         choices=PortTypeChoices.CHOICES
     )
     )
     rear_port = forms.ModelChoiceField(
     rear_port = forms.ModelChoiceField(
+        label=_('Rear port'),
         queryset=RearPortTemplate.objects.all(),
         queryset=RearPortTemplate.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
@@ -143,6 +147,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
 
 
 class RearPortTemplateImportForm(ComponentTemplateImportForm):
 class RearPortTemplateImportForm(ComponentTemplateImportForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=PortTypeChoices.CHOICES
         choices=PortTypeChoices.CHOICES
     )
     )
 
 
@@ -173,15 +178,18 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
 
 
 class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
 class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
     parent = forms.ModelChoiceField(
     parent = forms.ModelChoiceField(
+        label=_('Parent'),
         queryset=InventoryItemTemplate.objects.all(),
         queryset=InventoryItemTemplate.objects.all(),
         required=False
         required=False
     )
     )
     role = forms.ModelChoiceField(
     role = forms.ModelChoiceField(
+        label=_('Role'),
         queryset=InventoryItemRole.objects.all(),
         queryset=InventoryItemRole.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False
     )
     )
     manufacturer = forms.ModelChoiceField(
     manufacturer = forms.ModelChoiceField(
+        label=_('Manufacturer'),
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False
         required=False

+ 33 - 1
netbox/extras/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
@@ -27,16 +27,20 @@ class CustomFieldBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     group_name = forms.CharField(
     group_name = forms.CharField(
+        label=_('Group name'),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         required=False
         required=False
     )
     )
     required = forms.NullBooleanField(
     required = forms.NullBooleanField(
+        label=_('Required'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     choice_set = DynamicModelChoiceField(
     choice_set = DynamicModelChoiceField(
@@ -50,6 +54,7 @@ class CustomFieldBulkEditForm(BulkEditForm):
         initial=''
         initial=''
     )
     )
     is_cloneable = forms.NullBooleanField(
     is_cloneable = forms.NullBooleanField(
+        label=_('Is cloneable'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
@@ -83,17 +88,21 @@ class CustomLinkBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     new_window = forms.NullBooleanField(
     new_window = forms.NullBooleanField(
+        label=_('New window'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     button_class = forms.ChoiceField(
     button_class = forms.ChoiceField(
+        label=_('Button class'),
         choices=add_blank_choice(CustomLinkButtonClassChoices),
         choices=add_blank_choice(CustomLinkButtonClassChoices),
         required=False
         required=False
     )
     )
@@ -105,18 +114,22 @@ class ExportTemplateBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
     mime_type = forms.CharField(
     mime_type = forms.CharField(
+        label=_('MIME type'),
         max_length=50,
         max_length=50,
         required=False
         required=False
     )
     )
     file_extension = forms.CharField(
     file_extension = forms.CharField(
+        label=_('File extension'),
         max_length=15,
         max_length=15,
         required=False
         required=False
     )
     )
     as_attachment = forms.NullBooleanField(
     as_attachment = forms.NullBooleanField(
+        label=_('As attachment'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
@@ -130,17 +143,21 @@ class SavedFilterBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     shared = forms.NullBooleanField(
     shared = forms.NullBooleanField(
+        label=_('Shared'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
@@ -154,26 +171,32 @@ class WebhookBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     type_create = forms.NullBooleanField(
     type_create = forms.NullBooleanField(
+        label=_('On create'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     type_update = forms.NullBooleanField(
     type_update = forms.NullBooleanField(
+        label=_('On update'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     type_delete = forms.NullBooleanField(
     type_delete = forms.NullBooleanField(
+        label=_('On delete'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     type_job_start = forms.NullBooleanField(
     type_job_start = forms.NullBooleanField(
+        label=_('On job start'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     type_job_end = forms.NullBooleanField(
     type_job_end = forms.NullBooleanField(
+        label=_('On job end'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
@@ -192,6 +215,7 @@ class WebhookBulkEditForm(BulkEditForm):
         label=_('SSL verification')
         label=_('SSL verification')
     )
     )
     secret = forms.CharField(
     secret = forms.CharField(
+        label=_('Secret'),
         required=False
         required=False
     )
     )
     ca_file_path = forms.CharField(
     ca_file_path = forms.CharField(
@@ -208,9 +232,11 @@ class TagBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     color = ColorField(
     color = ColorField(
+        label=_('Color'),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -224,14 +250,17 @@ class ConfigContextBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False,
         required=False,
         min_value=0
         min_value=0
     )
     )
     is_active = forms.NullBooleanField(
     is_active = forms.NullBooleanField(
+        label=_('Is active'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         required=False,
         required=False,
         max_length=100
         max_length=100
     )
     )
@@ -245,6 +274,7 @@ class ConfigTemplateBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -258,10 +288,12 @@ class JournalEntryBulkEditForm(BulkEditForm):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     kind = forms.ChoiceField(
     kind = forms.ChoiceField(
+        label=_('Kind'),
         choices=add_blank_choice(JournalEntryKindChoices),
         choices=add_blank_choice(JournalEntryKindChoices),
         required=False
         required=False
     )
     )
     comments = forms.CharField(
     comments = forms.CharField(
+        label=_('Comments'),
         required=False,
         required=False,
         widget=forms.Textarea()
         widget=forms.Textarea()
     )
     )

+ 11 - 1
netbox/extras/forms/bulk_import.py

@@ -2,7 +2,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
@@ -28,27 +28,32 @@ __all__ = (
 
 
 class CustomFieldImportForm(CSVModelForm):
 class CustomFieldImportForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=CustomFieldTypeChoices,
         choices=CustomFieldTypeChoices,
         help_text=_('Field data type (e.g. text, integer, etc.)')
         help_text=_('Field data type (e.g. text, integer, etc.)')
     )
     )
     object_type = CSVContentTypeField(
     object_type = CSVContentTypeField(
+        label=_('Object type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
         required=False,
         required=False,
         help_text=_("Object type (for object or multi-object fields)")
         help_text=_("Object type (for object or multi-object fields)")
     )
     )
     choice_set = CSVModelChoiceField(
     choice_set = CSVModelChoiceField(
+        label=_('Choice set'),
         queryset=CustomFieldChoiceSet.objects.all(),
         queryset=CustomFieldChoiceSet.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Choice set (for selection fields)')
         help_text=_('Choice set (for selection fields)')
     )
     )
     ui_visibility = CSVChoiceField(
     ui_visibility = CSVChoiceField(
+        label=_('UI visibility'),
         choices=CustomFieldVisibilityChoices,
         choices=CustomFieldVisibilityChoices,
         help_text=_('How the custom field is displayed in the user interface')
         help_text=_('How the custom field is displayed in the user interface')
     )
     )
@@ -83,6 +88,7 @@ class CustomFieldChoiceSetImportForm(CSVModelForm):
 
 
 class CustomLinkImportForm(CSVModelForm):
 class CustomLinkImportForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_links'),
         limit_choices_to=FeatureQuery('custom_links'),
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
@@ -98,6 +104,7 @@ class CustomLinkImportForm(CSVModelForm):
 
 
 class ExportTemplateImportForm(CSVModelForm):
 class ExportTemplateImportForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('export_templates'),
         limit_choices_to=FeatureQuery('export_templates'),
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
@@ -121,6 +128,7 @@ class ConfigTemplateImportForm(CSVModelForm):
 
 
 class SavedFilterImportForm(CSVModelForm):
 class SavedFilterImportForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
     )
     )
@@ -134,6 +142,7 @@ class SavedFilterImportForm(CSVModelForm):
 
 
 class WebhookImportForm(CSVModelForm):
 class WebhookImportForm(CSVModelForm):
     content_types = CSVMultipleContentTypeField(
     content_types = CSVMultipleContentTypeField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('webhooks'),
         limit_choices_to=FeatureQuery('webhooks'),
         help_text=_("One or more assigned object types")
         help_text=_("One or more assigned object types")
@@ -165,6 +174,7 @@ class JournalEntryImportForm(NetBoxModelImportForm):
         label=_('Assigned object type'),
         label=_('Assigned object type'),
     )
     )
     kind = CSVChoiceField(
     kind = CSVChoiceField(
+        label=_('Kind'),
         choices=JournalEntryKindChoices,
         choices=JournalEntryKindChoices,
         help_text=_('The classification of entry')
         help_text=_('The classification of entry')
     )
     )

+ 38 - 18
netbox/extras/forms/filtersets.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from core.models import DataFile, DataSource
 from core.models import DataFile, DataSource
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
@@ -39,7 +39,7 @@ __all__ = (
 class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
 class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Attributes', (
+        (_('Attributes'), (
             'type', 'content_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visibility',
             'type', 'content_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visibility',
             'is_cloneable',
             'is_cloneable',
         )),
         )),
@@ -55,12 +55,15 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
         label=_('Field type')
         label=_('Field type')
     )
     )
     group_name = forms.CharField(
     group_name = forms.CharField(
+        label=_('Group name'),
         required=False
         required=False
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     required = forms.NullBooleanField(
     required = forms.NullBooleanField(
+        label=_('Required'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -77,6 +80,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
         label=_('UI visibility')
         label=_('UI visibility')
     )
     )
     is_cloneable = forms.NullBooleanField(
     is_cloneable = forms.NullBooleanField(
+        label=_('Is cloneable'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -104,22 +108,26 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
         (_('Attributes'), ('content_types', 'enabled', 'new_window', 'weight')),
         (_('Attributes'), ('content_types', 'enabled', 'new_window', 'weight')),
     )
     )
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query()),
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     new_window = forms.NullBooleanField(
     new_window = forms.NullBooleanField(
+        label=_('New window'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
 
 
@@ -127,8 +135,8 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
 class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
 class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Data', ('data_source_id', 'data_file_id')),
-        ('Attributes', ('content_types', 'mime_type', 'file_extension', 'as_attachment')),
+        (_('Data'), ('data_source_id', 'data_file_id')),
+        (_('Attributes'), ('content_types', 'mime_type', 'file_extension', 'as_attachment')),
     )
     )
     data_source_id = DynamicModelMultipleChoiceField(
     data_source_id = DynamicModelMultipleChoiceField(
         queryset=DataSource.objects.all(),
         queryset=DataSource.objects.all(),
@@ -144,6 +152,7 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
         }
         }
     )
     )
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
         required=False
         required=False
     )
     )
@@ -152,9 +161,11 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
         label=_('MIME type')
         label=_('MIME type')
     )
     )
     file_extension = forms.CharField(
     file_extension = forms.CharField(
+        label=_('File extension'),
         required=False
         required=False
     )
     )
     as_attachment = forms.NullBooleanField(
     as_attachment = forms.NullBooleanField(
+        label=_('As attachment'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -165,13 +176,15 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
 class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
 class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Attributes', ('content_type_id', 'name',)),
+        (_('Attributes'), ('content_type_id', 'name',)),
     )
     )
     content_type_id = ContentTypeChoiceField(
     content_type_id = ContentTypeChoiceField(
+        label=_('Content type'),
         queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
         required=False
         required=False
     )
     )
     name = forms.CharField(
     name = forms.CharField(
+        label=_('Name'),
         required=False
         required=False
     )
     )
 
 
@@ -179,25 +192,29 @@ class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
 class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
 class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Attributes', ('content_types', 'enabled', 'shared', 'weight')),
+        (_('Attributes'), ('content_types', 'enabled', 'shared', 'weight')),
     )
     )
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     shared = forms.NullBooleanField(
     shared = forms.NullBooleanField(
+        label=_('Shared'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
         )
         )
     )
     )
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
 
 
@@ -205,8 +222,8 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
 class WebhookFilterForm(SavedFiltersMixin, FilterForm):
 class WebhookFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Attributes', ('content_type_id', 'http_method', 'enabled')),
-        ('Events', ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
+        (_('Attributes'), ('content_type_id', 'http_method', 'enabled')),
+        (_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
     )
     )
     content_type_id = ContentTypeMultipleChoiceField(
     content_type_id = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
@@ -219,6 +236,7 @@ class WebhookFilterForm(SavedFiltersMixin, FilterForm):
         label=_('HTTP method')
         label=_('HTTP method')
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -278,11 +296,11 @@ class TagFilterForm(SavedFiltersMixin, FilterForm):
 class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
 class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag_id')),
         (None, ('q', 'filter_id', 'tag_id')),
-        ('Data', ('data_source_id', 'data_file_id')),
-        ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
-        ('Device', ('device_type_id', 'platform_id', 'role_id')),
-        ('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id'))
+        (_('Data'), ('data_source_id', 'data_file_id')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
+        (_('Device'), ('device_type_id', 'platform_id', 'role_id')),
+        (_('Cluster'), ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id'))
     )
     )
     data_source_id = DynamicModelMultipleChoiceField(
     data_source_id = DynamicModelMultipleChoiceField(
         queryset=DataSource.objects.all(),
         queryset=DataSource.objects.all(),
@@ -368,7 +386,7 @@ class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
 class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
 class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Data', ('data_source_id', 'data_file_id')),
+        (_('Data'), ('data_source_id', 'data_file_id')),
     )
     )
     data_source_id = DynamicModelMultipleChoiceField(
     data_source_id = DynamicModelMultipleChoiceField(
         queryset=DataSource.objects.all(),
         queryset=DataSource.objects.all(),
@@ -400,8 +418,8 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
     model = JournalEntry
     model = JournalEntry
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Creation', ('created_before', 'created_after', 'created_by_id')),
-        ('Attributes', ('assigned_object_type_id', 'kind'))
+        (_('Creation'), ('created_before', 'created_after', 'created_by_id')),
+        (_('Attributes'), ('assigned_object_type_id', 'kind'))
     )
     )
     created_after = forms.DateTimeField(
     created_after = forms.DateTimeField(
         required=False,
         required=False,
@@ -430,6 +448,7 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
         )
         )
     )
     )
     kind = forms.ChoiceField(
     kind = forms.ChoiceField(
+        label=_('Kind'),
         choices=add_blank_choice(JournalEntryKindChoices),
         choices=add_blank_choice(JournalEntryKindChoices),
         required=False
         required=False
     )
     )
@@ -440,8 +459,8 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
     model = ObjectChange
     model = ObjectChange
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Time', ('time_before', 'time_after')),
-        ('Attributes', ('action', 'user_id', 'changed_object_type_id')),
+        (_('Time'), ('time_before', 'time_after')),
+        (_('Attributes'), ('action', 'user_id', 'changed_object_type_id')),
     )
     )
     time_after = forms.DateTimeField(
     time_after = forms.DateTimeField(
         required=False,
         required=False,
@@ -454,6 +473,7 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
         widget=DateTimePicker()
         widget=DateTimePicker()
     )
     )
     action = forms.ChoiceField(
     action = forms.ChoiceField(
+        label=_('Action'),
         choices=add_blank_choice(ObjectChangeActionChoices),
         choices=add_blank_choice(ObjectChangeActionChoices),
         required=False
         required=False
     )
     )

+ 2 - 0
netbox/extras/forms/misc.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 __all__ = (
 __all__ = (
     'RenderMarkdownForm',
     'RenderMarkdownForm',
@@ -10,5 +11,6 @@ class RenderMarkdownForm(forms.Form):
     Provides basic validation for markup to be rendered.
     Provides basic validation for markup to be rendered.
     """
     """
     text = forms.CharField(
     text = forms.CharField(
+        label=_('Text'),
         required=False
         required=False
     )
     )

+ 70 - 44
netbox/extras/forms/model_forms.py

@@ -4,7 +4,7 @@ from django import forms
 from django.conf import settings
 from django.conf import settings
 from django.db.models import Q
 from django.db.models import Q
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from core.forms.mixins import SyncedDataMixin
 from core.forms.mixins import SyncedDataMixin
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
@@ -42,10 +42,12 @@ __all__ = (
 
 
 class CustomFieldForm(BootstrapMixin, forms.ModelForm):
 class CustomFieldForm(BootstrapMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
     )
     )
     object_type = ContentTypeChoiceField(
     object_type = ContentTypeChoiceField(
+        label=_('Object type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         # TODO: Come up with a canonical way to register suitable models
         # TODO: Come up with a canonical way to register suitable models
         limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
         limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
@@ -58,12 +60,12 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Custom Field', (
+        (_('Custom Field'), (
             'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
             'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
         )),
         )),
-        ('Behavior', ('search_weight', 'filter_logic', 'ui_visibility', 'weight', 'is_cloneable')),
-        ('Values', ('default', 'choice_set')),
-        ('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
+        (_('Behavior'), ('search_weight', 'filter_logic', 'ui_visibility', 'weight', 'is_cloneable')),
+        (_('Values'), ('default', 'choice_set')),
+        (_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -106,13 +108,14 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
 
 
 class CustomLinkForm(BootstrapMixin, forms.ModelForm):
 class CustomLinkForm(BootstrapMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('custom_links')
         limit_choices_to=FeatureQuery('custom_links')
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Custom Link', ('name', 'content_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
-        ('Templates', ('link_text', 'link_url')),
+        (_('Custom Link'), ('name', 'content_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
+        (_('Templates'), ('link_text', 'link_url')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -133,18 +136,20 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
 
 
 class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
 class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('export_templates')
         limit_choices_to=FeatureQuery('export_templates')
     )
     )
     template_code = forms.CharField(
     template_code = forms.CharField(
+        label=_('Template code'),
         required=False,
         required=False,
         widget=forms.Textarea(attrs={'class': 'font-monospace'})
         widget=forms.Textarea(attrs={'class': 'font-monospace'})
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Export Template', ('name', 'content_types', 'description', 'template_code')),
-        ('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
-        ('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
+        (_('Export Template'), ('name', 'content_types', 'description', 'template_code')),
+        (_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
+        (_('Rendering'), ('mime_type', 'file_extension', 'as_attachment')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -165,7 +170,7 @@ class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         super().clean()
         super().clean()
 
 
         if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
         if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
-            raise forms.ValidationError("Must specify either local content or a data file")
+            raise forms.ValidationError(_("Must specify either local content or a data file"))
 
 
         return self.cleaned_data
         return self.cleaned_data
 
 
@@ -173,13 +178,14 @@ class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
 class SavedFilterForm(BootstrapMixin, forms.ModelForm):
 class SavedFilterForm(BootstrapMixin, forms.ModelForm):
     slug = SlugField()
     slug = SlugField()
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.all()
         queryset=ContentType.objects.all()
     )
     )
     parameters = JSONField()
     parameters = JSONField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Saved Filter', ('name', 'slug', 'content_types', 'description', 'weight', 'enabled', 'shared')),
-        ('Parameters', ('parameters',)),
+        (_('Saved Filter'), ('name', 'slug', 'content_types', 'description', 'weight', 'enabled', 'shared')),
+        (_('Parameters'), ('parameters',)),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -198,6 +204,7 @@ class SavedFilterForm(BootstrapMixin, forms.ModelForm):
 
 
 class BookmarkForm(BootstrapMixin, forms.ModelForm):
 class BookmarkForm(BootstrapMixin, forms.ModelForm):
     object_type = ContentTypeChoiceField(
     object_type = ContentTypeChoiceField(
+        label=_('Object type'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('bookmarks').get_query()
         limit_choices_to=FeatureQuery('bookmarks').get_query()
     )
     )
@@ -209,29 +216,30 @@ class BookmarkForm(BootstrapMixin, forms.ModelForm):
 
 
 class WebhookForm(BootstrapMixin, forms.ModelForm):
 class WebhookForm(BootstrapMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
+        label=_('Content types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('webhooks')
         limit_choices_to=FeatureQuery('webhooks')
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Webhook', ('name', 'content_types', 'enabled')),
-        ('Events', ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
-        ('HTTP Request', (
+        (_('Webhook'), ('name', 'content_types', 'enabled')),
+        (_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
+        (_('HTTP Request'), (
             'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
             'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
         )),
         )),
-        ('Conditions', ('conditions',)),
-        ('SSL', ('ssl_verification', 'ca_file_path')),
+        (_('Conditions'), ('conditions',)),
+        (_('SSL'), ('ssl_verification', 'ca_file_path')),
     )
     )
 
 
     class Meta:
     class Meta:
         model = Webhook
         model = Webhook
         fields = '__all__'
         fields = '__all__'
         labels = {
         labels = {
-            'type_create': 'Creations',
-            'type_update': 'Updates',
-            'type_delete': 'Deletions',
-            'type_job_start': 'Job executions',
-            'type_job_end': 'Job terminations',
+            'type_create': _('Creations'),
+            'type_update': _('Updates'),
+            'type_delete': _('Deletions'),
+            'type_job_start': _('Job executions'),
+            'type_job_end': _('Job terminations'),
         }
         }
         widgets = {
         widgets = {
             'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
             'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
@@ -243,6 +251,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
 class TagForm(BootstrapMixin, forms.ModelForm):
 class TagForm(BootstrapMixin, forms.ModelForm):
     slug = SlugField()
     slug = SlugField()
     object_types = ContentTypeMultipleChoiceField(
     object_types = ContentTypeMultipleChoiceField(
+        label=_('Object types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
         limit_choices_to=FeatureQuery('tags'),
         limit_choices_to=FeatureQuery('tags'),
         required=False
         required=False
@@ -261,65 +270,79 @@ class TagForm(BootstrapMixin, forms.ModelForm):
 
 
 class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
 class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
     regions = DynamicModelMultipleChoiceField(
     regions = DynamicModelMultipleChoiceField(
+        label=_('Regions'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False
         required=False
     )
     )
     site_groups = DynamicModelMultipleChoiceField(
     site_groups = DynamicModelMultipleChoiceField(
+        label=_('Site groups'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False
         required=False
     )
     )
     sites = DynamicModelMultipleChoiceField(
     sites = DynamicModelMultipleChoiceField(
+        label=_('Sites'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
     locations = DynamicModelMultipleChoiceField(
     locations = DynamicModelMultipleChoiceField(
+        label=_('Locations'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False
         required=False
     )
     )
     device_types = DynamicModelMultipleChoiceField(
     device_types = DynamicModelMultipleChoiceField(
+        label=_('Device types'),
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
         required=False
         required=False
     )
     )
     roles = DynamicModelMultipleChoiceField(
     roles = DynamicModelMultipleChoiceField(
+        label=_('Roles'),
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False
         required=False
     )
     )
     platforms = DynamicModelMultipleChoiceField(
     platforms = DynamicModelMultipleChoiceField(
+        label=_('Platforms'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False
         required=False
     )
     )
     cluster_types = DynamicModelMultipleChoiceField(
     cluster_types = DynamicModelMultipleChoiceField(
+        label=_('Cluster types'),
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         required=False
         required=False
     )
     )
     cluster_groups = DynamicModelMultipleChoiceField(
     cluster_groups = DynamicModelMultipleChoiceField(
+        label=_('Cluster groups'),
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False
         required=False
     )
     )
     clusters = DynamicModelMultipleChoiceField(
     clusters = DynamicModelMultipleChoiceField(
+        label=_('Clusters'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False
         required=False
     )
     )
     tenant_groups = DynamicModelMultipleChoiceField(
     tenant_groups = DynamicModelMultipleChoiceField(
+        label=_('Tenat groups'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False
         required=False
     )
     )
     tenants = DynamicModelMultipleChoiceField(
     tenants = DynamicModelMultipleChoiceField(
+        label=_('Tenants'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
+        label=_('Tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
     )
     )
     data = JSONField(
     data = JSONField(
+        label=_('Data'),
         required=False
         required=False
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Config Context', ('name', 'weight', 'description', 'data', 'is_active')),
-        ('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
-        ('Assignment', (
+        (_('Config Context'), ('name', 'weight', 'description', 'data', 'is_active')),
+        (_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
+        (_('Assignment'), (
             'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
             'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
             'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
             'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
         )),
         )),
@@ -351,25 +374,27 @@ class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         super().clean()
         super().clean()
 
 
         if not self.cleaned_data.get('data') and not self.cleaned_data.get('data_file'):
         if not self.cleaned_data.get('data') and not self.cleaned_data.get('data_file'):
-            raise forms.ValidationError("Must specify either local data or a data file")
+            raise forms.ValidationError(_("Must specify either local data or a data file"))
 
 
         return self.cleaned_data
         return self.cleaned_data
 
 
 
 
 class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
 class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
+        label=_('Tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
     )
     )
     template_code = forms.CharField(
     template_code = forms.CharField(
+        label=_('Template code'),
         required=False,
         required=False,
         widget=forms.Textarea(attrs={'class': 'font-monospace'})
         widget=forms.Textarea(attrs={'class': 'font-monospace'})
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Config Template', ('name', 'description', 'environment_params', 'tags')),
-        ('Content', ('template_code',)),
-        ('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
+        (_('Config Template'), ('name', 'description', 'environment_params', 'tags')),
+        (_('Content'), ('template_code',)),
+        (_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -393,7 +418,7 @@ class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         super().clean()
         super().clean()
 
 
         if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
         if not self.cleaned_data.get('template_code') and not self.cleaned_data.get('data_file'):
-            raise forms.ValidationError("Must specify either local content or a data file")
+            raise forms.ValidationError(_("Must specify either local content or a data file"))
 
 
         return self.cleaned_data
         return self.cleaned_data
 
 
@@ -409,6 +434,7 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
 
 
 class JournalEntryForm(NetBoxModelForm):
 class JournalEntryForm(NetBoxModelForm):
     kind = forms.ChoiceField(
     kind = forms.ChoiceField(
+        label=_('Kind'),
         choices=add_blank_choice(JournalEntryKindChoices),
         choices=add_blank_choice(JournalEntryKindChoices),
         required=False
         required=False
     )
     )
@@ -451,16 +477,16 @@ class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMe
     """
     """
 
 
     fieldsets = (
     fieldsets = (
-        ('Rack Elevations', ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH')),
-        ('Power', ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')),
-        ('IPAM', ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4')),
-        ('Security', ('ALLOWED_URL_SCHEMES',)),
-        ('Banners', ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')),
-        ('Pagination', ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')),
-        ('Validation', ('CUSTOM_VALIDATORS',)),
-        ('User Preferences', ('DEFAULT_USER_PREFERENCES',)),
-        ('Miscellaneous', ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL')),
-        ('Config Revision', ('comment',))
+        (_('Rack Elevations'), ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH')),
+        (_('Power'), ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')),
+        (_('IPAM'), ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4')),
+        (_('Security'), ('ALLOWED_URL_SCHEMES',)),
+        (_('Banners'), ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')),
+        (_('Pagination'), ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')),
+        (_('Validation'), ('CUSTOM_VALIDATORS',)),
+        (_('User Preferences'), ('DEFAULT_USER_PREFERENCES',)),
+        (_('Miscellaneous'), ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL')),
+        (_('Config Revision'), ('comment',))
     )
     )
 
 
     class Meta:
     class Meta:
@@ -487,11 +513,11 @@ class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMe
                 help_text = self.fields[param.name].help_text
                 help_text = self.fields[param.name].help_text
                 if help_text:
                 if help_text:
                     help_text += '<br />'  # Line break
                     help_text += '<br />'  # Line break
-                help_text += f'Current value: <strong>{value}</strong>'
+                help_text += _('Current value: <strong>{value}</strong>').format(value=value)
                 if is_static:
                 if is_static:
-                    help_text += ' (defined statically)'
+                    help_text += _(' (defined statically)')
                 elif value == param.default:
                 elif value == param.default:
-                    help_text += ' (default)'
+                    help_text += _(' (default)')
                 self.fields[param.name].help_text = help_text
                 self.fields[param.name].help_text = help_text
                 self.fields[param.name].initial = value
                 self.fields[param.name].initial = value
             if is_static:
             if is_static:

+ 2 - 2
netbox/extras/forms/reports.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import DurationChoices
 from extras.choices import DurationChoices
 from utilities.forms import BootstrapMixin
 from utilities.forms import BootstrapMixin
@@ -33,7 +33,7 @@ class ReportForm(BootstrapMixin, forms.Form):
 
 
         # Annotate the current system time for reference
         # Annotate the current system time for reference
         now = local_now().strftime('%Y-%m-%d %H:%M:%S')
         now = local_now().strftime('%Y-%m-%d %H:%M:%S')
-        self.fields['schedule_at'].help_text += f' (current time: <strong>{now}</strong>)'
+        self.fields['schedule_at'].help_text += _(' (current time: <strong>{now}</strong>)').format(now=now)
 
 
         # Remove scheduling fields if scheduling is disabled
         # Remove scheduling fields if scheduling is disabled
         if not scheduling_enabled:
         if not scheduling_enabled:

+ 2 - 2
netbox/extras/forms/scripts.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import DurationChoices
 from extras.choices import DurationChoices
 from utilities.forms import BootstrapMixin
 from utilities.forms import BootstrapMixin
@@ -39,7 +39,7 @@ class ScriptForm(BootstrapMixin, forms.Form):
 
 
         # Annotate the current system time for reference
         # Annotate the current system time for reference
         now = local_now().strftime('%Y-%m-%d %H:%M:%S')
         now = local_now().strftime('%Y-%m-%d %H:%M:%S')
-        self.fields['_schedule_at'].help_text += f' (current time: <strong>{now}</strong>)'
+        self.fields['_schedule_at'].help_text += _(' (current time: <strong>{now}</strong>)').format(now=now)
 
 
         # Remove scheduling fields if scheduling is disabled
         # Remove scheduling fields if scheduling is disabled
         if not scheduling_enabled:
         if not scheduling_enabled:

+ 1 - 1
netbox/ipam/forms/bulk_create.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from utilities.forms import BootstrapMixin
 from utilities.forms import BootstrapMixin
 from utilities.forms.fields import ExpandableIPAddressField
 from utilities.forms.fields import ExpandableIPAddressField

+ 69 - 39
netbox/ipam/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Region, Site, SiteGroup
 from dcim.models import Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
@@ -37,6 +37,7 @@ __all__ = (
 
 
 class VRFBulkEditForm(NetBoxModelBulkEditForm):
 class VRFBulkEditForm(NetBoxModelBulkEditForm):
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
@@ -46,12 +47,11 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Enforce unique space')
         label=_('Enforce unique space')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = VRF
     model = VRF
     fieldsets = (
     fieldsets = (
@@ -62,16 +62,16 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
 class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = RouteTarget
     model = RouteTarget
     fieldsets = (
     fieldsets = (
@@ -82,10 +82,12 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class RIRBulkEditForm(NetBoxModelBulkEditForm):
 class RIRBulkEditForm(NetBoxModelBulkEditForm):
     is_private = forms.NullBooleanField(
     is_private = forms.NullBooleanField(
+        label=_('Is private'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect
         widget=BulkEditNullBooleanSelect
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -104,10 +106,12 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm):
         label=_('RIR')
         label=_('RIR')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -121,6 +125,7 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ASNBulkEditForm(NetBoxModelBulkEditForm):
 class ASNBulkEditForm(NetBoxModelBulkEditForm):
     sites = DynamicModelMultipleChoiceField(
     sites = DynamicModelMultipleChoiceField(
+        label=_('Sites'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
@@ -130,16 +135,16 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
         label=_('RIR')
         label=_('RIR')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = ASN
     model = ASN
     fieldsets = (
     fieldsets = (
@@ -155,19 +160,20 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
         label=_('RIR')
         label=_('RIR')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     date_added = forms.DateField(
     date_added = forms.DateField(
+        label=_('Date added'),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = Aggregate
     model = Aggregate
     fieldsets = (
     fieldsets = (
@@ -178,9 +184,11 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class RoleBulkEditForm(NetBoxModelBulkEditForm):
 class RoleBulkEditForm(NetBoxModelBulkEditForm):
     weight = forms.IntegerField(
     weight = forms.IntegerField(
+        label=_('Weight'),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -194,14 +202,17 @@ class RoleBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class PrefixBulkEditForm(NetBoxModelBulkEditForm):
 class PrefixBulkEditForm(NetBoxModelBulkEditForm):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False
         required=False
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
+        label=_('Site group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False
         required=False
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -215,19 +226,23 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
         label=_('VRF')
         label=_('VRF')
     )
     )
     prefix_length = forms.IntegerField(
     prefix_length = forms.IntegerField(
+        label=_('Prefix length'),
         min_value=PREFIX_LENGTH_MIN,
         min_value=PREFIX_LENGTH_MIN,
         max_value=PREFIX_LENGTH_MAX,
         max_value=PREFIX_LENGTH_MAX,
         required=False
         required=False
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(PrefixStatusChoices),
         choices=add_blank_choice(PrefixStatusChoices),
         required=False
         required=False
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
@@ -242,18 +257,17 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Treat as 100% utilized')
         label=_('Treat as 100% utilized')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = Prefix
     model = Prefix
     fieldsets = (
     fieldsets = (
         (None, ('tenant', 'status', 'role', 'description')),
         (None, ('tenant', 'status', 'role', 'description')),
-        ('Site', ('region', 'site_group', 'site')),
-        ('Addressing', ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
+        (_('Site'), ('region', 'site_group', 'site')),
+        (_('Addressing'), ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'site', 'vrf', 'tenant', 'role', 'description', 'comments',
         'site', 'vrf', 'tenant', 'role', 'description', 'comments',
@@ -267,14 +281,17 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
         label=_('VRF')
         label=_('VRF')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(IPRangeStatusChoices),
         choices=add_blank_choice(IPRangeStatusChoices),
         required=False
         required=False
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
@@ -284,12 +301,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Treat as 100% utilized')
         label=_('Treat as 100% utilized')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = IPRange
     model = IPRange
     fieldsets = (
     fieldsets = (
@@ -307,19 +323,23 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
         label=_('VRF')
         label=_('VRF')
     )
     )
     mask_length = forms.IntegerField(
     mask_length = forms.IntegerField(
+        label=_('Mask length'),
         min_value=IPADDRESS_MASK_LENGTH_MIN,
         min_value=IPADDRESS_MASK_LENGTH_MIN,
         max_value=IPADDRESS_MASK_LENGTH_MAX,
         max_value=IPADDRESS_MASK_LENGTH_MAX,
         required=False
         required=False
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(IPAddressStatusChoices),
         choices=add_blank_choice(IPAddressStatusChoices),
         required=False
         required=False
     )
     )
     role = forms.ChoiceField(
     role = forms.ChoiceField(
+        label=_('Role'),
         choices=add_blank_choice(IPAddressRoleChoices),
         choices=add_blank_choice(IPAddressRoleChoices),
         required=False
         required=False
     )
     )
@@ -329,17 +349,16 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
         label=_('DNS name')
         label=_('DNS name')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = IPAddress
     model = IPAddress
     fieldsets = (
     fieldsets = (
         (None, ('status', 'role', 'tenant', 'description')),
         (None, ('status', 'role', 'tenant', 'description')),
-        ('Addressing', ('vrf', 'mask_length', 'dns_name')),
+        (_('Addressing'), ('vrf', 'mask_length', 'dns_name')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
         'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
@@ -348,6 +367,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
 class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
     protocol = forms.ChoiceField(
     protocol = forms.ChoiceField(
+        label=_('Protocol'),
         choices=add_blank_choice(FHRPGroupProtocolChoices),
         choices=add_blank_choice(FHRPGroupProtocolChoices),
         required=False
         required=False
     )
     )
@@ -367,27 +387,28 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Authentication key')
         label=_('Authentication key')
     )
     )
     name = forms.CharField(
     name = forms.CharField(
+        label=_('Name'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = FHRPGroup
     model = FHRPGroup
     fieldsets = (
     fieldsets = (
         (None, ('protocol', 'group_id', 'name', 'description')),
         (None, ('protocol', 'group_id', 'name', 'description')),
-        ('Authentication', ('auth_type', 'auth_key')),
+        (_('Authentication'), ('auth_type', 'auth_key')),
     )
     )
     nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
     nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
 
 
 
 
 class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
 class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
@@ -404,6 +425,7 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Maximum child VLAN VID')
         label=_('Maximum child VLAN VID')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -417,14 +439,17 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class VLANBulkEditForm(NetBoxModelBulkEditForm):
 class VLANBulkEditForm(NetBoxModelBulkEditForm):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False
         required=False
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
+        label=_('Site group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False
         required=False
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -433,6 +458,7 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -440,29 +466,31 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(VLANStatusChoices),
         choices=add_blank_choice(VLANStatusChoices),
         required=False
         required=False
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = VLAN
     model = VLAN
     fieldsets = (
     fieldsets = (
         (None, ('status', 'role', 'tenant', 'description')),
         (None, ('status', 'role', 'tenant', 'description')),
-        ('Site & Group', ('region', 'site_group', 'site', 'group')),
+        (_('Site & Group'), ('region', 'site_group', 'site', 'group')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'site', 'group', 'tenant', 'role', 'description', 'comments',
         'site', 'group', 'tenant', 'role', 'description', 'comments',
@@ -471,10 +499,12 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
 class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
     protocol = forms.ChoiceField(
     protocol = forms.ChoiceField(
+        label=_('Protocol'),
         choices=add_blank_choice(ServiceProtocolChoices),
         choices=add_blank_choice(ServiceProtocolChoices),
         required=False
         required=False
     )
     )
     ports = NumericArrayField(
     ports = NumericArrayField(
+        label=_('Ports'),
         base_field=forms.IntegerField(
         base_field=forms.IntegerField(
             min_value=SERVICE_PORT_MIN,
             min_value=SERVICE_PORT_MIN,
             max_value=SERVICE_PORT_MAX
             max_value=SERVICE_PORT_MAX
@@ -482,12 +512,11 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = ServiceTemplate
     model = ServiceTemplate
     fieldsets = (
     fieldsets = (
@@ -502,20 +531,21 @@ class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
 
 
 class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
 class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=add_blank_choice(L2VPNTypeChoices),
         choices=add_blank_choice(L2VPNTypeChoices),
         required=False
         required=False
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = L2VPN
     model = L2VPN
     fieldsets = (
     fieldsets = (

+ 52 - 8
netbox/ipam/forms/bulk_import.py

@@ -2,7 +2,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db.models import Q
 from django.db.models import Q
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Device, Interface, Site
 from dcim.models import Device, Interface, Site
 from ipam.choices import *
 from ipam.choices import *
@@ -36,6 +36,7 @@ __all__ = (
 
 
 class VRFImportForm(NetBoxModelImportForm):
 class VRFImportForm(NetBoxModelImportForm):
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -49,6 +50,7 @@ class VRFImportForm(NetBoxModelImportForm):
 
 
 class RouteTargetImportForm(NetBoxModelImportForm):
 class RouteTargetImportForm(NetBoxModelImportForm):
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -70,11 +72,13 @@ class RIRImportForm(NetBoxModelImportForm):
 
 
 class AggregateImportForm(NetBoxModelImportForm):
 class AggregateImportForm(NetBoxModelImportForm):
     rir = CSVModelChoiceField(
     rir = CSVModelChoiceField(
+        label=_('RIR'),
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned RIR')
         help_text=_('Assigned RIR')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -88,11 +92,13 @@ class AggregateImportForm(NetBoxModelImportForm):
 
 
 class ASNRangeImportForm(NetBoxModelImportForm):
 class ASNRangeImportForm(NetBoxModelImportForm):
     rir = CSVModelChoiceField(
     rir = CSVModelChoiceField(
+        label=_('RIR'),
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned RIR')
         help_text=_('Assigned RIR')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -106,11 +112,13 @@ class ASNRangeImportForm(NetBoxModelImportForm):
 
 
 class ASNImportForm(NetBoxModelImportForm):
 class ASNImportForm(NetBoxModelImportForm):
     rir = CSVModelChoiceField(
     rir = CSVModelChoiceField(
+        label=_('RIR'),
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned RIR')
         help_text=_('Assigned RIR')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -132,40 +140,47 @@ class RoleImportForm(NetBoxModelImportForm):
 
 
 class PrefixImportForm(NetBoxModelImportForm):
 class PrefixImportForm(NetBoxModelImportForm):
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
+        label=_('VRF'),
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned VRF')
         help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     vlan_group = CSVModelChoiceField(
     vlan_group = CSVModelChoiceField(
+        label=_('VLAN group'),
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_("VLAN's group (if any)")
         help_text=_("VLAN's group (if any)")
     )
     )
     vlan = CSVModelChoiceField(
     vlan = CSVModelChoiceField(
+        label=_('VLAN'),
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='vid',
         to_field_name='vid',
         help_text=_("Assigned VLAN")
         help_text=_("Assigned VLAN")
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -211,22 +226,26 @@ class PrefixImportForm(NetBoxModelImportForm):
 
 
 class IPRangeImportForm(NetBoxModelImportForm):
 class IPRangeImportForm(NetBoxModelImportForm):
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
+        label=_('VRF'),
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned VRF')
         help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=IPRangeStatusChoices,
         choices=IPRangeStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -243,45 +262,53 @@ class IPRangeImportForm(NetBoxModelImportForm):
 
 
 class IPAddressImportForm(NetBoxModelImportForm):
 class IPAddressImportForm(NetBoxModelImportForm):
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
+        label=_('VRF'),
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned VRF')
         help_text=_('Assigned VRF')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     role = CSVChoiceField(
     role = CSVChoiceField(
+        label=_('Role'),
         choices=IPAddressRoleChoices,
         choices=IPAddressRoleChoices,
         required=False,
         required=False,
         help_text=_('Functional role')
         help_text=_('Functional role')
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         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(
     virtual_machine = CSVModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent VM of assigned interface (if any)')
         help_text=_('Parent VM of assigned interface (if any)')
     )
     )
     interface = CSVModelChoiceField(
     interface = CSVModelChoiceField(
+        label=_('Interface'),
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned interface')
         help_text=_('Assigned interface')
     )
     )
     is_primary = forms.BooleanField(
     is_primary = forms.BooleanField(
+        label=_('Is primary'),
         help_text=_('Make this the primary IP for the assigned device'),
         help_text=_('Make this the primary IP for the assigned device'),
         required=False
         required=False
     )
     )
@@ -321,11 +348,11 @@ class IPAddressImportForm(NetBoxModelImportForm):
         # Validate is_primary
         # Validate is_primary
         if is_primary and not device and not virtual_machine:
         if is_primary and not device and not virtual_machine:
             raise forms.ValidationError({
             raise forms.ValidationError({
-                "is_primary": "No device or virtual machine specified; cannot set as primary IP"
+                "is_primary": _("No device or virtual machine specified; cannot set as primary IP")
             })
             })
         if is_primary and not interface:
         if is_primary and not interface:
             raise forms.ValidationError({
             raise forms.ValidationError({
-                "is_primary": "No interface specified; cannot set as primary IP"
+                "is_primary": _("No interface specified; cannot set as primary IP")
             })
             })
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
@@ -350,9 +377,11 @@ class IPAddressImportForm(NetBoxModelImportForm):
 
 
 class FHRPGroupImportForm(NetBoxModelImportForm):
 class FHRPGroupImportForm(NetBoxModelImportForm):
     protocol = CSVChoiceField(
     protocol = CSVChoiceField(
+        label=_('Protocol'),
         choices=FHRPGroupProtocolChoices
         choices=FHRPGroupProtocolChoices
     )
     )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
+        label=_('Auth type'),
         choices=FHRPGroupAuthTypeChoices,
         choices=FHRPGroupAuthTypeChoices,
         required=False
         required=False
     )
     )
@@ -373,13 +402,13 @@ class VLANGroupImportForm(NetBoxModelImportForm):
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
         required=False,
         required=False,
-        label=f'Minimum child VLAN VID (default: {VLAN_VID_MIN})'
+        label=_('Minimum child VLAN VID (default: {minimum})').format(minimum=VLAN_VID_MIN)
     )
     )
     max_vid = forms.IntegerField(
     max_vid = forms.IntegerField(
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
         max_value=VLAN_VID_MAX,
         max_value=VLAN_VID_MAX,
         required=False,
         required=False,
-        label=f'Maximum child VLAN VID (default: {VLAN_VID_MIN})'
+        label=_('Maximum child VLAN VID (default: {maximum})').format(maximum=VLAN_VID_MIN)
     )
     )
 
 
     class Meta:
     class Meta:
@@ -392,28 +421,33 @@ class VLANGroupImportForm(NetBoxModelImportForm):
 
 
 class VLANImportForm(NetBoxModelImportForm):
 class VLANImportForm(NetBoxModelImportForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned VLAN group')
         help_text=_('Assigned VLAN group')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=VLANStatusChoices,
         choices=VLANStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -427,6 +461,7 @@ class VLANImportForm(NetBoxModelImportForm):
 
 
 class ServiceTemplateImportForm(NetBoxModelImportForm):
 class ServiceTemplateImportForm(NetBoxModelImportForm):
     protocol = CSVChoiceField(
     protocol = CSVChoiceField(
+        label=_('Protocol'),
         choices=ServiceProtocolChoices,
         choices=ServiceProtocolChoices,
         help_text=_('IP protocol')
         help_text=_('IP protocol')
     )
     )
@@ -438,18 +473,21 @@ class ServiceTemplateImportForm(NetBoxModelImportForm):
 
 
 class ServiceImportForm(NetBoxModelImportForm):
 class ServiceImportForm(NetBoxModelImportForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         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(
     virtual_machine = CSVModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Required if not assigned to a device')
         help_text=_('Required if not assigned to a device')
     )
     )
     protocol = CSVChoiceField(
     protocol = CSVChoiceField(
+        label=_('Protocol'),
         choices=ServiceProtocolChoices,
         choices=ServiceProtocolChoices,
         help_text=_('IP protocol')
         help_text=_('IP protocol')
     )
     )
@@ -461,11 +499,13 @@ class ServiceImportForm(NetBoxModelImportForm):
 
 
 class L2VPNImportForm(NetBoxModelImportForm):
 class L2VPNImportForm(NetBoxModelImportForm):
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
     )
     )
     type = CSVChoiceField(
     type = CSVChoiceField(
+        label=_('Type'),
         choices=L2VPNTypeChoices,
         choices=L2VPNTypeChoices,
         help_text=_('L2VPN type')
         help_text=_('L2VPN type')
     )
     )
@@ -484,24 +524,28 @@ class L2VPNTerminationImportForm(NetBoxModelImportForm):
         label=_('L2VPN'),
         label=_('L2VPN'),
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent device (for interface)')
         help_text=_('Parent device (for interface)')
     )
     )
     virtual_machine = CSVModelChoiceField(
     virtual_machine = CSVModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent virtual machine (for interface)')
         help_text=_('Parent virtual machine (for interface)')
     )
     )
     interface = CSVModelChoiceField(
     interface = CSVModelChoiceField(
+        label=_('Interface'),
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         queryset=Interface.objects.none(),  # Can also refer to VMInterface
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned interface (device or VM)')
         help_text=_('Assigned interface (device or VM)')
     )
     )
     vlan = CSVModelChoiceField(
     vlan = CSVModelChoiceField(
+        label=_('VLAN'),
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -531,10 +575,10 @@ class L2VPNTerminationImportForm(NetBoxModelImportForm):
         super().clean()
         super().clean()
 
 
         if self.cleaned_data.get('device') and self.cleaned_data.get('virtual_machine'):
         if self.cleaned_data.get('device') and self.cleaned_data.get('virtual_machine'):
-            raise ValidationError('Cannot import device and VM interface terminations simultaneously.')
+            raise ValidationError(_('Cannot import device and VM interface terminations simultaneously.'))
         if not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')):
         if not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')):
-            raise ValidationError('Each termination must specify either an interface or a VLAN.')
+            raise ValidationError(_('Each termination must specify either an interface or a VLAN.'))
         if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'):
         if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'):
-            raise ValidationError('Cannot assign both an interface and a VLAN.')
+            raise ValidationError(_('Cannot assign both an interface and a VLAN.'))
 
 
         self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')
         self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')

+ 49 - 37
netbox/ipam/forms/filtersets.py

@@ -1,6 +1,6 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Location, Rack, Region, Site, SiteGroup, Device
 from dcim.models import Location, Rack, Region, Site, SiteGroup, Device
 from ipam.choices import *
 from ipam.choices import *
@@ -47,8 +47,8 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VRF
     model = VRF
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Route Targets', ('import_target_id', 'export_target_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Route Targets'), ('import_target_id', 'export_target_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     import_target_id = DynamicModelMultipleChoiceField(
     import_target_id = DynamicModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
@@ -67,8 +67,8 @@ class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = RouteTarget
     model = RouteTarget
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('VRF', ('importing_vrf_id', 'exporting_vrf_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('VRF'), ('importing_vrf_id', 'exporting_vrf_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     importing_vrf_id = DynamicModelMultipleChoiceField(
     importing_vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
@@ -99,8 +99,8 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Aggregate
     model = Aggregate
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('family', 'rir_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Attributes'), ('family', 'rir_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     family = forms.ChoiceField(
     family = forms.ChoiceField(
         required=False,
         required=False,
@@ -119,8 +119,8 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = ASNRange
     model = ASNRange
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Range', ('rir_id', 'start', 'end')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Range'), ('rir_id', 'start', 'end')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     rir_id = DynamicModelMultipleChoiceField(
     rir_id = DynamicModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
@@ -128,9 +128,11 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('RIR')
         label=_('RIR')
     )
     )
     start = forms.IntegerField(
     start = forms.IntegerField(
+        label=_('Start'),
         required=False
         required=False
     )
     )
     end = forms.IntegerField(
     end = forms.IntegerField(
+        label=_('End'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -140,8 +142,8 @@ class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = ASN
     model = ASN
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Assignment', ('rir_id', 'site_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Assignment'), ('rir_id', 'site_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     rir_id = DynamicModelMultipleChoiceField(
     rir_id = DynamicModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
@@ -165,10 +167,10 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = Prefix
     model = Prefix
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Addressing', ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
-        ('VRF', ('vrf_id', 'present_in_vrf_id')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Addressing'), ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
+        (_('VRF'), ('vrf_id', 'present_in_vrf_id')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     mask_length__lte = forms.IntegerField(
     mask_length__lte = forms.IntegerField(
         widget=forms.HiddenInput()
         widget=forms.HiddenInput()
@@ -204,6 +206,7 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Present in VRF')
         label=_('Present in VRF')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
         required=False
         required=False
     )
     )
@@ -253,8 +256,8 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPRange
     model = IPRange
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Attriubtes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     family = forms.ChoiceField(
     family = forms.ChoiceField(
         required=False,
         required=False,
@@ -268,6 +271,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         null_option='Global'
         null_option='Global'
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=IPRangeStatusChoices,
         choices=IPRangeStatusChoices,
         required=False
         required=False
     )
     )
@@ -291,10 +295,10 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = IPAddress
     model = IPAddress
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')),
-        ('VRF', ('vrf_id', 'present_in_vrf_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Device/VM', ('device_id', 'virtual_machine_id')),
+        (_('Attributes'), ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')),
+        (_('VRF'), ('vrf_id', 'present_in_vrf_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Device/VM'), ('device_id', 'virtual_machine_id')),
     )
     )
     parent = forms.CharField(
     parent = forms.CharField(
         required=False,
         required=False,
@@ -337,10 +341,12 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Assigned VM'),
         label=_('Assigned VM'),
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
         required=False
         required=False
     )
     )
     role = forms.MultipleChoiceField(
     role = forms.MultipleChoiceField(
+        label=_('Role'),
         choices=IPAddressRoleChoices,
         choices=IPAddressRoleChoices,
         required=False
         required=False
     )
     )
@@ -358,29 +364,31 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
     model = FHRPGroup
     model = FHRPGroup
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('name', 'protocol', 'group_id')),
-        ('Authentication', ('auth_type', 'auth_key')),
+        (_('Attributes'), ('name', 'protocol', 'group_id')),
+        (_('Authentication'), ('auth_type', 'auth_key')),
     )
     )
     name = forms.CharField(
     name = forms.CharField(
+        label=_('Name'),
         required=False
         required=False
     )
     )
     protocol = forms.MultipleChoiceField(
     protocol = forms.MultipleChoiceField(
+        label=_('Protocol'),
         choices=FHRPGroupProtocolChoices,
         choices=FHRPGroupProtocolChoices,
         required=False
         required=False
     )
     )
     group_id = forms.IntegerField(
     group_id = forms.IntegerField(
         min_value=0,
         min_value=0,
         required=False,
         required=False,
-        label='Group ID'
+        label=_('Group ID')
     )
     )
     auth_type = forms.MultipleChoiceField(
     auth_type = forms.MultipleChoiceField(
         choices=FHRPGroupAuthTypeChoices,
         choices=FHRPGroupAuthTypeChoices,
         required=False,
         required=False,
-        label='Authentication type'
+        label=_('Authentication type')
     )
     )
     auth_key = forms.CharField(
     auth_key = forms.CharField(
         required=False,
         required=False,
-        label='Authentication key'
+        label=_('Authentication key')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -388,8 +396,8 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
 class VLANGroupFilterForm(NetBoxModelFilterSetForm):
 class VLANGroupFilterForm(NetBoxModelFilterSetForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region', 'sitegroup', 'site', 'location', 'rack')),
-        ('VLAN ID', ('min_vid', 'max_vid')),
+        (_('Location'), ('region', 'sitegroup', 'site', 'location', 'rack')),
+        (_('VLAN ID'), ('min_vid', 'max_vid')),
     )
     )
     model = VLANGroup
     model = VLANGroup
     region = DynamicModelMultipleChoiceField(
     region = DynamicModelMultipleChoiceField(
@@ -436,9 +444,9 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = VLAN
     model = VLAN
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Attributes', ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Attributes'), ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -469,6 +477,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('VLAN group')
         label=_('VLAN group')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=VLANStatusChoices,
         choices=VLANStatusChoices,
         required=False
         required=False
     )
     )
@@ -480,7 +489,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     )
     )
     vid = forms.IntegerField(
     vid = forms.IntegerField(
         required=False,
         required=False,
-        label='VLAN ID'
+        label=_('VLAN ID')
     )
     )
     l2vpn_id = DynamicModelMultipleChoiceField(
     l2vpn_id = DynamicModelMultipleChoiceField(
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
@@ -494,13 +503,15 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
     model = ServiceTemplate
     model = ServiceTemplate
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('protocol', 'port')),
+        (_('Attributes'), ('protocol', 'port')),
     )
     )
     protocol = forms.ChoiceField(
     protocol = forms.ChoiceField(
+        label=_('Protocol'),
         choices=add_blank_choice(ServiceProtocolChoices),
         choices=add_blank_choice(ServiceProtocolChoices),
         required=False
         required=False
     )
     )
     port = forms.IntegerField(
     port = forms.IntegerField(
+        label=_('Port'),
         required=False,
         required=False,
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -515,10 +526,11 @@ class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = L2VPN
     model = L2VPN
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('type', 'import_target_id', 'export_target_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
+        (_('Attributes'), ('type', 'import_target_id', 'export_target_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
     )
     )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
+        label=_('Type'),
         choices=add_blank_choice(L2VPNTypeChoices),
         choices=add_blank_choice(L2VPNTypeChoices),
         required=False
         required=False
     )
     )
@@ -539,14 +551,14 @@ class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm):
     model = L2VPNTermination
     model = L2VPNTermination
     fieldsets = (
     fieldsets = (
         (None, ('filter_id', 'l2vpn_id',)),
         (None, ('filter_id', 'l2vpn_id',)),
-        ('Assigned Object', (
+        (_('Assigned Object'), (
             'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id',
             'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id',
         )),
         )),
     )
     )
     l2vpn_id = DynamicModelChoiceField(
     l2vpn_id = DynamicModelChoiceField(
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),
         required=False,
         required=False,
-        label='L2VPN'
+        label=_('L2VPN')
     )
     )
     assigned_object_type_id = ContentTypeMultipleChoiceField(
     assigned_object_type_id = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.filter(L2VPN_ASSIGNMENT_MODELS),
         queryset=ContentType.objects.filter(L2VPN_ASSIGNMENT_MODELS),

+ 59 - 34
netbox/ipam/forms/model_forms.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
@@ -46,19 +46,21 @@ __all__ = (
 
 
 class VRFForm(TenancyForm, NetBoxModelForm):
 class VRFForm(TenancyForm, NetBoxModelForm):
     import_targets = DynamicModelMultipleChoiceField(
     import_targets = DynamicModelMultipleChoiceField(
+        label=_('Import targets'),
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False
         required=False
     )
     )
     export_targets = DynamicModelMultipleChoiceField(
     export_targets = DynamicModelMultipleChoiceField(
+        label=_('Export targets'),
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
-        ('Route Targets', ('import_targets', 'export_targets')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('VRF'), ('name', 'rd', 'enforce_unique', 'description', 'tags')),
+        (_('Route Targets'), ('import_targets', 'export_targets')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -90,7 +92,7 @@ class RIRForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('RIR', (
+        (_('RIR'), (
             'name', 'slug', 'is_private', 'description', 'tags',
             'name', 'slug', 'is_private', 'description', 'tags',
         )),
         )),
     )
     )
@@ -110,8 +112,8 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Aggregate'), ('prefix', 'rir', 'date_added', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -131,8 +133,8 @@ class ASNRangeForm(TenancyForm, NetBoxModelForm):
     )
     )
     slug = SlugField()
     slug = SlugField()
     fieldsets = (
     fieldsets = (
-        ('ASN Range', ('name', 'slug', 'rir', 'start', 'end', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('ASN Range'), ('name', 'slug', 'rir', 'start', 'end', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -155,8 +157,8 @@ class ASNForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('ASN'), ('asn', 'rir', 'sites', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -184,7 +186,7 @@ class RoleForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Role', (
+        (_('Role'), (
             'name', 'slug', 'weight', 'description', 'tags',
             'name', 'slug', 'weight', 'description', 'tags',
         )),
         )),
     )
     )
@@ -203,6 +205,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
         label=_('VRF')
         label=_('VRF')
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         selector=True,
         selector=True,
@@ -215,15 +218,16 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
         label=_('VLAN'),
         label=_('VLAN'),
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
-        ('Site/VLAN Assignment', ('site', 'vlan')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Prefix'), ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
+        (_('Site/VLAN Assignment'), ('site', 'vlan')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -241,14 +245,15 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
         label=_('VRF')
         label=_('VRF')
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('IP Range'), ('vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -261,6 +266,7 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
 
 
 class IPAddressForm(TenancyForm, NetBoxModelForm):
 class IPAddressForm(TenancyForm, NetBoxModelForm):
     interface = DynamicModelChoiceField(
     interface = DynamicModelChoiceField(
+        label=_('Interface'),
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         selector=True,
         selector=True,
@@ -341,13 +347,13 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
         ]
         ]
         if len(selected_objects) > 1:
         if len(selected_objects) > 1:
             raise forms.ValidationError({
             raise forms.ValidationError({
-                selected_objects[1]: "An IP address can only be assigned to a single object."
+                selected_objects[1]: _("An IP address can only be assigned to a single object.")
             })
             })
         elif selected_objects:
         elif selected_objects:
             assigned_object = self.cleaned_data[selected_objects[0]]
             assigned_object = self.cleaned_data[selected_objects[0]]
             if self.instance.pk and self.cleaned_data['primary_for_parent'] and assigned_object != self.instance.assigned_object:
             if self.instance.pk and self.cleaned_data['primary_for_parent'] and assigned_object != self.instance.assigned_object:
                 raise ValidationError(
                 raise ValidationError(
-                    "Cannot reassign IP address while it is designated as the primary IP for the parent object"
+                    _("Cannot reassign IP address while it is designated as the primary IP for the parent object")
                 )
                 )
             self.instance.assigned_object = assigned_object
             self.instance.assigned_object = assigned_object
         else:
         else:
@@ -357,19 +363,21 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
         interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
         interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
         if self.cleaned_data.get('primary_for_parent') and not interface:
         if self.cleaned_data.get('primary_for_parent') and not interface:
             self.add_error(
             self.add_error(
-                'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
+                'primary_for_parent', _("Only IP addresses assigned to an interface can be designated as primary IPs.")
             )
             )
 
 
         # Do not allow assigning a network ID or broadcast address to an interface.
         # Do not allow assigning a network ID or broadcast address to an interface.
         if interface and (address := self.cleaned_data.get('address')):
         if interface and (address := self.cleaned_data.get('address')):
             if address.ip == address.network:
             if address.ip == address.network:
-                msg = f"{address} is a network ID, which may not be assigned to an interface."
+                msg = _("{address} is a network ID, which may not be assigned to an interface.").format(address=address)
                 if address.version == 4 and address.prefixlen not in (31, 32):
                 if address.version == 4 and address.prefixlen not in (31, 32):
                     raise ValidationError(msg)
                     raise ValidationError(msg)
                 if address.version == 6 and address.prefixlen not in (127, 128):
                 if address.version == 6 and address.prefixlen not in (127, 128):
                     raise ValidationError(msg)
                     raise ValidationError(msg)
             if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32):
             if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32):
-                msg = f"{address} is a broadcast address, which may not be assigned to an interface."
+                msg = _("{address} is a broadcast address, which may not be assigned to an interface.").format(
+                    address=address
+                )
                 raise ValidationError(msg)
                 raise ValidationError(msg)
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
@@ -442,9 +450,9 @@ class FHRPGroupForm(NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('FHRP Group', ('protocol', 'group_id', 'name', 'description', 'tags')),
-        ('Authentication', ('auth_type', 'auth_key')),
-        ('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
+        (_('FHRP Group'), ('protocol', 'group_id', 'name', 'description', 'tags')),
+        (_('Authentication'), ('auth_type', 'auth_key')),
+        (_('Virtual IP Address'), ('ip_vrf', 'ip_address', 'ip_status'))
     )
     )
 
 
     class Meta:
     class Meta:
@@ -497,6 +505,7 @@ class FHRPGroupForm(NetBoxModelForm):
 
 
 class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
 class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=FHRPGroup.objects.all()
         queryset=FHRPGroup.objects.all()
     )
     )
 
 
@@ -514,10 +523,12 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
 
 
 class VLANGroupForm(NetBoxModelForm):
 class VLANGroupForm(NetBoxModelForm):
     scope_type = ContentTypeChoiceField(
     scope_type = ContentTypeChoiceField(
+        label=_('Scope type'),
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         required=False
         required=False
     )
     )
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -533,6 +544,7 @@ class VLANGroupForm(NetBoxModelForm):
         label=_('Site group')
         label=_('Site group')
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -544,6 +556,7 @@ class VLANGroupForm(NetBoxModelForm):
         }
         }
     )
     )
     location = DynamicModelChoiceField(
     location = DynamicModelChoiceField(
+        label=_('Location'),
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -554,6 +567,7 @@ class VLANGroupForm(NetBoxModelForm):
         }
         }
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -570,6 +584,7 @@ class VLANGroupForm(NetBoxModelForm):
         label=_('Cluster group')
         label=_('Cluster group')
     )
     )
     cluster = DynamicModelChoiceField(
     cluster = DynamicModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -579,9 +594,9 @@ class VLANGroupForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('VLAN Group', ('name', 'slug', 'description', 'tags')),
-        ('Child VLANs', ('min_vid', 'max_vid')),
-        ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
+        (_('VLAN Group'), ('name', 'slug', 'description', 'tags')),
+        (_('Child VLANs'), ('min_vid', 'max_vid')),
+        (_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -621,12 +636,14 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         label=_('VLAN Group')
         label=_('VLAN Group')
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
         selector=True
         selector=True
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False
         required=False
     )
     )
@@ -642,6 +659,7 @@ class VLANForm(TenancyForm, NetBoxModelForm):
 
 
 class ServiceTemplateForm(NetBoxModelForm):
 class ServiceTemplateForm(NetBoxModelForm):
     ports = NumericArrayField(
     ports = NumericArrayField(
+        label=_('Ports'),
         base_field=forms.IntegerField(
         base_field=forms.IntegerField(
             min_value=SERVICE_PORT_MIN,
             min_value=SERVICE_PORT_MIN,
             max_value=SERVICE_PORT_MAX
             max_value=SERVICE_PORT_MAX
@@ -651,7 +669,7 @@ class ServiceTemplateForm(NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Service Template', (
+        (_('Service Template'), (
             'name', 'protocol', 'ports', 'description', 'tags',
             'name', 'protocol', 'ports', 'description', 'tags',
         )),
         )),
     )
     )
@@ -663,16 +681,19 @@ class ServiceTemplateForm(NetBoxModelForm):
 
 
 class ServiceForm(NetBoxModelForm):
 class ServiceForm(NetBoxModelForm):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
     )
     )
     virtual_machine = DynamicModelChoiceField(
     virtual_machine = DynamicModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
     )
     )
     ports = NumericArrayField(
     ports = NumericArrayField(
+        label=_('Ports'),
         base_field=forms.IntegerField(
         base_field=forms.IntegerField(
             min_value=SERVICE_PORT_MIN,
             min_value=SERVICE_PORT_MIN,
             max_value=SERVICE_PORT_MAX
             max_value=SERVICE_PORT_MAX
@@ -699,6 +720,7 @@ class ServiceForm(NetBoxModelForm):
 
 
 class ServiceCreateForm(ServiceForm):
 class ServiceCreateForm(ServiceForm):
     service_template = DynamicModelChoiceField(
     service_template = DynamicModelChoiceField(
+        label=_('Service template'),
         queryset=ServiceTemplate.objects.all(),
         queryset=ServiceTemplate.objects.all(),
         required=False
         required=False
     )
     )
@@ -739,19 +761,21 @@ class ServiceCreateForm(ServiceForm):
 class L2VPNForm(TenancyForm, NetBoxModelForm):
 class L2VPNForm(TenancyForm, NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
     import_targets = DynamicModelMultipleChoiceField(
     import_targets = DynamicModelMultipleChoiceField(
+        label=_('Import targets'),
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False
         required=False
     )
     )
     export_targets = DynamicModelMultipleChoiceField(
     export_targets = DynamicModelMultipleChoiceField(
+        label=_('Export targets'),
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('L2VPN', ('name', 'slug', 'type', 'identifier', 'description', 'tags')),
-        ('Route Targets', ('import_targets', 'export_targets')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('L2VPN'), ('name', 'slug', 'type', 'identifier', 'description', 'tags')),
+        (_('Route Targets'), ('import_targets', 'export_targets')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -777,6 +801,7 @@ class L2VPNTerminationForm(NetBoxModelForm):
         label=_('VLAN')
         label=_('VLAN')
     )
     )
     interface = DynamicModelChoiceField(
     interface = DynamicModelChoiceField(
+        label=_('Interface'),
         queryset=Interface.objects.all(),
         queryset=Interface.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
@@ -815,8 +840,8 @@ class L2VPNTerminationForm(NetBoxModelForm):
         vlan = self.cleaned_data.get('vlan')
         vlan = self.cleaned_data.get('vlan')
 
 
         if not (interface or vminterface or vlan):
         if not (interface or vminterface or vlan):
-            raise ValidationError('A termination must specify an interface or VLAN.')
+            raise ValidationError(_('A termination must specify an interface or VLAN.'))
         if len([x for x in (interface, vminterface, vlan) if x]) > 1:
         if len([x for x in (interface, vminterface, vlan) if x]) > 1:
-            raise ValidationError('A termination can only have one terminating object (an interface or VLAN).')
+            raise ValidationError(_('A termination can only have one terminating object (an interface or VLAN).'))
 
 
         self.instance.assigned_object = interface or vminterface or vlan
         self.instance.assigned_object = interface or vminterface or vlan

+ 7 - 2
netbox/netbox/forms/base.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
 from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
@@ -28,7 +28,8 @@ class NetBoxModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
     fieldsets = ()
     fieldsets = ()
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
-        required=False
+        required=False,
+        label=_('Tags'),
     )
     )
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
@@ -73,10 +74,12 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
     Base form for creating a NetBox objects from CSV data. Used for bulk importing.
     Base form for creating a NetBox objects from CSV data. Used for bulk importing.
     """
     """
     id = forms.IntegerField(
     id = forms.IntegerField(
+        label=_('Id'),
         required=False,
         required=False,
         help_text='Numeric ID of an existing object to update (if not creating a new object)'
         help_text='Numeric ID of an existing object to update (if not creating a new object)'
     )
     )
     tags = CSVModelMultipleChoiceField(
     tags = CSVModelMultipleChoiceField(
+        label=_('Tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False,
         required=False,
         to_field_name='slug',
         to_field_name='slug',
@@ -109,10 +112,12 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
     )
     )
     add_tags = DynamicModelMultipleChoiceField(
     add_tags = DynamicModelMultipleChoiceField(
+        label=_('Add tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
     )
     )
     remove_tags = DynamicModelMultipleChoiceField(
     remove_tags = DynamicModelMultipleChoiceField(
+        label=_('Remove tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
     )
     )

+ 18 - 3
netbox/tenancy/forms/bulk_edit.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 from netbox.forms import NetBoxModelBulkEditForm
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.choices import ContactPriorityChoices
 from tenancy.choices import ContactPriorityChoices
@@ -22,10 +23,12 @@ __all__ = (
 
 
 class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
 class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -36,6 +39,7 @@ class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class TenantBulkEditForm(NetBoxModelBulkEditForm):
 class TenantBulkEditForm(NetBoxModelBulkEditForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False
         required=False
     )
     )
@@ -53,10 +57,12 @@ class TenantBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
 class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Desciption'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -70,6 +76,7 @@ class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
 class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -83,34 +90,39 @@ class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ContactBulkEditForm(NetBoxModelBulkEditForm):
 class ContactBulkEditForm(NetBoxModelBulkEditForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False
         required=False
     )
     )
     title = forms.CharField(
     title = forms.CharField(
+        label=_('Title'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
     phone = forms.CharField(
     phone = forms.CharField(
+        label=_('Phone'),
         max_length=50,
         max_length=50,
         required=False
         required=False
     )
     )
     email = forms.EmailField(
     email = forms.EmailField(
+        label=_('Email'),
         required=False
         required=False
     )
     )
     address = forms.CharField(
     address = forms.CharField(
+        label=_('Address'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
     link = forms.URLField(
     link = forms.URLField(
+        label=_('Link'),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = Contact
     model = Contact
     fieldsets = (
     fieldsets = (
@@ -121,14 +133,17 @@ class ContactBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ContactAssignmentBulkEditForm(NetBoxModelBulkEditForm):
 class ContactAssignmentBulkEditForm(NetBoxModelBulkEditForm):
     contact = DynamicModelChoiceField(
     contact = DynamicModelChoiceField(
+        label=_('Contact'),
         queryset=Contact.objects.all(),
         queryset=Contact.objects.all(),
         required=False
         required=False
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=ContactRole.objects.all(),
         queryset=ContactRole.objects.all(),
         required=False
         required=False
     )
     )
     priority = forms.ChoiceField(
     priority = forms.ChoiceField(
+        label=_('Priority'),
         choices=add_blank_choice(ContactPriorityChoices),
         choices=add_blank_choice(ContactPriorityChoices),
         required=False
         required=False
     )
     )

+ 6 - 1
netbox/tenancy/forms/bulk_import.py

@@ -1,5 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
+
 from netbox.forms import NetBoxModelImportForm
 from netbox.forms import NetBoxModelImportForm
 from tenancy.models import *
 from tenancy.models import *
 from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, SlugField
 from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, SlugField
@@ -20,6 +21,7 @@ __all__ = (
 
 
 class TenantGroupImportForm(NetBoxModelImportForm):
 class TenantGroupImportForm(NetBoxModelImportForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -35,6 +37,7 @@ class TenantGroupImportForm(NetBoxModelImportForm):
 class TenantImportForm(NetBoxModelImportForm):
 class TenantImportForm(NetBoxModelImportForm):
     slug = SlugField()
     slug = SlugField()
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -52,6 +55,7 @@ class TenantImportForm(NetBoxModelImportForm):
 
 
 class ContactGroupImportForm(NetBoxModelImportForm):
 class ContactGroupImportForm(NetBoxModelImportForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -74,6 +78,7 @@ class ContactRoleImportForm(NetBoxModelImportForm):
 
 
 class ContactImportForm(NetBoxModelImportForm):
 class ContactImportForm(NetBoxModelImportForm):
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',

+ 3 - 2
netbox/tenancy/forms/filtersets.py

@@ -1,6 +1,6 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
 from netbox.forms import NetBoxModelFilterSetForm
 from netbox.forms import NetBoxModelFilterSetForm
@@ -84,7 +84,7 @@ class ContactAssignmentFilterForm(NetBoxModelFilterSetForm):
     model = ContactAssignment
     model = ContactAssignment
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
-        ('Assignment', ('content_type_id', 'group_id', 'contact_id', 'role_id', 'priority')),
+        (_('Assignment'), ('content_type_id', 'group_id', 'contact_id', 'role_id', 'priority')),
     )
     )
     content_type_id = ContentTypeMultipleChoiceField(
     content_type_id = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
@@ -108,6 +108,7 @@ class ContactAssignmentFilterForm(NetBoxModelFilterSetForm):
         label=_('Role')
         label=_('Role')
     )
     )
     priority = forms.MultipleChoiceField(
     priority = forms.MultipleChoiceField(
+        label=_('Priority'),
         choices=ContactPriorityChoices,
         choices=ContactPriorityChoices,
         required=False
         required=False
     )
     )

+ 3 - 1
netbox/tenancy/forms/forms.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from tenancy.models import *
 from tenancy.models import *
 from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
 from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
@@ -13,6 +13,7 @@ __all__ = (
 
 
 class TenancyForm(forms.Form):
 class TenancyForm(forms.Form):
     tenant_group = DynamicModelChoiceField(
     tenant_group = DynamicModelChoiceField(
+        label=_('Tenant group'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
@@ -21,6 +22,7 @@ class TenancyForm(forms.Form):
         }
         }
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={

+ 13 - 5
netbox/tenancy/forms/model_forms.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 
 
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.models import *
 from tenancy.models import *
@@ -21,13 +22,14 @@ __all__ = (
 
 
 class TenantGroupForm(NetBoxModelForm):
 class TenantGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Tenant Group', (
+        (_('Tenant Group'), (
             'parent', 'name', 'slug', 'description', 'tags',
             'parent', 'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -42,13 +44,14 @@ class TenantGroupForm(NetBoxModelForm):
 class TenantForm(NetBoxModelForm):
 class TenantForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
+        (_('Tenant'), ('name', 'slug', 'group', 'description', 'tags')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -64,13 +67,14 @@ class TenantForm(NetBoxModelForm):
 
 
 class ContactGroupForm(NetBoxModelForm):
 class ContactGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Contact Group', (
+        (_('Contact Group'), (
             'parent', 'name', 'slug', 'description', 'tags',
             'parent', 'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -84,7 +88,7 @@ class ContactRoleForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Contact Role', (
+        (_('Contact Role'), (
             'name', 'slug', 'description', 'tags',
             'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -96,13 +100,14 @@ class ContactRoleForm(NetBoxModelForm):
 
 
 class ContactForm(NetBoxModelForm):
 class ContactForm(NetBoxModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False
         required=False
     )
     )
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'tags')),
+        (_('Contact'), ('group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'tags')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -117,6 +122,7 @@ class ContactForm(NetBoxModelForm):
 
 
 class ContactAssignmentForm(BootstrapMixin, forms.ModelForm):
 class ContactAssignmentForm(BootstrapMixin, forms.ModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
@@ -124,12 +130,14 @@ class ContactAssignmentForm(BootstrapMixin, forms.ModelForm):
         }
         }
     )
     )
     contact = DynamicModelChoiceField(
     contact = DynamicModelChoiceField(
+        label=_('Contact'),
         queryset=Contact.objects.all(),
         queryset=Contact.objects.all(),
         query_params={
         query_params={
             'group_id': '$group'
             'group_id': '$group'
         }
         }
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=ContactRole.objects.all()
         queryset=ContactRole.objects.all()
     )
     )
 
 

+ 0 - 1
netbox/users/forms/model_forms.py

@@ -66,7 +66,6 @@ class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMe
     )
     )
     # List of clearable preferences
     # List of clearable preferences
     pk = forms.MultipleChoiceField(
     pk = forms.MultipleChoiceField(
-        label=_('Pk'),
         choices=[],
         choices=[],
         required=False
         required=False
     )
     )

+ 4 - 2
netbox/utilities/forms/fields/array.py

@@ -1,5 +1,6 @@
 from django import forms
 from django import forms
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
+from django.utils.translation import gettext_lazy as _
 
 
 from ..utils import parse_numeric_range
 from ..utils import parse_numeric_range
 
 
@@ -12,8 +13,9 @@ class NumericArrayField(SimpleArrayField):
 
 
     def clean(self, value):
     def clean(self, value):
         if value and not self.to_python(value):
         if value and not self.to_python(value):
-            raise forms.ValidationError(f'Invalid list ({value}). '
-                                        f'Must be numeric and ranges must be in ascending order')
+            raise forms.ValidationError(
+                _("Invalid list ({value}). Must be numeric and ranges must be in ascending order.").format(value=value)
+            )
         return super().clean(value)
         return super().clean(value)
 
 
     def to_python(self, value):
     def to_python(self, value):

+ 7 - 6
netbox/utilities/forms/fields/csv.py

@@ -1,4 +1,5 @@
 from django import forms
 from django import forms
+from django.utils.translation import gettext_lazy as _
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
 from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
@@ -40,7 +41,7 @@ class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
         if not value:
         if not value:
             return []
             return []
         if not isinstance(value, str):
         if not isinstance(value, str):
-            raise forms.ValidationError(f"Invalid value for a multiple choice field: {value}")
+            raise forms.ValidationError(_("Invalid value for a multiple choice field: {value}").format(value=value))
         return value.split(',')
         return value.split(',')
 
 
 
 
@@ -53,7 +54,7 @@ class CSVModelChoiceField(forms.ModelChoiceField):
     Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
     Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
     """
     """
     default_error_messages = {
     default_error_messages = {
-        'invalid_choice': 'Object not found: %(value)s',
+        'invalid_choice': _('Object not found: %(value)s'),
     }
     }
 
 
     def to_python(self, value):
     def to_python(self, value):
@@ -61,7 +62,7 @@ class CSVModelChoiceField(forms.ModelChoiceField):
             return super().to_python(value)
             return super().to_python(value)
         except MultipleObjectsReturned:
         except MultipleObjectsReturned:
             raise forms.ValidationError(
             raise forms.ValidationError(
-                f'"{value}" is not a unique value for this field; multiple objects were found'
+                _('"{value}" is not a unique value for this field; multiple objects were found').format(value=value)
             )
             )
 
 
 
 
@@ -70,7 +71,7 @@ class CSVModelMultipleChoiceField(forms.ModelMultipleChoiceField):
     Extends Django's `ModelMultipleChoiceField` to support comma-separated values.
     Extends Django's `ModelMultipleChoiceField` to support comma-separated values.
     """
     """
     default_error_messages = {
     default_error_messages = {
-        'invalid_choice': 'Object not found: %(value)s',
+        'invalid_choice': _('Object not found: %(value)s'),
     }
     }
 
 
     def clean(self, value):
     def clean(self, value):
@@ -93,11 +94,11 @@ class CSVContentTypeField(CSVModelChoiceField):
         try:
         try:
             app_label, model = value.split('.')
             app_label, model = value.split('.')
         except ValueError:
         except ValueError:
-            raise forms.ValidationError(f'Object type must be specified as "<app>.<model>"')
+            raise forms.ValidationError(_('Object type must be specified as "<app>.<model>"'))
         try:
         try:
             return self.queryset.get(app_label=app_label, model=model)
             return self.queryset.get(app_label=app_label, model=model)
         except ObjectDoesNotExist:
         except ObjectDoesNotExist:
-            raise forms.ValidationError(f'Invalid object type')
+            raise forms.ValidationError(_('Invalid object type'))
 
 
 
 
 class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
 class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):

+ 5 - 5
netbox/utilities/forms/fields/expandable.py

@@ -1,7 +1,7 @@
 import re
 import re
 
 
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from utilities.forms.constants import *
 from utilities.forms.constants import *
 from utilities.forms.utils import expand_alphanumeric_pattern, expand_ipaddress_pattern
 from utilities.forms.utils import expand_alphanumeric_pattern, expand_ipaddress_pattern
@@ -21,10 +21,10 @@ class ExpandableNameField(forms.CharField):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
         if not self.help_text:
         if not self.help_text:
-            self.help_text = """
-                Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
-                are not supported (example: <code>[ge,xe]-0/0/[0-9]</code>).
-                """
+            self.help_text = _(
+                "Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range are "
+                "not supported (example: <code>[ge,xe]-0/0/[0-9]</code>)."
+            )
 
 
     def to_python(self, value):
     def to_python(self, value):
         if not value:
         if not value:

+ 13 - 12
netbox/utilities/forms/fields/fields.py

@@ -4,7 +4,7 @@ from django import forms
 from django.db.models import Count
 from django.db.models import Count
 from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
 from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
 from django.templatetags.static import static
 from django.templatetags.static import static
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 from netaddr import AddrFormatError, EUI
 from netaddr import AddrFormatError, EUI
 
 
 from utilities.forms import widgets
 from utilities.forms import widgets
@@ -26,14 +26,14 @@ class CommentField(forms.CharField):
     A textarea with support for Markdown rendering. Exists mostly just to add a standard `help_text`.
     A textarea with support for Markdown rendering. Exists mostly just to add a standard `help_text`.
     """
     """
     widget = widgets.MarkdownWidget
     widget = widgets.MarkdownWidget
-    help_text = f"""
-        <i class="mdi mdi-information-outline"></i>
-        <a href="{static('docs/reference/markdown/')}" target="_blank" tabindex="-1">
-        Markdown</a> syntax is supported
-    """
+    label = _('Comments')
+    help_text = _(
+        '<i class="mdi mdi-information-outline"></i> '
+        '<a href="{url}" target="_blank" tabindex="-1">Markdown</a> syntax is supported'
+    ).format(url=static('docs/reference/markdown/'))
 
 
-    def __init__(self, *, help_text=help_text, required=False, **kwargs):
-        super().__init__(help_text=help_text, required=required, **kwargs)
+    def __init__(self, *, label=label, help_text=help_text, required=False, **kwargs):
+        super().__init__(label=label, help_text=help_text, required=required, **kwargs)
 
 
 
 
 class SlugField(forms.SlugField):
 class SlugField(forms.SlugField):
@@ -44,10 +44,11 @@ class SlugField(forms.SlugField):
         slug_source: Name of the form field from which the slug value will be derived
         slug_source: Name of the form field from which the slug value will be derived
     """
     """
     widget = widgets.SlugWidget
     widget = widgets.SlugWidget
+    label = _('Slug')
     help_text = _("URL-friendly unique shorthand")
     help_text = _("URL-friendly unique shorthand")
 
 
-    def __init__(self, *, slug_source='name', help_text=help_text, **kwargs):
-        super().__init__(help_text=help_text, **kwargs)
+    def __init__(self, *, slug_source='name', label=label, help_text=help_text, **kwargs):
+        super().__init__(label=label, help_text=help_text, **kwargs)
 
 
         self.widget.attrs['slug-source'] = slug_source
         self.widget.attrs['slug-source'] = slug_source
 
 
@@ -77,7 +78,7 @@ class TagFilterField(forms.MultipleChoiceField):
             ]
             ]
 
 
         # Choices are fetched each time the form is initialized
         # 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 LaxURLField(forms.URLField):
 class LaxURLField(forms.URLField):
@@ -113,7 +114,7 @@ class MACAddressField(forms.Field):
     """
     """
     widget = forms.CharField
     widget = forms.CharField
     default_error_messages = {
     default_error_messages = {
-        'invalid': 'MAC address must be in EUI-48 format',
+        'invalid': _('MAC address must be in EUI-48 format'),
     }
     }
 
 
     def to_python(self, value):
     def to_python(self, value):

+ 1 - 1
netbox/virtualization/forms/bulk_create.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from utilities.forms import BootstrapMixin, form_from_model
 from utilities.forms import BootstrapMixin, form_from_model
 from utilities.forms.fields import ExpandableNameField
 from utilities.forms.fields import ExpandableNameField

+ 31 - 11
netbox/virtualization/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
 from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
@@ -25,6 +25,7 @@ __all__ = (
 
 
 class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
 class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -38,6 +39,7 @@ class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
 class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -51,31 +53,38 @@ class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class ClusterBulkEditForm(NetBoxModelBulkEditForm):
 class ClusterBulkEditForm(NetBoxModelBulkEditForm):
     type = DynamicModelChoiceField(
     type = DynamicModelChoiceField(
+        label=_('Type'),
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         required=False
         required=False
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False
         required=False
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(ClusterStatusChoices),
         choices=add_blank_choice(ClusterStatusChoices),
         required=False,
         required=False,
         initial=''
         initial=''
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
+        label=_('Site group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -84,17 +93,16 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Site'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = Cluster
     model = Cluster
     fieldsets = (
     fieldsets = (
         (None, ('type', 'group', 'status', 'tenant', 'description')),
         (None, ('type', 'group', 'status', 'tenant', 'description')),
-        ('Site', ('region', 'site_group', 'site')),
+        (_('Site'), ('region', 'site_group', 'site')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'group', 'site', 'tenant', 'description', 'comments',
         'group', 'site', 'tenant', 'description', 'comments',
@@ -103,15 +111,18 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
 class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(VirtualMachineStatusChoices),
         choices=add_blank_choice(VirtualMachineStatusChoices),
         required=False,
         required=False,
         initial='',
         initial='',
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
     cluster = DynamicModelChoiceField(
     cluster = DynamicModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -119,6 +130,7 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -126,6 +138,7 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=DeviceRole.objects.filter(
         queryset=DeviceRole.objects.filter(
             vm_role=True
             vm_role=True
         ),
         ),
@@ -135,10 +148,12 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
         }
         }
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     platform = DynamicModelChoiceField(
     platform = DynamicModelChoiceField(
+        label=_('Platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False
         required=False
     )
     )
@@ -155,17 +170,16 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Disk (GB)')
         label=_('Disk (GB)')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label=_('Comments')
-    )
+    comments = CommentField()
 
 
     model = VirtualMachine
     model = VirtualMachine
     fieldsets = (
     fieldsets = (
         (None, ('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform', 'description')),
         (None, ('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform', 'description')),
-        ('Resources', ('vcpus', 'memory', 'disk'))
+        (_('Resources'), ('vcpus', 'memory', 'disk'))
     )
     )
     nullable_fields = (
     nullable_fields = (
         'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'description', 'comments',
         'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'description', 'comments',
@@ -174,20 +188,24 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
 class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
     virtual_machine = forms.ModelChoiceField(
     virtual_machine = forms.ModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         required=False,
         required=False,
         disabled=True,
         disabled=True,
         widget=forms.HiddenInput()
         widget=forms.HiddenInput()
     )
     )
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False
         required=False
     )
     )
     bridge = DynamicModelChoiceField(
     bridge = DynamicModelChoiceField(
+        label=_('Bridge'),
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False
         required=False
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
@@ -198,10 +216,12 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
         label=_('MTU')
         label=_('MTU')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=100,
         max_length=100,
         required=False
         required=False
     )
     )
     mode = forms.ChoiceField(
     mode = forms.ChoiceField(
+        label=_('Mode'),
         choices=add_blank_choice(InterfaceModeChoices),
         choices=add_blank_choice(InterfaceModeChoices),
         required=False
         required=False
     )
     )
@@ -235,8 +255,8 @@ class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
     model = VMInterface
     model = VMInterface
     fieldsets = (
     fieldsets = (
         (None, ('mtu', 'enabled', 'vrf', 'description')),
         (None, ('mtu', 'enabled', 'vrf', 'description')),
-        ('Related Interfaces', ('parent', 'bridge')),
-        ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
+        (_('Related Interfaces'), ('parent', 'bridge')),
+        (_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'parent', 'bridge', 'mtu', 'vrf', 'description',
         'parent', 'bridge', 'mtu', 'vrf', 'description',

+ 18 - 1
netbox/virtualization/forms/bulk_import.py

@@ -1,4 +1,4 @@
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import Device, DeviceRole, Platform, Site
 from dcim.models import Device, DeviceRole, Platform, Site
@@ -36,27 +36,32 @@ class ClusterGroupImportForm(NetBoxModelImportForm):
 
 
 class ClusterImportForm(NetBoxModelImportForm):
 class ClusterImportForm(NetBoxModelImportForm):
     type = CSVModelChoiceField(
     type = CSVModelChoiceField(
+        label=_('Type'),
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         to_field_name='name',
         to_field_name='name',
         help_text=_('Type of cluster')
         help_text=_('Type of cluster')
     )
     )
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned cluster group')
         help_text=_('Assigned cluster group')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=ClusterStatusChoices,
         choices=ClusterStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
@@ -70,28 +75,33 @@ class ClusterImportForm(NetBoxModelImportForm):
 
 
 class VirtualMachineImportForm(NetBoxModelImportForm):
 class VirtualMachineImportForm(NetBoxModelImportForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=VirtualMachineStatusChoices,
         choices=VirtualMachineStatusChoices,
         help_text=_('Operational status')
         help_text=_('Operational status')
     )
     )
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned site')
         help_text=_('Assigned site')
     )
     )
     cluster = CSVModelChoiceField(
     cluster = CSVModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned cluster')
         help_text=_('Assigned cluster')
     )
     )
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         to_field_name='name',
         to_field_name='name',
         required=False,
         required=False,
         help_text=_('Assigned device within cluster')
         help_text=_('Assigned device within cluster')
     )
     )
     role = CSVModelChoiceField(
     role = CSVModelChoiceField(
+        label=_('Role'),
         queryset=DeviceRole.objects.filter(
         queryset=DeviceRole.objects.filter(
             vm_role=True
             vm_role=True
         ),
         ),
@@ -100,12 +110,14 @@ class VirtualMachineImportForm(NetBoxModelImportForm):
         help_text=_('Functional role')
         help_text=_('Functional role')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     platform = CSVModelChoiceField(
     platform = CSVModelChoiceField(
+        label=_('Platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -122,27 +134,32 @@ class VirtualMachineImportForm(NetBoxModelImportForm):
 
 
 class VMInterfaceImportForm(NetBoxModelImportForm):
 class VMInterfaceImportForm(NetBoxModelImportForm):
     virtual_machine = CSVModelChoiceField(
     virtual_machine = CSVModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         to_field_name='name'
         to_field_name='name'
     )
     )
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Parent interface')
         help_text=_('Parent interface')
     )
     )
     bridge = CSVModelChoiceField(
     bridge = CSVModelChoiceField(
+        label=_('Bridge'),
         queryset=VMInterface.objects.all(),
         queryset=VMInterface.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Bridged interface')
         help_text=_('Bridged interface')
     )
     )
     mode = CSVChoiceField(
     mode = CSVChoiceField(
+        label=_('Mode'),
         choices=InterfaceModeChoices,
         choices=InterfaceModeChoices,
         required=False,
         required=False,
         help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
         help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
     )
     )
     vrf = CSVModelChoiceField(
     vrf = CSVModelChoiceField(
+        label=_('VRF'),
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         to_field_name='rd',
         to_field_name='rd',

+ 18 - 15
netbox/virtualization/forms/filtersets.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
 from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
 from extras.forms import LocalConfigContextFilterForm
 from extras.forms import LocalConfigContextFilterForm
@@ -30,7 +30,7 @@ class ClusterGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
 
 
 
 
@@ -38,10 +38,10 @@ class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
     model = Cluster
     model = Cluster
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('group_id', 'type_id', 'status')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Attributes'), ('group_id', 'type_id', 'status')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     type_id = DynamicModelMultipleChoiceField(
     type_id = DynamicModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
@@ -54,6 +54,7 @@ class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
         label=_('Region')
         label=_('Region')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=ClusterStatusChoices,
         choices=ClusterStatusChoices,
         required=False
         required=False
     )
     )
@@ -90,11 +91,11 @@ class VirtualMachineFilterForm(
     model = VirtualMachine
     model = VirtualMachine
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')),
-        ('Location', ('region_id', 'site_group_id', 'site_id')),
-        ('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Contacts', ('contact', 'contact_role', 'contact_group')),
+        (_('Cluster'), ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')),
+        (_('Location'), ('region_id', 'site_group_id', 'site_id')),
+        (_('Attributes'), ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     )
     cluster_group_id = DynamicModelMultipleChoiceField(
     cluster_group_id = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
@@ -148,6 +149,7 @@ class VirtualMachineFilterForm(
         label=_('Role')
         label=_('Role')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
+        label=_('Status'),
         choices=VirtualMachineStatusChoices,
         choices=VirtualMachineStatusChoices,
         required=False
         required=False
     )
     )
@@ -175,8 +177,8 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
     model = VMInterface
     model = VMInterface
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Virtual Machine', ('cluster_id', 'virtual_machine_id')),
-        ('Attributes', ('enabled', 'mac_address', 'vrf_id', 'l2vpn_id')),
+        (_('Virtual Machine'), ('cluster_id', 'virtual_machine_id')),
+        (_('Attributes'), ('enabled', 'mac_address', 'vrf_id', 'l2vpn_id')),
     )
     )
     cluster_id = DynamicModelMultipleChoiceField(
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
@@ -192,6 +194,7 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
         label=_('Virtual machine')
         label=_('Virtual machine')
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
+        label=_('Enabled'),
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -199,12 +202,12 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
     )
     )
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         required=False,
         required=False,
-        label='MAC address'
+        label=_('MAC address')
     )
     )
     vrf_id = DynamicModelMultipleChoiceField(
     vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label='VRF'
+        label=_('VRF')
     )
     )
     l2vpn_id = DynamicModelMultipleChoiceField(
     l2vpn_id = DynamicModelMultipleChoiceField(
         queryset=L2VPN.objects.all(),
         queryset=L2VPN.objects.all(),

+ 31 - 17
netbox/virtualization/forms/model_forms.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.forms.common import InterfaceCommonForm
 from dcim.forms.common import InterfaceCommonForm
 from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
 from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
@@ -30,7 +30,7 @@ class ClusterTypeForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Cluster Type', (
+        (_('Cluster Type'), (
             'name', 'slug', 'description', 'tags',
             'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -46,7 +46,7 @@ class ClusterGroupForm(NetBoxModelForm):
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Cluster Group', (
+        (_('Cluster Group'), (
             'name', 'slug', 'description', 'tags',
             'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -60,13 +60,16 @@ class ClusterGroupForm(NetBoxModelForm):
 
 
 class ClusterForm(TenancyForm, NetBoxModelForm):
 class ClusterForm(TenancyForm, NetBoxModelForm):
     type = DynamicModelChoiceField(
     type = DynamicModelChoiceField(
+        label=_('Type'),
         queryset=ClusterType.objects.all()
         queryset=ClusterType.objects.all()
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False
         required=False
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         selector=True
         selector=True
@@ -74,8 +77,8 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Cluster', ('name', 'type', 'group', 'site', 'status', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
+        (_('Cluster'), ('name', 'type', 'group', 'site', 'status', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -87,16 +90,19 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
 
 
 class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
 class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
+        label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
         null_option='None'
         null_option='None'
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
+        label=_('Site group'),
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         null_option='None'
         null_option='None'
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -105,6 +111,7 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
         }
         }
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
+        label=_('Rack'),
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
@@ -113,6 +120,7 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
         }
         }
     )
     )
     devices = DynamicModelMultipleChoiceField(
     devices = DynamicModelMultipleChoiceField(
+        label=_('Devices'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         query_params={
         query_params={
             'site_id': '$site',
             'site_id': '$site',
@@ -142,7 +150,7 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
             for device in self.cleaned_data.get('devices', []):
             for device in self.cleaned_data.get('devices', []):
                 if device.site != self.cluster.site:
                 if device.site != self.cluster.site:
                     raise ValidationError({
                     raise ValidationError({
-                        'devices': "{} belongs to a different site ({}) than the cluster ({})".format(
+                        'devices': _("{} belongs to a different site ({}) than the cluster ({})").format(
                             device, device.site, self.cluster.site
                             device, device.site, self.cluster.site
                         )
                         )
                     })
                     })
@@ -157,10 +165,12 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
 
 
 class VirtualMachineForm(TenancyForm, NetBoxModelForm):
 class VirtualMachineForm(TenancyForm, NetBoxModelForm):
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
+        label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
     cluster = DynamicModelChoiceField(
     cluster = DynamicModelChoiceField(
+        label=_('Cluster'),
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
         selector=True,
         selector=True,
@@ -169,6 +179,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         }
         }
     )
     )
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
+        label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -178,6 +189,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         help_text=_("Optionally pin this VM to a specific host device within the cluster")
         help_text=_("Optionally pin this VM to a specific host device within the cluster")
     )
     )
     role = DynamicModelChoiceField(
     role = DynamicModelChoiceField(
+        label=_('Role'),
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
@@ -185,6 +197,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         }
         }
     )
     )
     platform = DynamicModelChoiceField(
     platform = DynamicModelChoiceField(
+        label=_('Platform'),
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False
         required=False
     )
     )
@@ -195,12 +208,12 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Virtual Machine', ('name', 'role', 'status', 'description', 'tags')),
-        ('Site/Cluster', ('site', 'cluster', 'device')),
-        ('Tenancy', ('tenant_group', 'tenant')),
-        ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
-        ('Resources', ('vcpus', 'memory', 'disk')),
-        ('Config Context', ('local_context_data',)),
+        (_('Virtual Machine'), ('name', 'role', 'status', 'description', 'tags')),
+        (_('Site/Cluster'), ('site', 'cluster', 'device')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
+        (_('Management'), ('platform', 'primary_ip4', 'primary_ip6')),
+        (_('Resources'), ('vcpus', 'memory', 'disk')),
+        (_('Config Context'), ('local_context_data',)),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -253,6 +266,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
 
 
 class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
 class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     virtual_machine = DynamicModelChoiceField(
     virtual_machine = DynamicModelChoiceField(
+        label=_('Virtual machine'),
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         selector=True
         selector=True
     )
     )
@@ -302,11 +316,11 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
     )
     )
 
 
     fieldsets = (
     fieldsets = (
-        ('Interface', ('virtual_machine', 'name', 'description', 'tags')),
-        ('Addressing', ('vrf', 'mac_address')),
-        ('Operation', ('mtu', 'enabled')),
-        ('Related Interfaces', ('parent', 'bridge')),
-        ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
+        (_('Interface'), ('virtual_machine', 'name', 'description', 'tags')),
+        (_('Addressing'), ('vrf', 'mac_address')),
+        (_('Operation'), ('mtu', 'enabled')),
+        (_('Related Interfaces'), ('parent', 'bridge')),
+        (_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
     )
     )
 
 
     class Meta:
     class Meta:

+ 4 - 1
netbox/virtualization/forms/object_create.py

@@ -1,3 +1,4 @@
+from django.utils.translation import gettext_lazy as _
 from utilities.forms.fields import ExpandableNameField
 from utilities.forms.fields import ExpandableNameField
 from .model_forms import VMInterfaceForm
 from .model_forms import VMInterfaceForm
 
 
@@ -7,7 +8,9 @@ __all__ = (
 
 
 
 
 class VMInterfaceCreateForm(VMInterfaceForm):
 class VMInterfaceCreateForm(VMInterfaceForm):
-    name = ExpandableNameField()
+    name = ExpandableNameField(
+        label=_('Name'),
+    )
     replication_fields = ('name',)
     replication_fields = ('name',)
 
 
     class Meta(VMInterfaceForm.Meta):
     class Meta(VMInterfaceForm.Meta):

+ 18 - 9
netbox/wireless/forms/bulk_edit.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from ipam.models import VLAN
 from ipam.models import VLAN
@@ -20,10 +20,12 @@ __all__ = (
 
 
 class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
 class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False
         required=False
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
@@ -37,10 +39,12 @@ class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
 class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(WirelessLANStatusChoices),
         choices=add_blank_choice(WirelessLANStatusChoices),
         required=False
         required=False
     )
     )
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False
         required=False
     )
     )
@@ -55,14 +59,17 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         label=_('SSID')
         label=_('SSID')
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
+        label=_('Authentication type'),
         choices=add_blank_choice(WirelessAuthTypeChoices),
         choices=add_blank_choice(WirelessAuthTypeChoices),
         required=False
         required=False
     )
     )
     auth_cipher = forms.ChoiceField(
     auth_cipher = forms.ChoiceField(
+        label=_('Authentication cipher'),
         choices=add_blank_choice(WirelessAuthCipherChoices),
         choices=add_blank_choice(WirelessAuthCipherChoices),
         required=False
         required=False
     )
     )
@@ -71,17 +78,16 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Pre-shared key')
         label=_('Pre-shared key')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = WirelessLAN
     model = WirelessLAN
     fieldsets = (
     fieldsets = (
         (None, ('group', 'ssid', 'status', 'vlan', 'tenant', 'description')),
         (None, ('group', 'ssid', 'status', 'vlan', 'tenant', 'description')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
         'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
@@ -95,18 +101,22 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         label=_('SSID')
         label=_('SSID')
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         choices=add_blank_choice(LinkStatusChoices),
         choices=add_blank_choice(LinkStatusChoices),
         required=False
         required=False
     )
     )
     tenant = DynamicModelChoiceField(
     tenant = DynamicModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False
         required=False
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
+        label=_('Authentication type'),
         choices=add_blank_choice(WirelessAuthTypeChoices),
         choices=add_blank_choice(WirelessAuthTypeChoices),
         required=False
         required=False
     )
     )
     auth_cipher = forms.ChoiceField(
     auth_cipher = forms.ChoiceField(
+        label=_('Authentication cipher'),
         choices=add_blank_choice(WirelessAuthCipherChoices),
         choices=add_blank_choice(WirelessAuthCipherChoices),
         required=False
         required=False
     )
     )
@@ -115,17 +125,16 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         label=_('Pre-shared key')
         label=_('Pre-shared key')
     )
     )
     description = forms.CharField(
     description = forms.CharField(
+        label=_('Description'),
         max_length=200,
         max_length=200,
         required=False
         required=False
     )
     )
-    comments = CommentField(
-        label='Comments'
-    )
+    comments = CommentField()
 
 
     model = WirelessLink
     model = WirelessLink
     fieldsets = (
     fieldsets = (
         (None, ('ssid', 'status', 'tenant', 'description')),
         (None, ('ssid', 'status', 'tenant', 'description')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk'))
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk'))
     )
     )
     nullable_fields = (
     nullable_fields = (
         'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
         'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',

+ 14 - 1
netbox/wireless/forms/bulk_import.py

@@ -1,4 +1,4 @@
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from dcim.models import Interface
 from dcim.models import Interface
@@ -18,6 +18,7 @@ __all__ = (
 
 
 class WirelessLANGroupImportForm(NetBoxModelImportForm):
 class WirelessLANGroupImportForm(NetBoxModelImportForm):
     parent = CSVModelChoiceField(
     parent = CSVModelChoiceField(
+        label=_('Parent'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
@@ -32,33 +33,39 @@ class WirelessLANGroupImportForm(NetBoxModelImportForm):
 
 
 class WirelessLANImportForm(NetBoxModelImportForm):
 class WirelessLANImportForm(NetBoxModelImportForm):
     group = CSVModelChoiceField(
     group = CSVModelChoiceField(
+        label=_('Group'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned group')
         help_text=_('Assigned group')
     )
     )
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=WirelessLANStatusChoices,
         choices=WirelessLANStatusChoices,
         help_text='Operational status'
         help_text='Operational status'
     )
     )
     vlan = CSVModelChoiceField(
     vlan = CSVModelChoiceField(
+        label=_('VLAN'),
         queryset=VLAN.objects.all(),
         queryset=VLAN.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Bridged VLAN')
         help_text=_('Bridged VLAN')
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
+        label=_('Authentication type'),
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
         help_text=_('Authentication type')
         help_text=_('Authentication type')
     )
     )
     auth_cipher = CSVChoiceField(
     auth_cipher = CSVChoiceField(
+        label=_('Authentication cipher'),
         choices=WirelessAuthCipherChoices,
         choices=WirelessAuthCipherChoices,
         required=False,
         required=False,
         help_text=_('Authentication cipher')
         help_text=_('Authentication cipher')
@@ -74,27 +81,33 @@ class WirelessLANImportForm(NetBoxModelImportForm):
 
 
 class WirelessLinkImportForm(NetBoxModelImportForm):
 class WirelessLinkImportForm(NetBoxModelImportForm):
     status = CSVChoiceField(
     status = CSVChoiceField(
+        label=_('Status'),
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
         help_text=_('Connection status')
         help_text=_('Connection status')
     )
     )
     interface_a = CSVModelChoiceField(
     interface_a = CSVModelChoiceField(
+        label=_('Interface A'),
         queryset=Interface.objects.all()
         queryset=Interface.objects.all()
     )
     )
     interface_b = CSVModelChoiceField(
     interface_b = CSVModelChoiceField(
+        label=_('Interface B'),
         queryset=Interface.objects.all()
         queryset=Interface.objects.all()
     )
     )
     tenant = CSVModelChoiceField(
     tenant = CSVModelChoiceField(
+        label=_('Tenant'),
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
         to_field_name='name',
         to_field_name='name',
         help_text=_('Assigned tenant')
         help_text=_('Assigned tenant')
     )
     )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
+        label=_('Authentication type'),
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
         help_text=_('Authentication type')
         help_text=_('Authentication type')
     )
     )
     auth_cipher = CSVChoiceField(
     auth_cipher = CSVChoiceField(
+        label=_('Authentication cipher'),
         choices=WirelessAuthCipherChoices,
         choices=WirelessAuthCipherChoices,
         required=False,
         required=False,
         help_text=_('Authentication cipher')
         help_text=_('Authentication cipher')

+ 15 - 7
netbox/wireless/forms/filtersets.py

@@ -1,5 +1,5 @@
 from django import forms
 from django import forms
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from netbox.forms import NetBoxModelFilterSetForm
 from netbox.forms import NetBoxModelFilterSetForm
@@ -30,9 +30,9 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = WirelessLAN
     model = WirelessLAN
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('ssid', 'group_id', 'status')),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+        (_('Attributes'), ('ssid', 'group_id', 'status')),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
         required=False,
         required=False,
@@ -45,18 +45,22 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Group')
         label=_('Group')
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         required=False,
         required=False,
         choices=add_blank_choice(WirelessLANStatusChoices)
         choices=add_blank_choice(WirelessLANStatusChoices)
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
+        label=_('Authentication type'),
         required=False,
         required=False,
         choices=add_blank_choice(WirelessAuthTypeChoices)
         choices=add_blank_choice(WirelessAuthTypeChoices)
     )
     )
     auth_cipher = forms.ChoiceField(
     auth_cipher = forms.ChoiceField(
+        label=_('Authentication cipher'),
         required=False,
         required=False,
         choices=add_blank_choice(WirelessAuthCipherChoices)
         choices=add_blank_choice(WirelessAuthCipherChoices)
     )
     )
     auth_psk = forms.CharField(
     auth_psk = forms.CharField(
+        label=_('Pre-shared key'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
@@ -66,27 +70,31 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = WirelessLink
     model = WirelessLink
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (None, ('q', 'filter_id', 'tag')),
-        ('Attributes', ('ssid', 'status',)),
-        ('Tenant', ('tenant_group_id', 'tenant_id')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+        (_('Attributes'), ('ssid', 'status',)),
+        (_('Tenant'), ('tenant_group_id', 'tenant_id')),
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
         required=False,
         required=False,
         label=_('SSID')
         label=_('SSID')
     )
     )
     status = forms.ChoiceField(
     status = forms.ChoiceField(
+        label=_('Status'),
         required=False,
         required=False,
         choices=add_blank_choice(LinkStatusChoices)
         choices=add_blank_choice(LinkStatusChoices)
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
+        label=_('Authentication type'),
         required=False,
         required=False,
         choices=add_blank_choice(WirelessAuthTypeChoices)
         choices=add_blank_choice(WirelessAuthTypeChoices)
     )
     )
     auth_cipher = forms.ChoiceField(
     auth_cipher = forms.ChoiceField(
+        label=_('Authentication cipher'),
         required=False,
         required=False,
         choices=add_blank_choice(WirelessAuthCipherChoices)
         choices=add_blank_choice(WirelessAuthCipherChoices)
     )
     )
     auth_psk = forms.CharField(
     auth_psk = forms.CharField(
+        label=_('Pre-shared key'),
         required=False
         required=False
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)

+ 12 - 10
netbox/wireless/forms/model_forms.py

@@ -1,5 +1,5 @@
 from django.forms import PasswordInput
 from django.forms import PasswordInput
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
 
 
 from dcim.models import Device, Interface, Location, Site
 from dcim.models import Device, Interface, Location, Site
 from ipam.models import VLAN
 from ipam.models import VLAN
@@ -17,13 +17,14 @@ __all__ = (
 
 
 class WirelessLANGroupForm(NetBoxModelForm):
 class WirelessLANGroupForm(NetBoxModelForm):
     parent = DynamicModelChoiceField(
     parent = DynamicModelChoiceField(
+        label=_('Parent'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False
         required=False
     )
     )
     slug = SlugField()
     slug = SlugField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Wireless LAN Group', (
+        (_('Wireless LAN Group'), (
             'parent', 'name', 'slug', 'description', 'tags',
             'parent', 'name', 'slug', 'description', 'tags',
         )),
         )),
     )
     )
@@ -37,6 +38,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
 
 
 class WirelessLANForm(TenancyForm, NetBoxModelForm):
 class WirelessLANForm(TenancyForm, NetBoxModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
+        label=_('Group'),
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False
         required=False
     )
     )
@@ -49,9 +51,9 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Wireless LAN', ('ssid', 'group', 'vlan', 'status', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+        (_('Wireless LAN'), ('ssid', 'group', 'vlan', 'status', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -152,11 +154,11 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
     comments = CommentField()
 
 
     fieldsets = (
     fieldsets = (
-        ('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
-        ('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
-        ('Link', ('status', 'ssid', 'description', 'tags')),
-        ('Tenancy', ('tenant_group', 'tenant')),
-        ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+        (_('Side A'), ('site_a', 'location_a', 'device_a', 'interface_a')),
+        (_('Side B'), ('site_b', 'location_b', 'device_b', 'interface_b')),
+        (_('Link'), ('status', 'ssid', 'description', 'tags')),
+        (_('Tenancy'), ('tenant_group', 'tenant')),
+        (_('Authentication'), ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
 
 
     class Meta:
     class Meta:

Некоторые файлы не были показаны из-за большого количества измененных файлов