Feature: Add chinese translation to db and view

Add chinese translation code to netbox

Signed-off-by: yj.bai <bai.yongjun@99cloud.net>
This commit is contained in:
yj.bai 2020-08-05 19:24:32 +08:00
parent 9c3b67048a
commit e1441537ef
48 changed files with 2916 additions and 892 deletions

View File

@ -1,9 +1,10 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext as _
class CircuitsConfig(AppConfig): class CircuitsConfig(AppConfig):
name = "circuits" name = "circuits"
verbose_name = "Circuits" verbose_name = _('Circuits')
def ready(self): def ready(self):
import circuits.signals import circuits.signals

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _
from dcim.models import Region, Site from dcim.models import Region, Site
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
@ -21,31 +22,31 @@ __all__ = (
class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='circuits__terminations__site__region', field_name='circuits__terminations__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='circuits__terminations__site__region', field_name='circuits__terminations__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='circuits__terminations__site', field_name='circuits__terminations__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site', label=_('Site'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='circuits__terminations__site__slug', field_name='circuits__terminations__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -75,27 +76,27 @@ class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet): class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
provider_id = django_filters.ModelMultipleChoiceFilter( provider_id = django_filters.ModelMultipleChoiceFilter(
queryset=Provider.objects.all(), queryset=Provider.objects.all(),
label='Provider (ID)', label=_('Provider (ID)'),
) )
provider = django_filters.ModelMultipleChoiceFilter( provider = django_filters.ModelMultipleChoiceFilter(
field_name='provider__slug', field_name='provider__slug',
queryset=Provider.objects.all(), queryset=Provider.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Provider (slug)', label=_('Provider (slug)'),
) )
type_id = django_filters.ModelMultipleChoiceFilter( type_id = django_filters.ModelMultipleChoiceFilter(
queryset=CircuitType.objects.all(), queryset=CircuitType.objects.all(),
label='Circuit type (ID)', label=_('Circuit type (ID)'),
) )
type = django_filters.ModelMultipleChoiceFilter( type = django_filters.ModelMultipleChoiceFilter(
field_name='type__slug', field_name='type__slug',
queryset=CircuitType.objects.all(), queryset=CircuitType.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Circuit type (slug)', label=_('Circuit type (slug)'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=CircuitStatusChoices, choices=CircuitStatusChoices,
@ -104,26 +105,26 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site', field_name='terminations__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site__slug', field_name='terminations__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='terminations__site__region', field_name='terminations__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='terminations__site__region', field_name='terminations__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -147,21 +148,21 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
class CircuitTerminationFilterSet(BaseFilterSet): class CircuitTerminationFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
circuit_id = django_filters.ModelMultipleChoiceFilter( circuit_id = django_filters.ModelMultipleChoiceFilter(
queryset=Circuit.objects.all(), queryset=Circuit.objects.all(),
label='Circuit', label=_('Circuit'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
class Meta: class Meta:

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.utils.translation import gettext as _
from dcim.models import Region, Site from dcim.models import Region, Site
from extras.forms import ( from extras.forms import (
@ -64,30 +65,30 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi
) )
asn = forms.IntegerField( asn = forms.IntegerField(
required=False, required=False,
label='ASN' label=_('ASN')
) )
account = forms.CharField( account = forms.CharField(
max_length=30, max_length=30,
required=False, required=False,
label='Account number' label=_('Account number')
) )
portal_url = forms.URLField( portal_url = forms.URLField(
required=False, required=False,
label='Portal' label=_('Portal')
) )
noc_contact = forms.CharField( noc_contact = forms.CharField(
required=False, required=False,
widget=SmallTextarea, widget=SmallTextarea,
label='NOC contact' label=_('NOC contact')
) )
admin_contact = forms.CharField( admin_contact = forms.CharField(
required=False, required=False,
widget=SmallTextarea, widget=SmallTextarea,
label='Admin contact' label=_('Admin contact')
) )
comments = CommentField( comments = CommentField(
widget=SmallTextarea, widget=SmallTextarea,
label='Comments' label=_('Comments')
) )
class Meta: class Meta:
@ -100,7 +101,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Provider model = Provider
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
region = DynamicModelMultipleChoiceField( region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -123,7 +124,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
) )
asn = forms.IntegerField( asn = forms.IntegerField(
required=False, required=False,
label='ASN' label=_('ASN')
) )
tag = TagFilterField(model) tag = TagFilterField(model)
@ -149,7 +150,7 @@ class CircuitTypeCSVForm(CSVModelForm):
model = CircuitType model = CircuitType
fields = CircuitType.csv_headers fields = CircuitType.csv_headers
help_texts = { help_texts = {
'name': 'Name of circuit type', 'name': _('Name of circuit type'),
} }
@ -189,23 +190,23 @@ class CircuitCSVForm(CustomFieldModelCSVForm):
provider = CSVModelChoiceField( provider = CSVModelChoiceField(
queryset=Provider.objects.all(), queryset=Provider.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned provider' help_text=_('Assigned provider')
) )
type = CSVModelChoiceField( type = CSVModelChoiceField(
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(
choices=CircuitStatusChoices, choices=CircuitStatusChoices,
required=False, required=False,
help_text='Operational status' help_text=_('Operational status')
) )
tenant = CSVModelChoiceField( tenant = CSVModelChoiceField(
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')
) )
class Meta: class Meta:
@ -240,7 +241,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
) )
commit_rate = forms.IntegerField( commit_rate = forms.IntegerField(
required=False, required=False,
label='Commit rate (Kbps)' label=_('Commit rate (Kbps)')
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -248,7 +249,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
) )
comments = CommentField( comments = CommentField(
widget=SmallTextarea, widget=SmallTextarea,
label='Comments' label=_('Comments')
) )
class Meta: class Meta:
@ -264,7 +265,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
type = DynamicModelMultipleChoiceField( type = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(), queryset=CircuitType.objects.all(),
@ -309,7 +310,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
commit_rate = forms.IntegerField( commit_rate = forms.IntegerField(
required=False, required=False,
min_value=0, min_value=0,
label='Commit rate (Kbps)' label=_('Commit rate (Kbps)')
) )
tag = TagFilterField(model) tag = TagFilterField(model)

View File

@ -1,6 +1,7 @@
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.constants import CONNECTION_STATUS_CHOICES from dcim.constants import CONNECTION_STATUS_CHOICES
@ -38,25 +39,25 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
asn = ASNField( asn = ASNField(
blank=True, blank=True,
null=True, null=True,
verbose_name='ASN', verbose_name=_('ASN'),
help_text='32-bit autonomous system number' help_text=_('32-bit autonomous system number')
) )
account = models.CharField( account = models.CharField(
max_length=30, max_length=30,
blank=True, blank=True,
verbose_name='Account number' verbose_name=_('Account number')
) )
portal_url = models.URLField( portal_url = models.URLField(
blank=True, blank=True,
verbose_name='Portal URL' verbose_name=_('Portal URL')
) )
noc_contact = models.TextField( noc_contact = models.TextField(
blank=True, blank=True,
verbose_name='NOC contact' verbose_name=_('NOC contact')
) )
admin_contact = models.TextField( admin_contact = models.TextField(
blank=True, blank=True,
verbose_name='Admin contact' verbose_name=_('Admin contact')
) )
comments = models.TextField( comments = models.TextField(
blank=True blank=True
@ -143,7 +144,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
""" """
cid = models.CharField( cid = models.CharField(
max_length=50, max_length=50,
verbose_name='Circuit ID' verbose_name=_('Circuit ID')
) )
provider = models.ForeignKey( provider = models.ForeignKey(
to='circuits.Provider', to='circuits.Provider',
@ -170,7 +171,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
install_date = models.DateField( install_date = models.DateField(
blank=True, blank=True,
null=True, null=True,
verbose_name='Date installed' verbose_name=_('Date installed')
) )
commit_rate = models.PositiveIntegerField( commit_rate = models.PositiveIntegerField(
blank=True, blank=True,
@ -258,7 +259,7 @@ class CircuitTermination(CableTermination):
term_side = models.CharField( term_side = models.CharField(
max_length=1, max_length=1,
choices=CircuitTerminationSideChoices, choices=CircuitTerminationSideChoices,
verbose_name='Termination' verbose_name=_('Termination')
) )
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
@ -277,23 +278,23 @@ class CircuitTermination(CableTermination):
blank=True blank=True
) )
port_speed = models.PositiveIntegerField( port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)' verbose_name=_('Port speed (Kbps)')
) )
upstream_speed = models.PositiveIntegerField( upstream_speed = models.PositiveIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name='Upstream speed (Kbps)', verbose_name=_('Upstream speed (Kbps)'),
help_text='Upstream speed, if different from port speed' help_text=_('Upstream speed, if different from port speed')
) )
xconnect_id = models.CharField( xconnect_id = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='Cross-connect ID' verbose_name=_('Cross-connect ID')
) )
pp_info = models.CharField( pp_info = models.CharField(
max_length=100, max_length=100,
blank=True, blank=True,
verbose_name='Patch panel/port(s)' verbose_name=_('Patch panel/port(s)')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,

View File

@ -1,5 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from django.utils.translation import gettext as _
from tenancy.tables import COL_TENANT from tenancy.tables import COL_TENANT
from utilities.tables import BaseTable, TagColumn, ToggleColumn from utilities.tables import BaseTable, TagColumn, ToggleColumn
@ -29,7 +30,7 @@ class ProviderTable(BaseTable):
name = tables.LinkColumn() name = tables.LinkColumn()
circuit_count = tables.Column( circuit_count = tables.Column(
accessor=Accessor('count_circuits'), accessor=Accessor('count_circuits'),
verbose_name='Circuits' verbose_name=_('Circuits')
) )
tags = TagColumn( tags = TagColumn(
url_name='circuits:provider_list' url_name='circuits:provider_list'
@ -51,7 +52,7 @@ class CircuitTypeTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
circuit_count = tables.Column( circuit_count = tables.Column(
verbose_name='Circuits' verbose_name=_('Circuits')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=CIRCUITTYPE_ACTIONS, template_code=CIRCUITTYPE_ACTIONS,
@ -72,7 +73,7 @@ class CircuitTypeTable(BaseTable):
class CircuitTable(BaseTable): class CircuitTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
cid = tables.LinkColumn( cid = tables.LinkColumn(
verbose_name='ID' verbose_name=_('ID')
) )
provider = tables.LinkColumn( provider = tables.LinkColumn(
viewname='circuits:provider', viewname='circuits:provider',
@ -85,10 +86,10 @@ class CircuitTable(BaseTable):
template_code=COL_TENANT template_code=COL_TENANT
) )
a_side = tables.Column( a_side = tables.Column(
verbose_name='A Side' verbose_name=_('A Side')
) )
z_side = tables.Column( z_side = tables.Column(
verbose_name='Z Side' verbose_name=_('Z Side')
) )
tags = TagColumn( tags = TagColumn(
url_name='circuits:circuit_list' url_name='circuits:circuit_list'

View File

@ -5,6 +5,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db import transaction from django.db import transaction
from django.db.models import Count, OuterRef, Subquery from django.db.models import Count, OuterRef, Subquery
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import gettext as _
from django.views.generic import View from django.views.generic import View
from django_tables2 import RequestConfig from django_tables2 import RequestConfig
@ -250,7 +251,7 @@ def circuit_terminations_swap(request, pk):
else: else:
termination_z.term_side = 'A' termination_z.term_side = 'A'
termination_z.save() termination_z.save()
messages.success(request, "Swapped terminations for circuit {}.".format(circuit)) messages.success(request, _('Swapped terminations for circuit {}.').format(circuit))
return redirect('circuits:circuit', pk=circuit.pk) return redirect('circuits:circuit', pk=circuit.pk)
else: else:

View File

@ -1,9 +1,9 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext as _
class DCIMConfig(AppConfig): class DCIMConfig(AppConfig):
name = "dcim" name = "dcim"
verbose_name = "DCIM" verbose_name = _('DCIM')
def ready(self): def ready(self):

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
@ -63,13 +64,13 @@ __all__ = (
class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Parent region (ID)', label=_('Parent region (ID)'),
) )
parent = django_filters.ModelMultipleChoiceFilter( parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug', field_name='parent__slug',
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent region (slug)', label=_('Parent region (slug)'),
) )
class Meta: class Meta:
@ -80,7 +81,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=SiteStatusChoices, choices=SiteStatusChoices,
@ -90,14 +91,14 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='region', field_name='region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='region', field_name='region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -134,34 +135,34 @@ class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Rack group (ID)', label=_('Rack group (ID)'),
) )
parent = django_filters.ModelMultipleChoiceFilter( parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug', field_name='parent__slug',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Rack group (slug)', label=_('Rack group (slug)'),
) )
class Meta: class Meta:
@ -179,43 +180,43 @@ class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
group_id = TreeNodeMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='group', field_name='group',
lookup_expr='in', lookup_expr='in',
label='Rack group (ID)', label=_('Rack group (ID)'),
) )
group = TreeNodeMultipleChoiceFilter( group = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='group', field_name='group',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Rack group (slug)', label=_('Rack group (slug)'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=RackStatusChoices, choices=RackStatusChoices,
@ -223,13 +224,13 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug', field_name='role__slug',
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
serial = django_filters.CharFilter( serial = django_filters.CharFilter(
lookup_expr='iexact' lookup_expr='iexact'
@ -258,45 +259,45 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat
class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet): class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
rack_id = django_filters.ModelMultipleChoiceFilter( rack_id = django_filters.ModelMultipleChoiceFilter(
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
label='Rack (ID)', label=_('Rack (ID)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='rack__site', field_name='rack__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='rack__site__slug', field_name='rack__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
group_id = TreeNodeMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='rack__group', field_name='rack__group',
lookup_expr='in', lookup_expr='in',
label='Rack group (ID)', label=_('Rack group (ID)'),
) )
group = TreeNodeMultipleChoiceFilter( group = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='rack__group', field_name='rack__group',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Rack group (slug)', label=_('Rack group (slug)'),
) )
user_id = django_filters.ModelMultipleChoiceFilter( user_id = django_filters.ModelMultipleChoiceFilter(
queryset=User.objects.all(), queryset=User.objects.all(),
label='User (ID)', label=_('User (ID)'),
) )
user = django_filters.ModelMultipleChoiceFilter( user = django_filters.ModelMultipleChoiceFilter(
field_name='user', field_name='user',
queryset=User.objects.all(), queryset=User.objects.all(),
to_field_name='username', to_field_name='username',
label='User (name)', label=_('User (name)'),
) )
class Meta: class Meta:
@ -324,45 +325,45 @@ class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
manufacturer_id = django_filters.ModelMultipleChoiceFilter( manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)', label=_('Manufacturer (ID)'),
) )
manufacturer = django_filters.ModelMultipleChoiceFilter( manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug', field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Manufacturer (slug)', label=_('Manufacturer (slug)'),
) )
console_ports = django_filters.BooleanFilter( console_ports = django_filters.BooleanFilter(
method='_console_ports', method='_console_ports',
label='Has console ports', label=_('Has console ports'),
) )
console_server_ports = django_filters.BooleanFilter( console_server_ports = django_filters.BooleanFilter(
method='_console_server_ports', method='_console_server_ports',
label='Has console server ports', label=_('Has console server ports'),
) )
power_ports = django_filters.BooleanFilter( power_ports = django_filters.BooleanFilter(
method='_power_ports', method='_power_ports',
label='Has power ports', label=_('Has power ports'),
) )
power_outlets = django_filters.BooleanFilter( power_outlets = django_filters.BooleanFilter(
method='_power_outlets', method='_power_outlets',
label='Has power outlets', label=_('Has power outlets'),
) )
interfaces = django_filters.BooleanFilter( interfaces = django_filters.BooleanFilter(
method='_interfaces', method='_interfaces',
label='Has interfaces', label=_('Has interfaces'),
) )
pass_through_ports = django_filters.BooleanFilter( pass_through_ports = django_filters.BooleanFilter(
method='_pass_through_ports', method='_pass_through_ports',
label='Has pass-through ports', label=_('Has pass-through ports'),
) )
device_bays = django_filters.BooleanFilter( device_bays = django_filters.BooleanFilter(
method='_device_bays', method='_device_bays',
label='Has device bays', label=_('Has device bays'),
) )
tag = TagFilter() tag = TagFilter()
@ -411,7 +412,7 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
devicetype_id = django_filters.ModelMultipleChoiceFilter( devicetype_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
field_name='device_type_id', field_name='device_type_id',
label='Device type (ID)', label=_('Device type (ID)'),
) )
@ -482,13 +483,13 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
manufacturer_id = django_filters.ModelMultipleChoiceFilter( manufacturer_id = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer', field_name='manufacturer',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)', label=_('Manufacturer (ID)'),
) )
manufacturer = django_filters.ModelMultipleChoiceFilter( manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug', field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Manufacturer (slug)', label=_('Manufacturer (slug)'),
) )
class Meta: class Meta:
@ -505,87 +506,87 @@ class DeviceFilterSet(
): ):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
manufacturer_id = django_filters.ModelMultipleChoiceFilter( manufacturer_id = django_filters.ModelMultipleChoiceFilter(
field_name='device_type__manufacturer', field_name='device_type__manufacturer',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)', label=_('Manufacturer (ID)'),
) )
manufacturer = django_filters.ModelMultipleChoiceFilter( manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='device_type__manufacturer__slug', field_name='device_type__manufacturer__slug',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Manufacturer (slug)', label=_('Manufacturer (slug)'),
) )
device_type_id = django_filters.ModelMultipleChoiceFilter( device_type_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
label='Device type (ID)', label=_('Device type (ID)'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device_role_id', field_name='device_role_id',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='device_role__slug', field_name='device_role__slug',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
platform_id = django_filters.ModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label=_('Platform (ID)'),
) )
platform = django_filters.ModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
field_name='platform__slug', field_name='platform__slug',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label=_('Platform (slug)'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
rack_group_id = TreeNodeMultipleChoiceFilter( rack_group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='rack__group', field_name='rack__group',
lookup_expr='in', lookup_expr='in',
label='Rack group (ID)', label=_('Rack group (ID)'),
) )
rack_id = django_filters.ModelMultipleChoiceFilter( rack_id = django_filters.ModelMultipleChoiceFilter(
field_name='rack', field_name='rack',
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
label='Rack (ID)', label=_('Rack (ID)'),
) )
cluster_id = django_filters.ModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='VM cluster (ID)', label=_('VM cluster (ID)'),
) )
model = django_filters.ModelMultipleChoiceFilter( model = django_filters.ModelMultipleChoiceFilter(
field_name='device_type__slug', field_name='device_type__slug',
queryset=DeviceType.objects.all(), queryset=DeviceType.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Device model (slug)', label=_('Device model (slug)'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=DeviceStatusChoices, choices=DeviceStatusChoices,
@ -593,55 +594,55 @@ class DeviceFilterSet(
) )
is_full_depth = django_filters.BooleanFilter( is_full_depth = django_filters.BooleanFilter(
field_name='device_type__is_full_depth', field_name='device_type__is_full_depth',
label='Is full depth', label=_('Is full depth'),
) )
mac_address = MultiValueMACAddressFilter( mac_address = MultiValueMACAddressFilter(
field_name='interfaces__mac_address', field_name='interfaces__mac_address',
label='MAC address', label=_('MAC address'),
) )
serial = django_filters.CharFilter( serial = django_filters.CharFilter(
lookup_expr='iexact' lookup_expr='iexact'
) )
has_primary_ip = django_filters.BooleanFilter( has_primary_ip = django_filters.BooleanFilter(
method='_has_primary_ip', method='_has_primary_ip',
label='Has a primary IP', label=_('Has a primary IP'),
) )
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter( virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_chassis', field_name='virtual_chassis',
queryset=VirtualChassis.objects.all(), queryset=VirtualChassis.objects.all(),
label='Virtual chassis (ID)', label=_('Virtual chassis (ID)'),
) )
virtual_chassis_member = django_filters.BooleanFilter( virtual_chassis_member = django_filters.BooleanFilter(
method='_virtual_chassis_member', method='_virtual_chassis_member',
label='Is a virtual chassis member' label=_('Is a virtual chassis member')
) )
console_ports = django_filters.BooleanFilter( console_ports = django_filters.BooleanFilter(
method='_console_ports', method='_console_ports',
label='Has console ports', label=_('Has console ports'),
) )
console_server_ports = django_filters.BooleanFilter( console_server_ports = django_filters.BooleanFilter(
method='_console_server_ports', method='_console_server_ports',
label='Has console server ports', label=_('Has console server ports'),
) )
power_ports = django_filters.BooleanFilter( power_ports = django_filters.BooleanFilter(
method='_power_ports', method='_power_ports',
label='Has power ports', label=_('Has power ports'),
) )
power_outlets = django_filters.BooleanFilter( power_outlets = django_filters.BooleanFilter(
method='_power_outlets', method='_power_outlets',
label='Has power outlets', label=_('Has power outlets'),
) )
interfaces = django_filters.BooleanFilter( interfaces = django_filters.BooleanFilter(
method='_interfaces', method='_interfaces',
label='Has interfaces', label=_('Has interfaces'),
) )
pass_through_ports = django_filters.BooleanFilter( pass_through_ports = django_filters.BooleanFilter(
method='_pass_through_ports', method='_pass_through_ports',
label='Has pass-through ports', label=_('Has pass-through ports'),
) )
device_bays = django_filters.BooleanFilter( device_bays = django_filters.BooleanFilter(
method='_device_bays', method='_device_bays',
label='Has device bays', label=_('Has device bays'),
) )
tag = TagFilter() tag = TagFilter()
@ -703,41 +704,41 @@ class DeviceFilterSet(
class DeviceComponentFilterSet(django_filters.FilterSet): class DeviceComponentFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region', field_name='device__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region', field_name='device__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__site', field_name='device__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='device__site__slug', field_name='device__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
device_id = django_filters.ModelMultipleChoiceFilter( device_id = django_filters.ModelMultipleChoiceFilter(
queryset=Device.objects.all(), queryset=Device.objects.all(),
label='Device (ID)', label=_('Device (ID)'),
) )
device = django_filters.ModelMultipleChoiceFilter( device = django_filters.ModelMultipleChoiceFilter(
field_name='device__name', field_name='device__name',
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
label='Device (name)', label=_('Device (name)'),
) )
tag = TagFilter() tag = TagFilter()
@ -817,19 +818,19 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet):
class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet): class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
# Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis # Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis
# members # members
device = MultiValueCharFilter( device = MultiValueCharFilter(
method='filter_device', method='filter_device',
field_name='name', field_name='name',
label='Device', label=_('Device'),
) )
device_id = MultiValueNumberFilter( device_id = MultiValueNumberFilter(
method='filter_device_id', method='filter_device_id',
field_name='pk', field_name='pk',
label='Device (ID)', label=_('Device (ID)'),
) )
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
field_name='cable', field_name='cable',
@ -838,22 +839,22 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet):
) )
kind = django_filters.CharFilter( kind = django_filters.CharFilter(
method='filter_kind', method='filter_kind',
label='Kind of interface', label=_('Kind of interface'),
) )
lag_id = django_filters.ModelMultipleChoiceFilter( lag_id = django_filters.ModelMultipleChoiceFilter(
field_name='lag', field_name='lag',
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
label='LAG interface (ID)', label=_('LAG interface (ID)'),
) )
mac_address = MultiValueMACAddressFilter() mac_address = MultiValueMACAddressFilter()
tag = TagFilter() tag = TagFilter()
vlan_id = django_filters.CharFilter( vlan_id = django_filters.CharFilter(
method='filter_vlan_id', method='filter_vlan_id',
label='Assigned VLAN' label=_('Assigned VLAN')
) )
vlan = django_filters.CharFilter( vlan = django_filters.CharFilter(
method='filter_vlan', method='filter_vlan',
label='Assigned VID' label=_('Assigned VID')
) )
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=InterfaceTypeChoices, choices=InterfaceTypeChoices,
@ -946,54 +947,54 @@ class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet):
class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region', field_name='device__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='device__site__region', field_name='device__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__site', field_name='device__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='device__site__slug', field_name='device__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
device_id = django_filters.ModelChoiceFilter( device_id = django_filters.ModelChoiceFilter(
queryset=Device.objects.all(), queryset=Device.objects.all(),
label='Device (ID)', label=_('Device (ID)'),
) )
device = django_filters.ModelChoiceFilter( device = django_filters.ModelChoiceFilter(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
label='Device (name)', label=_('Device (name)'),
) )
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(), queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)', label=_('Parent inventory item (ID)'),
) )
manufacturer_id = django_filters.ModelMultipleChoiceFilter( manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)', label=_('Manufacturer (ID)'),
) )
manufacturer = django_filters.ModelMultipleChoiceFilter( manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug', field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Manufacturer (slug)', label=_('Manufacturer (slug)'),
) )
serial = django_filters.CharFilter( serial = django_filters.CharFilter(
lookup_expr='iexact' lookup_expr='iexact'
@ -1019,42 +1020,42 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
class VirtualChassisFilterSet(BaseFilterSet): class VirtualChassisFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='master__site__region', field_name='master__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='master__site__region', field_name='master__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='master__site', field_name='master__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='master__site__slug', field_name='master__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
field_name='master__tenant', field_name='master__tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label=_('Tenant (ID)'),
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
field_name='master__tenant__slug', field_name='master__tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label=_('Tenant (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -1075,7 +1076,7 @@ class VirtualChassisFilterSet(BaseFilterSet):
class CableFilterSet(BaseFilterSet): class CableFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=CableTypeChoices choices=CableTypeChoices
@ -1138,7 +1139,7 @@ class CableFilterSet(BaseFilterSet):
class ConsoleConnectionFilterSet(BaseFilterSet): class ConsoleConnectionFilterSet(BaseFilterSet):
site = django_filters.CharFilter( site = django_filters.CharFilter(
method='filter_site', method='filter_site',
label='Site (slug)', label=_('Site (slug)'),
) )
device_id = MultiValueNumberFilter( device_id = MultiValueNumberFilter(
method='filter_device' method='filter_device'
@ -1169,7 +1170,7 @@ class ConsoleConnectionFilterSet(BaseFilterSet):
class PowerConnectionFilterSet(BaseFilterSet): class PowerConnectionFilterSet(BaseFilterSet):
site = django_filters.CharFilter( site = django_filters.CharFilter(
method='filter_site', method='filter_site',
label='Site (slug)', label=_('Site (slug)'),
) )
device_id = MultiValueNumberFilter( device_id = MultiValueNumberFilter(
method='filter_device' method='filter_device'
@ -1200,7 +1201,7 @@ class PowerConnectionFilterSet(BaseFilterSet):
class InterfaceConnectionFilterSet(BaseFilterSet): class InterfaceConnectionFilterSet(BaseFilterSet):
site = django_filters.CharFilter( site = django_filters.CharFilter(
method='filter_site', method='filter_site',
label='Site (slug)', label=_('Site (slug)'),
) )
device_id = MultiValueNumberFilter( device_id = MultiValueNumberFilter(
method='filter_device' method='filter_device'
@ -1234,36 +1235,36 @@ class InterfaceConnectionFilterSet(BaseFilterSet):
class PowerPanelFilterSet(BaseFilterSet): class PowerPanelFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
rack_group_id = TreeNodeMultipleChoiceFilter( rack_group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
field_name='rack_group', field_name='rack_group',
lookup_expr='in', lookup_expr='in',
label='Rack group (ID)', label=_('Rack group (ID)'),
) )
class Meta: class Meta:
@ -1282,40 +1283,40 @@ class PowerPanelFilterSet(BaseFilterSet):
class PowerFeedFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class PowerFeedFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='power_panel__site__region', field_name='power_panel__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='power_panel__site__region', field_name='power_panel__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='power_panel__site', field_name='power_panel__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='power_panel__site__slug', field_name='power_panel__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site name (slug)', label=_('Site name (slug)'),
) )
power_panel_id = django_filters.ModelMultipleChoiceFilter( power_panel_id = django_filters.ModelMultipleChoiceFilter(
queryset=PowerPanel.objects.all(), queryset=PowerPanel.objects.all(),
label='Power panel (ID)', label=_('Power panel (ID)'),
) )
rack_id = django_filters.ModelMultipleChoiceFilter( rack_id = django_filters.ModelMultipleChoiceFilter(
field_name='rack', field_name='rack',
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
label='Rack (ID)', label=_('Rack (ID)'),
) )
tag = TagFilter() tag = TagFilter()

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ from django.db import models
from django.db.models import Count, F, ProtectedError, Sum from django.db.models import Count, F, ProtectedError, Sum
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from timezone_field import TimeZoneField from timezone_field import TimeZoneField
@ -182,13 +183,13 @@ class Site(ChangeLoggedModel, CustomFieldModel):
facility = models.CharField( facility = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
help_text='Local facility ID or description' help_text=_('Local facility ID or description')
) )
asn = ASNField( asn = ASNField(
blank=True, blank=True,
null=True, null=True,
verbose_name='ASN', verbose_name=_('ASN'),
help_text='32-bit autonomous system number' help_text=_('32-bit autonomous system number')
) )
time_zone = TimeZoneField( time_zone = TimeZoneField(
blank=True blank=True
@ -210,14 +211,14 @@ class Site(ChangeLoggedModel, CustomFieldModel):
decimal_places=6, decimal_places=6,
blank=True, blank=True,
null=True, null=True,
help_text='GPS coordinate (latitude)' help_text=_('GPS coordinate (latitude)')
) )
longitude = models.DecimalField( longitude = models.DecimalField(
max_digits=9, max_digits=9,
decimal_places=6, decimal_places=6,
blank=True, blank=True,
null=True, null=True,
help_text='GPS coordinate (longitude)' help_text=_('GPS coordinate (longitude)')
) )
contact_name = models.CharField( contact_name = models.CharField(
max_length=50, max_length=50,
@ -229,7 +230,7 @@ class Site(ChangeLoggedModel, CustomFieldModel):
) )
contact_email = models.EmailField( contact_email = models.EmailField(
blank=True, blank=True,
verbose_name='Contact E-mail' verbose_name=_('Contact E-mail')
) )
comments = models.TextField( comments = models.TextField(
blank=True blank=True
@ -428,8 +429,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
max_length=50, max_length=50,
blank=True, blank=True,
null=True, null=True,
verbose_name='Facility ID', verbose_name=_('Facility ID'),
help_text='Locally-assigned identifier' help_text=_('Locally-assigned identifier')
) )
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
@ -442,7 +443,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
related_name='racks', related_name='racks',
blank=True, blank=True,
null=True, null=True,
help_text='Assigned group' help_text=_('Assigned group')
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -462,53 +463,53 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
related_name='racks', related_name='racks',
blank=True, blank=True,
null=True, null=True,
help_text='Functional role' help_text=_('Functional role')
) )
serial = models.CharField( serial = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='Serial number' verbose_name=_('Serial number')
) )
asset_tag = models.CharField( asset_tag = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
null=True, null=True,
unique=True, unique=True,
verbose_name='Asset tag', verbose_name=_('Asset tag'),
help_text='A unique tag used to identify this rack' help_text=_('A unique tag used to identify this rack')
) )
type = models.CharField( type = models.CharField(
choices=RackTypeChoices, choices=RackTypeChoices,
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='Type' verbose_name=_('Type')
) )
width = models.PositiveSmallIntegerField( width = models.PositiveSmallIntegerField(
choices=RackWidthChoices, choices=RackWidthChoices,
default=RackWidthChoices.WIDTH_19IN, default=RackWidthChoices.WIDTH_19IN,
verbose_name='Width', verbose_name=_('Width'),
help_text='Rail-to-rail width' help_text=_('Rail-to-rail width')
) )
u_height = models.PositiveSmallIntegerField( u_height = models.PositiveSmallIntegerField(
default=RACK_U_HEIGHT_DEFAULT, default=RACK_U_HEIGHT_DEFAULT,
verbose_name='Height (U)', verbose_name=_('Height (U)'),
validators=[MinValueValidator(1), MaxValueValidator(100)], validators=[MinValueValidator(1), MaxValueValidator(100)],
help_text='Height in rack units' help_text=_('Height in rack units')
) )
desc_units = models.BooleanField( desc_units = models.BooleanField(
default=False, default=False,
verbose_name='Descending units', verbose_name=_('Descending units'),
help_text='Units are numbered top-to-bottom' help_text=_('Units are numbered top-to-bottom')
) )
outer_width = models.PositiveSmallIntegerField( outer_width = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
help_text='Outer dimension of rack (width)' help_text=_('Outer dimension of rack (width)')
) )
outer_depth = models.PositiveSmallIntegerField( outer_depth = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
help_text='Outer dimension of rack (depth)' help_text=_('Outer dimension of rack (depth)')
) )
outer_unit = models.CharField( outer_unit = models.CharField(
max_length=50, max_length=50,
@ -949,24 +950,24 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
part_number = models.CharField( part_number = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
help_text='Discrete part number (optional)' help_text=_('Discrete part number (optional)')
) )
u_height = models.PositiveSmallIntegerField( u_height = models.PositiveSmallIntegerField(
default=1, default=1,
verbose_name='Height (U)' verbose_name=_('Height (U)')
) )
is_full_depth = models.BooleanField( is_full_depth = models.BooleanField(
default=True, default=True,
verbose_name='Is full depth', verbose_name=_('Is full depth'),
help_text='Device consumes both front and rear rack faces' help_text=_('Device consumes both front and rear rack faces')
) )
subdevice_role = models.CharField( subdevice_role = models.CharField(
max_length=50, max_length=50,
choices=SubdeviceRoleChoices, choices=SubdeviceRoleChoices,
blank=True, blank=True,
verbose_name='Parent/child status', verbose_name=_('Parent/child status'),
help_text='Parent devices house child devices in device bays. Leave blank ' help_text=_('Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.' 'if this device type is neither a parent nor a child.')
) )
front_image = models.ImageField( front_image = models.ImageField(
upload_to='devicetype-images', upload_to='devicetype-images',
@ -1200,8 +1201,8 @@ class DeviceRole(ChangeLoggedModel):
) )
vm_role = models.BooleanField( vm_role = models.BooleanField(
default=True, default=True,
verbose_name='VM Role', verbose_name=_('VM Role'),
help_text='Virtual machines may be assigned to this role' help_text=_('Virtual machines may be assigned to this role')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -1246,19 +1247,19 @@ class Platform(ChangeLoggedModel):
related_name='platforms', related_name='platforms',
blank=True, blank=True,
null=True, null=True,
help_text='Optionally limit this platform to devices of a certain manufacturer' help_text=_('Optionally limit this platform to devices of a certain manufacturer')
) )
napalm_driver = models.CharField( napalm_driver = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='NAPALM driver', verbose_name=_('NAPALM driver'),
help_text='The name of the NAPALM driver to use when interacting with devices' help_text=_('The name of the NAPALM driver to use when interacting with devices')
) )
napalm_args = JSONField( napalm_args = JSONField(
blank=True, blank=True,
null=True, null=True,
verbose_name='NAPALM arguments', verbose_name=_('NAPALM arguments'),
help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)' help_text=_('Additional arguments to pass when initiating the NAPALM driver (JSON format)')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -1338,15 +1339,15 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
serial = models.CharField( serial = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='Serial number' verbose_name=_('Serial number')
) )
asset_tag = models.CharField( asset_tag = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
null=True, null=True,
unique=True, unique=True,
verbose_name='Asset tag', verbose_name=_('Asset tag'),
help_text='A unique tag used to identify this device' help_text=_('A unique tag used to identify this device')
) )
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
@ -1364,14 +1365,14 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
verbose_name='Position (U)', verbose_name=_('Position (U)'),
help_text='The lowest-numbered unit occupied by the device' help_text=_('The lowest-numbered unit occupied by the device')
) )
face = models.CharField( face = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
choices=DeviceFaceChoices, choices=DeviceFaceChoices,
verbose_name='Rack face' verbose_name=_('Rack face')
) )
status = models.CharField( status = models.CharField(
max_length=50, max_length=50,
@ -1384,7 +1385,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
related_name='primary_ip4_for', related_name='primary_ip4_for',
blank=True, blank=True,
null=True, null=True,
verbose_name='Primary IPv4' verbose_name=_('Primary IPv4')
) )
primary_ip6 = models.OneToOneField( primary_ip6 = models.OneToOneField(
to='ipam.IPAddress', to='ipam.IPAddress',
@ -1392,7 +1393,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
related_name='primary_ip6_for', related_name='primary_ip6_for',
blank=True, blank=True,
null=True, null=True,
verbose_name='Primary IPv6' verbose_name=_('Primary IPv6')
) )
cluster = models.ForeignKey( cluster = models.ForeignKey(
to='virtualization.Cluster', to='virtualization.Cluster',
@ -1904,7 +1905,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
max_utilization = models.PositiveSmallIntegerField( max_utilization = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)], validators=[MinValueValidator(1), MaxValueValidator(100)],
default=POWERFEED_MAX_UTILIZATION_DEFAULT, default=POWERFEED_MAX_UTILIZATION_DEFAULT,
help_text="Maximum permissible draw (percentage)" help_text=_('Maximum permissible draw (percentage)')
) )
available_power = models.PositiveIntegerField( available_power = models.PositiveIntegerField(
default=0, default=0,

View File

@ -1,6 +1,7 @@
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.translation import gettext as _
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
@ -154,13 +155,13 @@ class PowerPortTemplate(ComponentTemplateModel):
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text="Maximum power draw (watts)" help_text=_('Maximum power draw (watts)')
) )
allocated_draw = models.PositiveSmallIntegerField( allocated_draw = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text="Allocated power draw (watts)" help_text=_('Allocated power draw (watts)')
) )
class Meta: class Meta:
@ -213,7 +214,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
max_length=50, max_length=50,
choices=PowerOutletFeedLegChoices, choices=PowerOutletFeedLegChoices,
blank=True, blank=True,
help_text="Phase (for three-phase feeds)" help_text=_('Phase (for three-phase feeds)')
) )
class Meta: class Meta:
@ -269,7 +270,7 @@ class InterfaceTemplate(ComponentTemplateModel):
) )
mgmt_only = models.BooleanField( mgmt_only = models.BooleanField(
default=False, default=False,
verbose_name='Management only' verbose_name=_('Management only')
) )
class Meta: class Meta:

View File

@ -6,6 +6,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import Sum from django.db.models import Sum
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.choices import * from dcim.choices import *
@ -252,7 +253,7 @@ class ConsolePort(CableTermination, ComponentModel):
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
blank=True, blank=True,
help_text='Physical port type' help_text=_('Physical port type')
) )
connected_endpoint = models.OneToOneField( connected_endpoint = models.OneToOneField(
to='dcim.ConsoleServerPort', to='dcim.ConsoleServerPort',
@ -311,7 +312,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
blank=True, blank=True,
help_text='Physical port type' help_text=_('Physical port type')
) )
connection_status = models.NullBooleanField( connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
@ -363,19 +364,19 @@ class PowerPort(CableTermination, ComponentModel):
max_length=50, max_length=50,
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
blank=True, blank=True,
help_text='Physical port type' help_text=_('Physical port type')
) )
maximum_draw = models.PositiveSmallIntegerField( maximum_draw = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text="Maximum power draw (watts)" help_text=_('Maximum power draw (watts)')
) )
allocated_draw = models.PositiveSmallIntegerField( allocated_draw = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
help_text="Allocated power draw (watts)" help_text=_('Allocated power draw (watts)')
) )
_connected_poweroutlet = models.OneToOneField( _connected_poweroutlet = models.OneToOneField(
to='dcim.PowerOutlet', to='dcim.PowerOutlet',
@ -523,7 +524,7 @@ class PowerOutlet(CableTermination, ComponentModel):
max_length=50, max_length=50,
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
blank=True, blank=True,
help_text='Physical port type' help_text=_('Physical port type')
) )
power_port = models.ForeignKey( power_port = models.ForeignKey(
to='dcim.PowerPort', to='dcim.PowerPort',
@ -536,7 +537,7 @@ class PowerOutlet(CableTermination, ComponentModel):
max_length=50, max_length=50,
choices=PowerOutletFeedLegChoices, choices=PowerOutletFeedLegChoices,
blank=True, blank=True,
help_text="Phase (for three-phase feeds)" help_text=_('Phase (for three-phase feeds)')
) )
connection_status = models.NullBooleanField( connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
@ -629,7 +630,7 @@ class Interface(CableTermination, ComponentModel):
related_name='member_interfaces', related_name='member_interfaces',
null=True, null=True,
blank=True, blank=True,
verbose_name='Parent LAG' verbose_name=_('Parent LAG')
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
@ -641,18 +642,18 @@ class Interface(CableTermination, ComponentModel):
mac_address = MACAddressField( mac_address = MACAddressField(
null=True, null=True,
blank=True, blank=True,
verbose_name='MAC Address' verbose_name=_('MAC Address')
) )
mtu = models.PositiveIntegerField( mtu = models.PositiveIntegerField(
blank=True, blank=True,
null=True, null=True,
validators=[MinValueValidator(1), MaxValueValidator(65536)], validators=[MinValueValidator(1), MaxValueValidator(65536)],
verbose_name='MTU' verbose_name=_('MTU')
) )
mgmt_only = models.BooleanField( mgmt_only = models.BooleanField(
default=False, default=False,
verbose_name='OOB Management', verbose_name=_('OOB Management'),
help_text='This interface is used only for out-of-band management' help_text=_('This interface is used only for out-of-band management')
) )
mode = models.CharField( mode = models.CharField(
max_length=50, max_length=50,
@ -665,13 +666,13 @@ class Interface(CableTermination, ComponentModel):
related_name='interfaces_as_untagged', related_name='interfaces_as_untagged',
null=True, null=True,
blank=True, blank=True,
verbose_name='Untagged VLAN' verbose_name=_('Untagged VLAN')
) )
tagged_vlans = models.ManyToManyField( tagged_vlans = models.ManyToManyField(
to='ipam.VLAN', to='ipam.VLAN',
related_name='interfaces_as_tagged', related_name='interfaces_as_tagged',
blank=True, blank=True,
verbose_name='Tagged VLANs' verbose_name=_('Tagged VLANs')
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
@ -976,7 +977,7 @@ class DeviceBay(ComponentModel):
) )
name = models.CharField( name = models.CharField(
max_length=50, max_length=50,
verbose_name='Name' verbose_name=_('Name')
) )
_name = NaturalOrderingField( _name = NaturalOrderingField(
target_field='name', target_field='name',
@ -1056,7 +1057,7 @@ class InventoryItem(ComponentModel):
) )
name = models.CharField( name = models.CharField(
max_length=50, max_length=50,
verbose_name='Name' verbose_name=_('Name')
) )
_name = NaturalOrderingField( _name = NaturalOrderingField(
target_field='name', target_field='name',
@ -1072,13 +1073,13 @@ class InventoryItem(ComponentModel):
) )
part_id = models.CharField( part_id = models.CharField(
max_length=50, max_length=50,
verbose_name='Part ID', verbose_name=_('Part ID'),
blank=True, blank=True,
help_text='Manufacturer-assigned part identifier' help_text=_('Manufacturer-assigned part identifier')
) )
serial = models.CharField( serial = models.CharField(
max_length=50, max_length=50,
verbose_name='Serial number', verbose_name=_('Serial number'),
blank=True blank=True
) )
asset_tag = models.CharField( asset_tag = models.CharField(
@ -1086,12 +1087,12 @@ class InventoryItem(ComponentModel):
unique=True, unique=True,
blank=True, blank=True,
null=True, null=True,
verbose_name='Asset tag', verbose_name=_('Asset tag'),
help_text='A unique tag used to identify this item' help_text=_('A unique tag used to identify this item')
) )
discovered = models.BooleanField( discovered = models.BooleanField(
default=False, default=False,
help_text='This item was automatically discovered' help_text=_('This item was automatically discovered')
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)

View File

@ -1,5 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from django.utils.translation import gettext as _
from tenancy.tables import COL_TENANT from tenancy.tables import COL_TENANT
from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ColoredLabelColumn, TagColumn, ToggleColumn
@ -196,7 +197,7 @@ class RegionTable(BaseTable):
orderable=False orderable=False
) )
site_count = tables.Column( site_count = tables.Column(
verbose_name='Sites' verbose_name=_('Sites')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=REGION_ACTIONS, template_code=REGION_ACTIONS,
@ -255,10 +256,10 @@ class RackGroupTable(BaseTable):
site = tables.LinkColumn( site = tables.LinkColumn(
viewname='dcim:site', viewname='dcim:site',
args=[Accessor('site.slug')], args=[Accessor('site.slug')],
verbose_name='Site' verbose_name=_('Site')
) )
rack_count = tables.Column( rack_count = tables.Column(
verbose_name='Racks' verbose_name=_('Racks')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=RACKGROUP_ACTIONS, template_code=RACKGROUP_ACTIONS,
@ -315,7 +316,7 @@ class RackTable(BaseTable):
role = ColoredLabelColumn() role = ColoredLabelColumn()
u_height = tables.TemplateColumn( u_height = tables.TemplateColumn(
template_code="{{ record.u_height }}U", template_code="{{ record.u_height }}U",
verbose_name='Height' verbose_name=_('Height')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -330,17 +331,17 @@ class RackTable(BaseTable):
class RackDetailTable(RackTable): class RackDetailTable(RackTable):
device_count = tables.TemplateColumn( device_count = tables.TemplateColumn(
template_code=RACK_DEVICE_COUNT, template_code=RACK_DEVICE_COUNT,
verbose_name='Devices' verbose_name=_('Devices')
) )
get_utilization = tables.TemplateColumn( get_utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH, template_code=UTILIZATION_GRAPH,
orderable=False, orderable=False,
verbose_name='Space' verbose_name=_('Space')
) )
get_power_utilization = tables.TemplateColumn( get_power_utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH, template_code=UTILIZATION_GRAPH,
orderable=False, orderable=False,
verbose_name='Power' verbose_name=_('Power')
) )
tags = TagColumn( tags = TagColumn(
url_name='dcim:rack_list' url_name='dcim:rack_list'
@ -382,7 +383,7 @@ class RackReservationTable(BaseTable):
) )
unit_list = tables.Column( unit_list = tables.Column(
orderable=False, orderable=False,
verbose_name='Units' verbose_name=_('Units')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=RACKRESERVATION_ACTIONS, template_code=RACKRESERVATION_ACTIONS,
@ -408,13 +409,13 @@ class ManufacturerTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
devicetype_count = tables.Column( devicetype_count = tables.Column(
verbose_name='Device Types' verbose_name=_('Device Types')
) )
inventoryitem_count = tables.Column( inventoryitem_count = tables.Column(
verbose_name='Inventory Items' verbose_name=_('Inventory Items')
) )
platform_count = tables.Column( platform_count = tables.Column(
verbose_name='Platforms' verbose_name=_('Platforms')
) )
slug = tables.Column() slug = tables.Column()
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
@ -439,14 +440,14 @@ class DeviceTypeTable(BaseTable):
model = tables.LinkColumn( model = tables.LinkColumn(
viewname='dcim:devicetype', viewname='dcim:devicetype',
args=[Accessor('pk')], args=[Accessor('pk')],
verbose_name='Device Type' verbose_name=_('Device Type')
) )
is_full_depth = BooleanColumn( is_full_depth = BooleanColumn(
verbose_name='Full Depth' verbose_name=_('Full Depth')
) )
instance_count = tables.TemplateColumn( instance_count = tables.TemplateColumn(
template_code=DEVICETYPE_INSTANCES_TEMPLATE, template_code=DEVICETYPE_INSTANCES_TEMPLATE,
verbose_name='Instances' verbose_name=_('Instances')
) )
tags = TagColumn( tags = TagColumn(
url_name='dcim:devicetype_list' url_name='dcim:devicetype_list'
@ -608,7 +609,7 @@ class InterfaceImportTable(BaseTable):
virtual_machine = tables.LinkColumn( virtual_machine = tables.LinkColumn(
viewname='virtualization:virtualmachine', viewname='virtualization:virtualmachine',
args=[Accessor('virtual_machine.pk')], args=[Accessor('virtual_machine.pk')],
verbose_name='Virtual Machine' verbose_name=_('Virtual Machine')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -626,7 +627,7 @@ class FrontPortTemplateTable(BaseTable):
order_by=('_name',) order_by=('_name',)
) )
rear_port_position = tables.Column( rear_port_position = tables.Column(
verbose_name='Position' verbose_name=_('Position')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=get_component_template_actions('frontporttemplate'), template_code=get_component_template_actions('frontporttemplate'),
@ -706,15 +707,15 @@ class DeviceRoleTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
device_count = tables.TemplateColumn( device_count = tables.TemplateColumn(
template_code=DEVICEROLE_DEVICE_COUNT, template_code=DEVICEROLE_DEVICE_COUNT,
verbose_name='Devices' verbose_name=_('Devices')
) )
vm_count = tables.TemplateColumn( vm_count = tables.TemplateColumn(
template_code=DEVICEROLE_VM_COUNT, template_code=DEVICEROLE_VM_COUNT,
verbose_name='VMs' verbose_name=_('VMs')
) )
color = tables.TemplateColumn( color = tables.TemplateColumn(
template_code=COLOR_LABEL, template_code=COLOR_LABEL,
verbose_name='Label' verbose_name=_('Label')
) )
vm_role = BooleanColumn() vm_role = BooleanColumn()
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
@ -737,11 +738,11 @@ class PlatformTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
device_count = tables.TemplateColumn( device_count = tables.TemplateColumn(
template_code=PLATFORM_DEVICE_COUNT, template_code=PLATFORM_DEVICE_COUNT,
verbose_name='Devices' verbose_name=_('Devices')
) )
vm_count = tables.TemplateColumn( vm_count = tables.TemplateColumn(
template_code=PLATFORM_VM_COUNT, template_code=PLATFORM_VM_COUNT,
verbose_name='VMs' verbose_name=_('VMs')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=PLATFORM_ACTIONS, template_code=PLATFORM_ACTIONS,
@ -785,28 +786,28 @@ class DeviceTable(BaseTable):
args=[Accessor('rack.pk')] args=[Accessor('rack.pk')]
) )
device_role = ColoredLabelColumn( device_role = ColoredLabelColumn(
verbose_name='Role' verbose_name=_('Role')
) )
device_type = tables.LinkColumn( device_type = tables.LinkColumn(
viewname='dcim:devicetype', viewname='dcim:devicetype',
args=[Accessor('device_type.pk')], args=[Accessor('device_type.pk')],
verbose_name='Type', verbose_name=_('Type'),
text=lambda record: record.device_type.display_name text=lambda record: record.device_type.display_name
) )
primary_ip = tables.TemplateColumn( primary_ip = tables.TemplateColumn(
template_code=DEVICE_PRIMARY_IP, template_code=DEVICE_PRIMARY_IP,
orderable=False, orderable=False,
verbose_name='IP Address' verbose_name=_('IP Address')
) )
primary_ip4 = tables.LinkColumn( primary_ip4 = tables.LinkColumn(
viewname='ipam:ipaddress', viewname='ipam:ipaddress',
args=[Accessor('primary_ip4.pk')], args=[Accessor('primary_ip4.pk')],
verbose_name='IPv4 Address' verbose_name=_('IPv4 Address')
) )
primary_ip6 = tables.LinkColumn( primary_ip6 = tables.LinkColumn(
viewname='ipam:ipaddress', viewname='ipam:ipaddress',
args=[Accessor('primary_ip6.pk')], args=[Accessor('primary_ip6.pk')],
verbose_name='IPv6 Address' verbose_name=_('IPv6 Address')
) )
cluster = tables.LinkColumn( cluster = tables.LinkColumn(
viewname='virtualization:cluster', viewname='virtualization:cluster',
@ -817,10 +818,10 @@ class DeviceTable(BaseTable):
args=[Accessor('virtual_chassis.pk')] args=[Accessor('virtual_chassis.pk')]
) )
vc_position = tables.Column( vc_position = tables.Column(
verbose_name='VC Position' verbose_name=_('VC Position')
) )
vc_priority = tables.Column( vc_priority = tables.Column(
verbose_name='VC Priority' verbose_name=_('VC Priority')
) )
tags = TagColumn( tags = TagColumn(
url_name='dcim:device_list' url_name='dcim:device_list'
@ -857,10 +858,10 @@ class DeviceImportTable(BaseTable):
args=[Accessor('rack.pk')] args=[Accessor('rack.pk')]
) )
device_role = tables.Column( device_role = tables.Column(
verbose_name='Role' verbose_name=_('Role')
) )
device_type = tables.Column( device_type = tables.Column(
verbose_name='Type' verbose_name=_('Type')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -1031,29 +1032,29 @@ class CableTable(BaseTable):
id = tables.LinkColumn( id = tables.LinkColumn(
viewname='dcim:cable', viewname='dcim:cable',
args=[Accessor('pk')], args=[Accessor('pk')],
verbose_name='ID' verbose_name=_('ID')
) )
termination_a_parent = tables.TemplateColumn( termination_a_parent = tables.TemplateColumn(
template_code=CABLE_TERMINATION_PARENT, template_code=CABLE_TERMINATION_PARENT,
accessor=Accessor('termination_a'), accessor=Accessor('termination_a'),
orderable=False, orderable=False,
verbose_name='Side A' verbose_name=_('Side A')
) )
termination_a = tables.LinkColumn( termination_a = tables.LinkColumn(
accessor=Accessor('termination_a'), accessor=Accessor('termination_a'),
orderable=False, orderable=False,
verbose_name='Termination A' verbose_name=_('Termination A')
) )
termination_b_parent = tables.TemplateColumn( termination_b_parent = tables.TemplateColumn(
template_code=CABLE_TERMINATION_PARENT, template_code=CABLE_TERMINATION_PARENT,
accessor=Accessor('termination_b'), accessor=Accessor('termination_b'),
orderable=False, orderable=False,
verbose_name='Side B' verbose_name=_('Side B')
) )
termination_b = tables.LinkColumn( termination_b = tables.LinkColumn(
accessor=Accessor('termination_b'), accessor=Accessor('termination_b'),
orderable=False, orderable=False,
verbose_name='Termination B' verbose_name=_('Termination B')
) )
status = tables.TemplateColumn( status = tables.TemplateColumn(
template_code=STATUS_LABEL template_code=STATUS_LABEL
@ -1085,17 +1086,17 @@ class ConsoleConnectionTable(BaseTable):
viewname='dcim:device', viewname='dcim:device',
accessor=Accessor('connected_endpoint.device'), accessor=Accessor('connected_endpoint.device'),
args=[Accessor('connected_endpoint.device.pk')], args=[Accessor('connected_endpoint.device.pk')],
verbose_name='Console Server' verbose_name=_('Console Server')
) )
connected_endpoint = tables.Column( connected_endpoint = tables.Column(
verbose_name='Port' verbose_name=_('Port')
) )
device = tables.LinkColumn( device = tables.LinkColumn(
viewname='dcim:device', viewname='dcim:device',
args=[Accessor('device.pk')] args=[Accessor('device.pk')]
) )
name = tables.Column( name = tables.Column(
verbose_name='Console Port' verbose_name=_('Console Port')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -1109,18 +1110,18 @@ class PowerConnectionTable(BaseTable):
accessor=Accessor('connected_endpoint.device'), accessor=Accessor('connected_endpoint.device'),
args=[Accessor('connected_endpoint.device.pk')], args=[Accessor('connected_endpoint.device.pk')],
order_by='_connected_poweroutlet__device', order_by='_connected_poweroutlet__device',
verbose_name='PDU' verbose_name=_('PDU')
) )
outlet = tables.Column( outlet = tables.Column(
accessor=Accessor('_connected_poweroutlet'), accessor=Accessor('_connected_poweroutlet'),
verbose_name='Outlet' verbose_name=_('Outlet')
) )
device = tables.LinkColumn( device = tables.LinkColumn(
viewname='dcim:device', viewname='dcim:device',
args=[Accessor('device.pk')] args=[Accessor('device.pk')]
) )
name = tables.Column( name = tables.Column(
verbose_name='Power Port' verbose_name=_('Power Port')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -1133,25 +1134,25 @@ class InterfaceConnectionTable(BaseTable):
viewname='dcim:device', viewname='dcim:device',
accessor=Accessor('device'), accessor=Accessor('device'),
args=[Accessor('device.pk')], args=[Accessor('device.pk')],
verbose_name='Device A' verbose_name=_('Device A')
) )
interface_a = tables.LinkColumn( interface_a = tables.LinkColumn(
viewname='dcim:interface', viewname='dcim:interface',
accessor=Accessor('name'), accessor=Accessor('name'),
args=[Accessor('pk')], args=[Accessor('pk')],
verbose_name='Interface A' verbose_name=_('Interface A')
) )
device_b = tables.LinkColumn( device_b = tables.LinkColumn(
viewname='dcim:device', viewname='dcim:device',
accessor=Accessor('_connected_interface.device'), accessor=Accessor('_connected_interface.device'),
args=[Accessor('_connected_interface.device.pk')], args=[Accessor('_connected_interface.device.pk')],
verbose_name='Device B' verbose_name=_('Device B')
) )
interface_b = tables.LinkColumn( interface_b = tables.LinkColumn(
viewname='dcim:interface', viewname='dcim:interface',
accessor=Accessor('_connected_interface'), accessor=Accessor('_connected_interface'),
args=[Accessor('_connected_interface.pk')], args=[Accessor('_connected_interface.pk')],
verbose_name='Interface B' verbose_name=_('Interface B')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -1195,7 +1196,7 @@ class VirtualChassisTable(BaseTable):
linkify=True linkify=True
) )
member_count = tables.Column( member_count = tables.Column(
verbose_name='Members' verbose_name=_('Members')
) )
tags = TagColumn( tags = TagColumn(
url_name='dcim:virtualchassis_list' url_name='dcim:virtualchassis_list'
@ -1220,7 +1221,7 @@ class PowerPanelTable(BaseTable):
) )
powerfeed_count = tables.TemplateColumn( powerfeed_count = tables.TemplateColumn(
template_code=POWERPANEL_POWERFEED_COUNT, template_code=POWERPANEL_POWERFEED_COUNT,
verbose_name='Feeds' verbose_name=_('Feeds')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -1254,7 +1255,7 @@ class PowerFeedTable(BaseTable):
template_code="{{ value }}%" template_code="{{ value }}%"
) )
available_power = tables.Column( available_power = tables.Column(
verbose_name='Available power (VA)' verbose_name=_('Available power (VA)')
) )
tags = TagColumn( tags = TagColumn(
url_name='dcim:powerfeed_list' url_name='dcim:powerfeed_list'

View File

@ -14,6 +14,7 @@ from django.urls import reverse
from django.utils.html import escape from django.utils.html import escape
from django.utils.http import is_safe_url from django.utils.http import is_safe_url
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django.views.generic import View from django.views.generic import View
from circuits.models import Circuit from circuits.models import Circuit
@ -74,7 +75,7 @@ class BulkRenameView(GetReturnURLMixin, View):
for obj in selected_objects: for obj in selected_objects:
obj.name = obj.new_name obj.name = obj.new_name
obj.save() obj.save()
messages.success(request, "Renamed {} {}".format( messages.success(request, _('Renamed {} {}').format(
len(selected_objects), len(selected_objects),
model._meta.verbose_name_plural model._meta.verbose_name_plural
)) ))
@ -119,7 +120,7 @@ class BulkDisconnectView(GetReturnURLMixin, View):
obj.cable.delete() obj.cable.delete()
count += 1 count += 1
messages.success(request, "Disconnected {} {}".format( messages.success(request, _('Disconnected {} {}').format(
count, self.model._meta.verbose_name_plural count, self.model._meta.verbose_name_plural
)) ))
@ -1853,7 +1854,7 @@ class DeviceBayPopulateView(PermissionRequiredMixin, View):
device_bay.installed_device = form.cleaned_data['installed_device'] device_bay.installed_device = form.cleaned_data['installed_device']
device_bay.save() device_bay.save()
messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay)) messages.success(request, _('Added {} to {}.').format(device_bay.installed_device, device_bay))
return redirect('dcim:device', pk=device_bay.device.pk) return redirect('dcim:device', pk=device_bay.device.pk)
@ -1888,7 +1889,7 @@ class DeviceBayDepopulateView(PermissionRequiredMixin, View):
removed_device = device_bay.installed_device removed_device = device_bay.installed_device
device_bay.installed_device = None device_bay.installed_device = None
device_bay.save() device_bay.save()
messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay)) messages.success(request, _('{} has been removed from {}.').format(removed_device, device_bay))
return redirect('dcim:device', pk=device_bay.device.pk) return redirect('dcim:device', pk=device_bay.device.pk)

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext as _
from utilities.forms import LaxURLField from utilities.forms import LaxURLField
from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, ReportResult, Webhook from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, ReportResult, Webhook
@ -20,7 +21,7 @@ def order_content_types(field):
class WebhookForm(forms.ModelForm): class WebhookForm(forms.ModelForm):
payload_url = LaxURLField( payload_url = LaxURLField(
label='URL' label=_('URL')
) )
class Meta: class Meta:
@ -116,11 +117,11 @@ class CustomLinkForm(forms.ModelForm):
'url': forms.Textarea, 'url': forms.Textarea,
} }
help_texts = { help_texts = {
'weight': 'A numeric weight to influence the ordering of this link among its peers. Lower weights appear ' 'weight': _('A numeric weight to influence the ordering of this link among its peers. Lower weights appear '
'first in a list.', 'first in a list.'),
'text': 'Jinja2 template code for the link text. Reference the object as <code>{{ obj }}</code>. Links ' 'text': _('Jinja2 template code for the link text. Reference the object as <code>{{ obj }}</code>. Links '
'which render as empty text will not be displayed.', 'which render as empty text will not be displayed.'),
'url': 'Jinja2 template code for the link URL. Reference the object as <code>{{ obj }}</code>.', 'url': _('Jinja2 template code for the link URL. Reference the object as <code>{{ obj }}</code>.'),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -1,6 +1,7 @@
import django_filters import django_filters
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 dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
@ -107,7 +108,7 @@ class ExportTemplateFilterSet(BaseFilterSet):
class TagFilterSet(BaseFilterSet): class TagFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
class Meta: class Meta:
@ -126,95 +127,95 @@ class TagFilterSet(BaseFilterSet):
class ConfigContextFilterSet(BaseFilterSet): class ConfigContextFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = django_filters.ModelMultipleChoiceFilter( region_id = django_filters.ModelMultipleChoiceFilter(
field_name='regions', field_name='regions',
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Region', label=_('Region'),
) )
region = django_filters.ModelMultipleChoiceFilter( region = django_filters.ModelMultipleChoiceFilter(
field_name='regions__slug', field_name='regions__slug',
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='sites', field_name='sites',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site', label=_('Site'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='sites__slug', field_name='sites__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
field_name='roles', field_name='roles',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
label='Role', label=_('Role'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='roles__slug', field_name='roles__slug',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
platform_id = django_filters.ModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
field_name='platforms', field_name='platforms',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform', label=_('Platform'),
) )
platform = django_filters.ModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
field_name='platforms__slug', field_name='platforms__slug',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label=_('Platform (slug)'),
) )
cluster_group_id = django_filters.ModelMultipleChoiceFilter( cluster_group_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster_groups', field_name='cluster_groups',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Cluster group', label=_('Cluster group'),
) )
cluster_group = django_filters.ModelMultipleChoiceFilter( cluster_group = django_filters.ModelMultipleChoiceFilter(
field_name='cluster_groups__slug', field_name='cluster_groups__slug',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Cluster group (slug)', label=_('Cluster group (slug)'),
) )
cluster_id = django_filters.ModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
field_name='clusters', field_name='clusters',
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='Cluster', label=_('Cluster'),
) )
tenant_group_id = django_filters.ModelMultipleChoiceFilter( tenant_group_id = django_filters.ModelMultipleChoiceFilter(
field_name='tenant_groups', field_name='tenant_groups',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Tenant group', label=_('Tenant group'),
) )
tenant_group = django_filters.ModelMultipleChoiceFilter( tenant_group = django_filters.ModelMultipleChoiceFilter(
field_name='tenant_groups__slug', field_name='tenant_groups__slug',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant group (slug)', label=_('Tenant group (slug)'),
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
field_name='tenants', field_name='tenants',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant', label=_('Tenant'),
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
field_name='tenants__slug', field_name='tenants__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label=_('Tenant (slug)'),
) )
tag = django_filters.ModelMultipleChoiceFilter( tag = django_filters.ModelMultipleChoiceFilter(
field_name='tags__slug', field_name='tags__slug',
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tag (slug)', label=_('Tag (slug)'),
) )
class Meta: class Meta:
@ -238,7 +239,7 @@ class ConfigContextFilterSet(BaseFilterSet):
class LocalConfigContextFilterSet(django_filters.FilterSet): class LocalConfigContextFilterSet(django_filters.FilterSet):
local_context_data = django_filters.BooleanFilter( local_context_data = django_filters.BooleanFilter(
method='_local_context_data', method='_local_context_data',
label='Has local config context data', label=_('Has local config context data'),
) )
def _local_context_data(self, queryset, name, value): def _local_context_data(self, queryset, name, value):
@ -248,7 +249,7 @@ class LocalConfigContextFilterSet(django_filters.FilterSet):
class ObjectChangeFilterSet(BaseFilterSet): class ObjectChangeFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
time = django_filters.DateTimeFromToRangeFilter() time = django_filters.DateTimeFromToRangeFilter()

View File

@ -1,6 +1,7 @@
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from mptt.forms import TreeNodeMultipleChoiceField from mptt.forms import TreeNodeMultipleChoiceField
from taggit.forms import TagField as TagField_ from taggit.forms import TagField as TagField_
@ -181,7 +182,7 @@ class TagFilterForm(BootstrapMixin, forms.Form):
model = Tag model = Tag
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
@ -285,7 +286,7 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
class ConfigContextFilterForm(BootstrapMixin, forms.Form): class ConfigContextFilterForm(BootstrapMixin, forms.Form):
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
region = DynamicModelMultipleChoiceField( region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -330,7 +331,7 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
cluster_id = DynamicModelMultipleChoiceField( cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
required=False, required=False,
label='Cluster' label=_('Cluster')
) )
tenant_group = DynamicModelMultipleChoiceField( tenant_group = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
@ -365,7 +366,7 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
class LocalConfigContextFilterForm(forms.Form): class LocalConfigContextFilterForm(forms.Form):
local_context_data = forms.NullBooleanField( local_context_data = forms.NullBooleanField(
required=False, required=False,
label='Has local config context data', label=_('Has local config context data'),
widget=StaticSelect2( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
@ -393,15 +394,15 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
model = ObjectChange model = ObjectChange
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
time_after = forms.DateTimeField( time_after = forms.DateTimeField(
label='After', label=_('After'),
required=False, required=False,
widget=DateTimePicker() widget=DateTimePicker()
) )
time_before = forms.DateTimeField( time_before = forms.DateTimeField(
label='Before', label=_('Before'),
required=False, required=False,
widget=DateTimePicker() widget=DateTimePicker()
) )
@ -420,7 +421,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
queryset=ContentType.objects.order_by('model'), queryset=ContentType.objects.order_by('model'),
required=False, required=False,
widget=ContentTypeSelect(), widget=ContentTypeSelect(),
label='Object Type' label=_('Object Type')
) )
@ -432,8 +433,8 @@ class ScriptForm(BootstrapMixin, forms.Form):
_commit = forms.BooleanField( _commit = forms.BooleanField(
required=False, required=False,
initial=True, initial=True,
label="Commit changes", label=_('Commit changes'),
help_text="Commit changes to the database (uncheck for a dry-run)" help_text=_('Commit changes to the database (uncheck for a dry-run)')
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -7,6 +7,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.utils.translation import gettext as _
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
from extras.choices import * from extras.choices import *
@ -70,9 +71,9 @@ class CustomField(models.Model):
obj_type = models.ManyToManyField( obj_type = models.ManyToManyField(
to=ContentType, to=ContentType,
related_name='custom_fields', related_name='custom_fields',
verbose_name='Object(s)', verbose_name=_('Object(s)'),
limit_choices_to=FeatureQuery('custom_fields'), limit_choices_to=FeatureQuery('custom_fields'),
help_text='The object(s) to which this field applies.' help_text=_('The object(s) to which this field applies.')
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
@ -86,8 +87,8 @@ class CustomField(models.Model):
label = models.CharField( label = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
help_text='Name of the field as displayed to users (if not provided, ' help_text=_('Name of the field as displayed to users (if not provided, '
'the field\'s name will be used)' 'the field\'s name will be used)')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -95,24 +96,24 @@ class CustomField(models.Model):
) )
required = models.BooleanField( required = models.BooleanField(
default=False, default=False,
help_text='If true, this field is required when creating new objects ' help_text=_('If true, this field is required when creating new objects '
'or editing an existing object.' 'or editing an existing object.')
) )
filter_logic = models.CharField( filter_logic = models.CharField(
max_length=50, max_length=50,
choices=CustomFieldFilterLogicChoices, choices=CustomFieldFilterLogicChoices,
default=CustomFieldFilterLogicChoices.FILTER_LOOSE, default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
help_text='Loose matches any instance of a given string; exact ' help_text=_('Loose matches any instance of a given string; exact '
'matches the entire field.' 'matches the entire field.')
) )
default = models.CharField( default = models.CharField(
max_length=100, max_length=100,
blank=True, blank=True,
help_text='Default value for the field. Use "true" or "false" for booleans.' help_text=_('Default value for the field. Use "true" or "false" for booleans.')
) )
weight = models.PositiveSmallIntegerField( weight = models.PositiveSmallIntegerField(
default=100, default=100,
help_text='Fields with higher weights appear lower in a form.' help_text=_('Fields with higher weights appear lower in a form.')
) )
objects = CustomFieldManager() objects = CustomFieldManager()
@ -284,7 +285,7 @@ class CustomFieldChoice(models.Model):
) )
weight = models.PositiveSmallIntegerField( weight = models.PositiveSmallIntegerField(
default=100, default=100,
help_text='Higher weights appear lower in the list' help_text=_('Higher weights appear lower in the list')
) )
class Meta: class Meta:

View File

@ -10,6 +10,7 @@ from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Template, Context from django.template import Template, Context
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from rest_framework.utils.encoders import JSONEncoder from rest_framework.utils.encoders import JSONEncoder
from utilities.utils import deepmerge, render_jinja2 from utilities.utils import deepmerge, render_jinja2
@ -32,9 +33,9 @@ class Webhook(models.Model):
obj_type = models.ManyToManyField( obj_type = models.ManyToManyField(
to=ContentType, to=ContentType,
related_name='webhooks', related_name='webhooks',
verbose_name='Object types', verbose_name=_('Object types'),
limit_choices_to=FeatureQuery('webhooks'), limit_choices_to=FeatureQuery('webhooks'),
help_text="The object(s) to which this Webhook applies." help_text=_('The object(s) to which this Webhook applies.')
) )
name = models.CharField( name = models.CharField(
max_length=150, max_length=150,
@ -42,20 +43,20 @@ class Webhook(models.Model):
) )
type_create = models.BooleanField( type_create = models.BooleanField(
default=False, default=False,
help_text="Call this webhook when a matching object is created." help_text=_('Call this webhook when a matching object is created.')
) )
type_update = models.BooleanField( type_update = models.BooleanField(
default=False, default=False,
help_text="Call this webhook when a matching object is updated." help_text=_('Call this webhook when a matching object is updated.')
) )
type_delete = models.BooleanField( type_delete = models.BooleanField(
default=False, default=False,
help_text="Call this webhook when a matching object is deleted." help_text=_('Call this webhook when a matching object is deleted.')
) )
payload_url = models.CharField( payload_url = models.CharField(
max_length=500, max_length=500,
verbose_name='URL', verbose_name=_('URL'),
help_text="A POST will be sent to this URL when the webhook is called." help_text=_('A POST will be sent to this URL when the webhook is called.')
) )
enabled = models.BooleanField( enabled = models.BooleanField(
default=True default=True
@ -64,47 +65,47 @@ class Webhook(models.Model):
max_length=30, max_length=30,
choices=WebhookHttpMethodChoices, choices=WebhookHttpMethodChoices,
default=WebhookHttpMethodChoices.METHOD_POST, default=WebhookHttpMethodChoices.METHOD_POST,
verbose_name='HTTP method' verbose_name=_('HTTP method')
) )
http_content_type = models.CharField( http_content_type = models.CharField(
max_length=100, max_length=100,
default=HTTP_CONTENT_TYPE_JSON, default=HTTP_CONTENT_TYPE_JSON,
verbose_name='HTTP content type', verbose_name=_('HTTP content type'),
help_text='The complete list of official content types is available ' help_text=_('The complete list of official content types is available '
'<a href="https://www.iana.org/assignments/media-types/media-types.xhtml">here</a>.' '<a href="https://www.iana.org/assignments/media-types/media-types.xhtml">here</a>.')
) )
additional_headers = models.TextField( additional_headers = models.TextField(
blank=True, blank=True,
help_text="User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. " help_text=_("User-supplied HTTP headers to be sent with the request in addition to the HTTP content type."
"Headers should be defined in the format <code>Name: Value</code>. Jinja2 template processing is " "Headers should be defined in the format <code>Name: Value</code>. Jinja2 template processing is "
"support with the same context as the request body (below)." "support with the same context as the request body (below).")
) )
body_template = models.TextField( body_template = models.TextField(
blank=True, blank=True,
help_text='Jinja2 template for a custom request body. If blank, a JSON object representing the change will be ' help_text=_('Jinja2 template for a custom request body. If blank, a JSON object representing the change will be '
'included. Available context data includes: <code>event</code>, <code>model</code>, ' 'included. Available context data includes: <code>event</code>, <code>model</code>, '
'<code>timestamp</code>, <code>username</code>, <code>request_id</code>, and <code>data</code>.' '<code>timestamp</code>, <code>username</code>, <code>request_id</code>, and <code>data</code>.')
) )
secret = models.CharField( secret = models.CharField(
max_length=255, max_length=255,
blank=True, blank=True,
help_text="When provided, the request will include a 'X-Hook-Signature' " help_text=_("When provided, the request will include a 'X-Hook-Signature' "
"header containing a HMAC hex digest of the payload body using " "header containing a HMAC hex digest of the payload body using "
"the secret as the key. The secret is not transmitted in " "the secret as the key. The secret is not transmitted in "
"the request." "the request.")
) )
ssl_verification = models.BooleanField( ssl_verification = models.BooleanField(
default=True, default=True,
verbose_name='SSL verification', verbose_name=_('SSL verification'),
help_text="Enable SSL certificate verification. Disable with caution!" help_text=_('Enable SSL certificate verification. Disable with caution!')
) )
ca_file_path = models.CharField( ca_file_path = models.CharField(
max_length=4096, max_length=4096,
null=True, null=True,
blank=True, blank=True,
verbose_name='CA File Path', verbose_name=_('CA File Path'),
help_text='The specific CA certificate file to use for SSL verification. ' help_text=_('The specific CA certificate file to use for SSL verification. '
'Leave blank to use the system defaults.' 'Leave blank to use the system defaults.')
) )
class Meta: class Meta:
@ -168,12 +169,12 @@ class CustomLink(models.Model):
) )
text = models.CharField( text = models.CharField(
max_length=500, max_length=500,
help_text="Jinja2 template code for link text" help_text=_('Jinja2 template code for link text')
) )
url = models.CharField( url = models.CharField(
max_length=500, max_length=500,
verbose_name='URL', verbose_name=_('URL'),
help_text="Jinja2 template code for link URL" help_text=_('Jinja2 template code for link URL')
) )
weight = models.PositiveSmallIntegerField( weight = models.PositiveSmallIntegerField(
default=100 default=100
@ -181,16 +182,16 @@ class CustomLink(models.Model):
group_name = models.CharField( group_name = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
help_text="Links with the same group will appear as a dropdown menu" help_text=_('Links with the same group will appear as a dropdown menu')
) )
button_class = models.CharField( button_class = models.CharField(
max_length=30, max_length=30,
choices=CustomLinkButtonClassChoices, choices=CustomLinkButtonClassChoices,
default=CustomLinkButtonClassChoices.CLASS_DEFAULT, default=CustomLinkButtonClassChoices.CLASS_DEFAULT,
help_text="The class of the first link in a group will be used for the dropdown button" help_text=_('The class of the first link in a group will be used for the dropdown button')
) )
new_window = models.BooleanField( new_window = models.BooleanField(
help_text="Force link to open in a new window" help_text=_('Force link to open in a new window')
) )
class Meta: class Meta:
@ -215,7 +216,7 @@ class Graph(models.Model):
) )
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
verbose_name='Name' verbose_name=_('Name')
) )
template_language = models.CharField( template_language = models.CharField(
max_length=50, max_length=50,
@ -224,11 +225,11 @@ class Graph(models.Model):
) )
source = models.CharField( source = models.CharField(
max_length=500, max_length=500,
verbose_name='Source URL' verbose_name=_('Source URL')
) )
link = models.URLField( link = models.URLField(
blank=True, blank=True,
verbose_name='Link URL' verbose_name=_('Link URL')
) )
class Meta: class Meta:
@ -284,18 +285,18 @@ class ExportTemplate(models.Model):
default=TemplateLanguageChoices.LANGUAGE_JINJA2 default=TemplateLanguageChoices.LANGUAGE_JINJA2
) )
template_code = models.TextField( template_code = models.TextField(
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.' help_text=_('The list of objects being exported is passed as a context variable named <code>queryset</code>.')
) )
mime_type = models.CharField( mime_type = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name='MIME type', verbose_name=_('MIME type'),
help_text='Defaults to <code>text/plain</code>' help_text=_('Defaults to <code>text/plain</code>')
) )
file_extension = models.CharField( file_extension = models.CharField(
max_length=15, max_length=15,
blank=True, blank=True,
help_text='Extension to append to the rendered filename' help_text=_('Extension to append to the rendered filename')
) )
class Meta: class Meta:

View File

@ -1,5 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from django.utils.translation import gettext as _
from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
from .models import ConfigContext, ObjectChange, Tag, TaggedItem from .models import ConfigContext, ObjectChange, Tag, TaggedItem
@ -84,10 +85,10 @@ class TaggedItemTable(BaseTable):
content_object = tables.TemplateColumn( content_object = tables.TemplateColumn(
template_code=TAGGED_ITEM, template_code=TAGGED_ITEM,
orderable=False, orderable=False,
verbose_name='Object' verbose_name=_('Object')
) )
content_type = tables.Column( content_type = tables.Column(
verbose_name='Type' verbose_name=_('Type')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -99,7 +100,7 @@ class ConfigContextTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
is_active = BooleanColumn( is_active = BooleanColumn(
verbose_name='Active' verbose_name=_('Active')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -119,15 +120,15 @@ class ObjectChangeTable(BaseTable):
template_code=OBJECTCHANGE_ACTION template_code=OBJECTCHANGE_ACTION
) )
changed_object_type = tables.Column( changed_object_type = tables.Column(
verbose_name='Type' verbose_name=_('Type')
) )
object_repr = tables.TemplateColumn( object_repr = tables.TemplateColumn(
template_code=OBJECTCHANGE_OBJECT, template_code=OBJECTCHANGE_OBJECT,
verbose_name='Object' verbose_name=_('Object')
) )
request_id = tables.TemplateColumn( request_id = tables.TemplateColumn(
template_code=OBJECTCHANGE_REQUEST_ID, template_code=OBJECTCHANGE_REQUEST_ID,
verbose_name='Request ID' verbose_name=_('Request ID')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):

View File

@ -1,6 +1,7 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext as _
class IPAMConfig(AppConfig): class IPAMConfig(AppConfig):
name = "ipam" name = "ipam"
verbose_name = "IPAM" verbose_name = _('IPAM')

View File

@ -2,6 +2,7 @@ import django_filters
import netaddr import netaddr
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 netaddr.core import AddrFormatError from netaddr.core import AddrFormatError
from dcim.models import Device, Interface, Region, Site from dcim.models import Device, Interface, Region, Site
@ -32,7 +33,7 @@ __all__ = (
class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
tag = TagFilter() tag = TagFilter()
@ -60,7 +61,7 @@ class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
family = django_filters.NumberFilter( family = django_filters.NumberFilter(
field_name='prefix', field_name='prefix',
@ -68,17 +69,17 @@ class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
) )
prefix = django_filters.CharFilter( prefix = django_filters.CharFilter(
method='filter_prefix', method='filter_prefix',
label='Prefix', label=_('Prefix'),
) )
rir_id = django_filters.ModelMultipleChoiceFilter( rir_id = django_filters.ModelMultipleChoiceFilter(
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
label='RIR (ID)', label=_('RIR (ID)'),
) )
rir = django_filters.ModelMultipleChoiceFilter( rir = django_filters.ModelMultipleChoiceFilter(
field_name='rir__slug', field_name='rir__slug',
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
to_field_name='slug', to_field_name='slug',
label='RIR (slug)', label=_('RIR (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -110,7 +111,7 @@ class AggregateFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
class Meta: class Meta:
@ -121,7 +122,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
family = django_filters.NumberFilter( family = django_filters.NumberFilter(
field_name='prefix', field_name='prefix',
@ -129,74 +130,74 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Cre
) )
prefix = django_filters.CharFilter( prefix = django_filters.CharFilter(
method='filter_prefix', method='filter_prefix',
label='Prefix', label=_('Prefix'),
) )
within = django_filters.CharFilter( within = django_filters.CharFilter(
method='search_within', method='search_within',
label='Within prefix', label=_('Within prefix'),
) )
within_include = django_filters.CharFilter( within_include = django_filters.CharFilter(
method='search_within_include', method='search_within_include',
label='Within and including prefix', label=_('Within and including prefix'),
) )
contains = django_filters.CharFilter( contains = django_filters.CharFilter(
method='search_contains', method='search_contains',
label='Prefixes which contain this prefix or IP', label=_('Prefixes which contain this prefix or IP'),
) )
mask_length = django_filters.NumberFilter( mask_length = django_filters.NumberFilter(
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label=_('Mask length'),
) )
vrf_id = django_filters.ModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label=_('VRF'),
) )
vrf = django_filters.ModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd', field_name='vrf__rd',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label=_('VRF (RD)'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
vlan_id = django_filters.ModelMultipleChoiceFilter( vlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
label='VLAN (ID)', label=_('VLAN (ID)'),
) )
vlan_vid = django_filters.NumberFilter( vlan_vid = django_filters.NumberFilter(
field_name='vlan__vid', field_name='vlan__vid',
label='VLAN number (1-4095)', label=_('VLAN number (1-4095)'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug', field_name='role__slug',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=PrefixStatusChoices, choices=PrefixStatusChoices,
@ -271,7 +272,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Cre
class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
family = django_filters.NumberFilter( family = django_filters.NumberFilter(
field_name='address', field_name='address',
@ -279,60 +280,60 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
) )
parent = django_filters.CharFilter( parent = django_filters.CharFilter(
method='search_by_parent', method='search_by_parent',
label='Parent prefix', label=_('Parent prefix'),
) )
address = MultiValueCharFilter( address = MultiValueCharFilter(
method='filter_address', method='filter_address',
label='Address', label=_('Address'),
) )
mask_length = django_filters.NumberFilter( mask_length = django_filters.NumberFilter(
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label=_('Mask length'),
) )
vrf_id = django_filters.ModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label=_('VRF'),
) )
vrf = django_filters.ModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd', field_name='vrf__rd',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label=_('VRF (RD)'),
) )
device = MultiValueCharFilter( device = MultiValueCharFilter(
method='filter_device', method='filter_device',
field_name='name', field_name='name',
label='Device (name)', label=_('Device (name)'),
) )
device_id = MultiValueNumberFilter( device_id = MultiValueNumberFilter(
method='filter_device', method='filter_device',
field_name='pk', field_name='pk',
label='Device (ID)', label=_('Device (ID)'),
) )
virtual_machine_id = django_filters.ModelMultipleChoiceFilter( virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
field_name='interface__virtual_machine', field_name='interface__virtual_machine',
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
label='Virtual machine (ID)', label=_('Virtual machine (ID)'),
) )
virtual_machine = django_filters.ModelMultipleChoiceFilter( virtual_machine = django_filters.ModelMultipleChoiceFilter(
field_name='interface__virtual_machine__name', field_name='interface__virtual_machine__name',
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
to_field_name='name', to_field_name='name',
label='Virtual machine (name)', label=_('Virtual machine (name)'),
) )
interface = django_filters.ModelMultipleChoiceFilter( interface = django_filters.ModelMultipleChoiceFilter(
field_name='interface__name', field_name='interface__name',
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
to_field_name='name', to_field_name='name',
label='Interface (ID)', label=_('Interface (ID)'),
) )
interface_id = django_filters.ModelMultipleChoiceFilter( interface_id = django_filters.ModelMultipleChoiceFilter(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
label='Interface (ID)', label=_('Interface (ID)'),
) )
assigned_to_interface = django_filters.BooleanFilter( assigned_to_interface = django_filters.BooleanFilter(
method='_assigned_to_interface', method='_assigned_to_interface',
label='Is assigned to an interface', label=_('Is assigned to an interface'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=IPAddressStatusChoices, choices=IPAddressStatusChoices,
@ -397,24 +398,24 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
class Meta: class Meta:
@ -425,50 +426,50 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
label='Group (ID)', label=_('Group (ID)'),
) )
group = django_filters.ModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
field_name='group__slug', field_name='group__slug',
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label=_('Group'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug', field_name='role__slug',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=VLANStatusChoices, choices=VLANStatusChoices,
@ -494,27 +495,27 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Creat
class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
device_id = django_filters.ModelMultipleChoiceFilter( device_id = django_filters.ModelMultipleChoiceFilter(
queryset=Device.objects.all(), queryset=Device.objects.all(),
label='Device (ID)', label=_('Device (ID)'),
) )
device = django_filters.ModelMultipleChoiceFilter( device = django_filters.ModelMultipleChoiceFilter(
field_name='device__name', field_name='device__name',
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
label='Device (name)', label=_('Device (name)'),
) )
virtual_machine_id = django_filters.ModelMultipleChoiceFilter( virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
label='Virtual machine (ID)', label=_('Virtual machine (ID)'),
) )
virtual_machine = django_filters.ModelMultipleChoiceFilter( virtual_machine = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_machine__name', field_name='virtual_machine__name',
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
to_field_name='name', to_field_name='name',
label='Virtual machine (name)', label=_('Virtual machine (name)'),
) )
tag = TagFilter() tag = TagFilter()

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.utils.translation import gettext as _
from dcim.models import Device, Interface, Rack, Region, Site from dcim.models import Device, Interface, Rack, Region, Site
from extras.forms import ( from extras.forms import (
@ -55,7 +56,7 @@ class VRFCSVForm(CustomFieldModelCSVForm):
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')
) )
class Meta: class Meta:
@ -75,7 +76,7 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
enforce_unique = forms.NullBooleanField( enforce_unique = forms.NullBooleanField(
required=False, required=False,
widget=BulkEditNullBooleanSelect(), widget=BulkEditNullBooleanSelect(),
label='Enforce unique space' label=_('Enforce unique space')
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -93,7 +94,7 @@ class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
field_order = ['q', 'tenant_group', 'tenant'] field_order = ['q', 'tenant_group', 'tenant']
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
tag = TagFilterField(model) tag = TagFilterField(model)
@ -119,14 +120,14 @@ class RIRCSVForm(CSVModelForm):
model = RIR model = RIR
fields = RIR.csv_headers fields = RIR.csv_headers
help_texts = { help_texts = {
'name': 'RIR name', 'name': _('RIR name'),
} }
class RIRFilterForm(BootstrapMixin, forms.Form): class RIRFilterForm(BootstrapMixin, forms.Form):
is_private = forms.NullBooleanField( is_private = forms.NullBooleanField(
required=False, required=False,
label='Private', label=_('Private'),
widget=StaticSelect2( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
@ -163,7 +164,7 @@ class AggregateCSVForm(CustomFieldModelCSVForm):
rir = CSVModelChoiceField( rir = CSVModelChoiceField(
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned RIR' help_text=_('Assigned RIR')
) )
class Meta: class Meta:
@ -179,7 +180,7 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
rir = DynamicModelChoiceField( rir = DynamicModelChoiceField(
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
required=False, required=False,
label='RIR' label=_('RIR')
) )
date_added = forms.DateField( date_added = forms.DateField(
required=False required=False
@ -202,19 +203,19 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Aggregate model = Aggregate
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
family = forms.ChoiceField( family = forms.ChoiceField(
required=False, required=False,
choices=add_blank_choice(IPAddressFamilyChoices), choices=add_blank_choice(IPAddressFamilyChoices),
label='Address family', label=_('Address family'),
widget=StaticSelect2() widget=StaticSelect2()
) )
rir = DynamicModelMultipleChoiceField( rir = DynamicModelMultipleChoiceField(
queryset=RIR.objects.all(), queryset=RIR.objects.all(),
to_field_name='slug', to_field_name='slug',
required=False, required=False,
label='RIR', label=_('RIR'),
widget=APISelectMultiple( widget=APISelectMultiple(
value_field="slug", value_field="slug",
) )
@ -252,7 +253,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
vrf = DynamicModelChoiceField( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF' label=_('VRF')
) )
site = DynamicModelChoiceField( site = DynamicModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
@ -270,7 +271,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
vlan_group = DynamicModelChoiceField( vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
required=False, required=False,
label='VLAN group', label=_('VLAN group'),
widget=APISelect( widget=APISelect(
filter_for={ filter_for={
'vlan': 'group_id' 'vlan': 'group_id'
@ -283,7 +284,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
vlan = DynamicModelChoiceField( vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
required=False, required=False,
label='VLAN', label=_('VLAN'),
widget=APISelect( widget=APISelect(
display_field='display_name' display_field='display_name'
) )
@ -323,41 +324,41 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
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(
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(
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(
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(
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(
choices=PrefixStatusChoices, choices=PrefixStatusChoices,
help_text='Operational status' help_text=_('Operational status')
) )
role = CSVModelChoiceField( role = CSVModelChoiceField(
queryset=Role.objects.all(), queryset=Role.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Functional role' help_text=_('Functional role')
) )
class Meta: class Meta:
@ -389,7 +390,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
vrf = DynamicModelChoiceField( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF' label=_('VRF')
) )
prefix_length = forms.IntegerField( prefix_length = forms.IntegerField(
min_value=PREFIX_LENGTH_MIN, min_value=PREFIX_LENGTH_MIN,
@ -412,7 +413,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
is_pool = forms.NullBooleanField( is_pool = forms.NullBooleanField(
required=False, required=False,
widget=BulkEditNullBooleanSelect(), widget=BulkEditNullBooleanSelect(),
label='Is a pool' label=_('Is a pool')
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -433,7 +434,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
within_include = forms.CharField( within_include = forms.CharField(
required=False, required=False,
@ -442,24 +443,24 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
'placeholder': 'Prefix', 'placeholder': 'Prefix',
} }
), ),
label='Search within' label=_('Search within')
) )
family = forms.ChoiceField( family = forms.ChoiceField(
required=False, required=False,
choices=add_blank_choice(IPAddressFamilyChoices), choices=add_blank_choice(IPAddressFamilyChoices),
label='Address family', label=_('Address family'),
widget=StaticSelect2() widget=StaticSelect2()
) )
mask_length = forms.ChoiceField( mask_length = forms.ChoiceField(
required=False, required=False,
choices=PREFIX_MASK_LENGTH_CHOICES, choices=PREFIX_MASK_LENGTH_CHOICES,
label='Mask length', label=_('Mask length'),
widget=StaticSelect2() widget=StaticSelect2()
) )
vrf_id = DynamicModelMultipleChoiceField( vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF', label=_('VRF'),
widget=APISelectMultiple( widget=APISelectMultiple(
null_option=True, null_option=True,
) )
@ -500,14 +501,14 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
) )
is_pool = forms.NullBooleanField( is_pool = forms.NullBooleanField(
required=False, required=False,
label='Is a pool', label=_('Is a pool'),
widget=StaticSelect2( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
expand = forms.BooleanField( expand = forms.BooleanField(
required=False, required=False,
label='Expand prefix hierarchy' label=_('Expand prefix hierarchy')
) )
tag = TagFilterField(model) tag = TagFilterField(model)
@ -524,12 +525,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
vrf = DynamicModelChoiceField( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF' label=_('VRF')
) )
nat_site = DynamicModelChoiceField( nat_site = DynamicModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
label='Site', label=_('Site'),
widget=APISelect( widget=APISelect(
filter_for={ filter_for={
'nat_rack': 'site_id', 'nat_rack': 'site_id',
@ -540,7 +541,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
nat_rack = DynamicModelChoiceField( nat_rack = DynamicModelChoiceField(
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
required=False, required=False,
label='Rack', label=_('Rack'),
widget=APISelect( widget=APISelect(
display_field='display_name', display_field='display_name',
filter_for={ filter_for={
@ -554,7 +555,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
nat_device = DynamicModelChoiceField( nat_device = DynamicModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
label='Device', label=_('Device'),
widget=APISelect( widget=APISelect(
display_field='display_name', display_field='display_name',
filter_for={ filter_for={
@ -565,7 +566,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
nat_vrf = DynamicModelChoiceField( nat_vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF', label=_('VRF'),
widget=APISelect( widget=APISelect(
filter_for={ filter_for={
'nat_inside': 'vrf_id' 'nat_inside': 'vrf_id'
@ -575,14 +576,14 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
nat_inside = DynamicModelChoiceField( nat_inside = DynamicModelChoiceField(
queryset=IPAddress.objects.all(), queryset=IPAddress.objects.all(),
required=False, required=False,
label='IP Address', label=_('IP Address'),
widget=APISelect( widget=APISelect(
display_field='address' display_field='address'
) )
) )
primary_for_parent = forms.BooleanField( primary_for_parent = forms.BooleanField(
required=False, required=False,
label='Make this the primary IP for the device/VM' label=_('Make this the primary IP for the device/VM')
) )
tags = TagField( tags = TagField(
required=False required=False
@ -671,7 +672,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
class IPAddressBulkCreateForm(BootstrapMixin, forms.Form): class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
pattern = ExpandableIPAddressField( pattern = ExpandableIPAddressField(
label='Address pattern' label=_('Address pattern')
) )
@ -679,7 +680,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
vrf = DynamicModelChoiceField( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF' label=_('VRF')
) )
tags = TagField( tags = TagField(
required=False required=False
@ -705,43 +706,43 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
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(
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(
choices=IPAddressStatusChoices, choices=IPAddressStatusChoices,
help_text='Operational status' help_text=_('Operational status')
) )
role = CSVChoiceField( role = CSVChoiceField(
choices=IPAddressRoleChoices, choices=IPAddressRoleChoices,
required=False, required=False,
help_text='Functional role' help_text=_('Functional role')
) )
device = CSVModelChoiceField( device = CSVModelChoiceField(
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(
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(
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
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(
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
) )
@ -779,7 +780,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
# 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("No device or virtual machine specified; cannot set as primary IP") raise forms.ValidationError(_('No device or virtual machine specified; cannot set as primary IP'))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -805,7 +806,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
vrf = DynamicModelChoiceField( vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF' label=_('VRF')
) )
mask_length = forms.IntegerField( mask_length = forms.IntegerField(
min_value=IPADDRESS_MASK_LENGTH_MIN, min_value=IPADDRESS_MASK_LENGTH_MIN,
@ -845,12 +846,12 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
vrf_id = DynamicModelChoiceField( vrf_id = DynamicModelChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF', label=_('VRF'),
empty_label='Global' empty_label='Global'
) )
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search', label=_('Search'),
) )
@ -862,7 +863,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
parent = forms.CharField( parent = forms.CharField(
required=False, required=False,
@ -871,24 +872,24 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
'placeholder': 'Prefix', 'placeholder': 'Prefix',
} }
), ),
label='Parent Prefix' label=_('Parent Prefix')
) )
family = forms.ChoiceField( family = forms.ChoiceField(
required=False, required=False,
choices=add_blank_choice(IPAddressFamilyChoices), choices=add_blank_choice(IPAddressFamilyChoices),
label='Address family', label=_('Address family'),
widget=StaticSelect2() widget=StaticSelect2()
) )
mask_length = forms.ChoiceField( mask_length = forms.ChoiceField(
required=False, required=False,
choices=IPADDRESS_MASK_LENGTH_CHOICES, choices=IPADDRESS_MASK_LENGTH_CHOICES,
label='Mask length', label=_('Mask length'),
widget=StaticSelect2() widget=StaticSelect2()
) )
vrf_id = DynamicModelMultipleChoiceField( vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
required=False, required=False,
label='VRF', label=_('VRF'),
widget=APISelectMultiple( widget=APISelectMultiple(
null_option=True, null_option=True,
) )
@ -905,7 +906,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
) )
assigned_to_interface = forms.NullBooleanField( assigned_to_interface = forms.NullBooleanField(
required=False, required=False,
label='Assigned to an interface', label=_('Assigned to an interface'),
widget=StaticSelect2( widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
@ -936,7 +937,7 @@ class VLANGroupCSVForm(CSVModelForm):
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')
) )
slug = SlugField() slug = SlugField()
@ -1018,37 +1019,37 @@ class VLANCSVForm(CustomFieldModelCSVForm):
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(
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(
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(
choices=VLANStatusChoices, choices=VLANStatusChoices,
help_text='Operational status' help_text=_('Operational status')
) )
role = CSVModelChoiceField( role = CSVModelChoiceField(
queryset=Role.objects.all(), queryset=Role.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Functional role' help_text=_('Functional role')
) )
class Meta: class Meta:
model = VLAN model = VLAN
fields = VLAN.csv_headers fields = VLAN.csv_headers
help_texts = { help_texts = {
'vid': 'Numeric VLAN ID (1-4095)', 'vid': _('Numeric VLAN ID (1-4095)'),
'name': 'VLAN name', 'name': _('VLAN name'),
} }
def __init__(self, data=None, *args, **kwargs): def __init__(self, data=None, *args, **kwargs):
@ -1108,7 +1109,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant'] field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
region = DynamicModelMultipleChoiceField( region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -1134,7 +1135,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
group_id = DynamicModelMultipleChoiceField( group_id = DynamicModelMultipleChoiceField(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
required=False, required=False,
label='VLAN group', label=_('VLAN group'),
widget=APISelectMultiple( widget=APISelectMultiple(
null_option=True, null_option=True,
) )
@ -1204,7 +1205,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Service model = Service
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
protocol = forms.ChoiceField( protocol = forms.ChoiceField(
choices=add_blank_choice(ServiceProtocolChoices), choices=add_blank_choice(ServiceProtocolChoices),
@ -1222,17 +1223,17 @@ class ServiceCSVForm(CustomFieldModelCSVForm):
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(
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(
choices=ServiceProtocolChoices, choices=ServiceProtocolChoices,
help_text='IP protocol' help_text=_('IP protocol')
) )
class Meta: class Meta:

View File

@ -6,6 +6,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.db.models import F, Q from django.db.models import F, Q
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.models import Device, Interface from dcim.models import Device, Interface
@ -50,8 +51,8 @@ class VRF(ChangeLoggedModel, CustomFieldModel):
unique=True, unique=True,
blank=True, blank=True,
null=True, null=True,
verbose_name='Route distinguisher', verbose_name=_('Route distinguisher'),
help_text='Unique route distinguisher (as defined in RFC 4364)' help_text=_('Unique route distinguisher (as defined in RFC 4364)')
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -62,8 +63,8 @@ class VRF(ChangeLoggedModel, CustomFieldModel):
) )
enforce_unique = models.BooleanField( enforce_unique = models.BooleanField(
default=True, default=True,
verbose_name='Enforce unique space', verbose_name=_('Enforce unique space'),
help_text='Prevent duplicate prefixes/IP addresses within this VRF' help_text=_('Prevent duplicate prefixes/IP addresses within this VRF')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -84,7 +85,7 @@ class VRF(ChangeLoggedModel, CustomFieldModel):
class Meta: class Meta:
ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique ordering = ('name', 'rd', 'pk') # (name, rd) may be non-unique
verbose_name = 'VRF' verbose_name = _('VRF')
verbose_name_plural = 'VRFs' verbose_name_plural = 'VRFs'
def __str__(self): def __str__(self):
@ -123,8 +124,8 @@ class RIR(ChangeLoggedModel):
) )
is_private = models.BooleanField( is_private = models.BooleanField(
default=False, default=False,
verbose_name='Private', verbose_name=_('Private'),
help_text='IP space managed by this RIR is considered private' help_text=_('IP space managed by this RIR is considered private')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -135,7 +136,7 @@ class RIR(ChangeLoggedModel):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
verbose_name = 'RIR' verbose_name = _('RIR')
verbose_name_plural = 'RIRs' verbose_name_plural = 'RIRs'
def __str__(self): def __str__(self):
@ -164,7 +165,7 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
to='ipam.RIR', to='ipam.RIR',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='aggregates', related_name='aggregates',
verbose_name='RIR' verbose_name=_('RIR')
) )
date_added = models.DateField( date_added = models.DateField(
blank=True, blank=True,
@ -299,7 +300,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
assigned to a VLAN where appropriate. assigned to a VLAN where appropriate.
""" """
prefix = IPNetworkField( prefix = IPNetworkField(
help_text='IPv4 or IPv6 network with mask' help_text=_('IPv4 or IPv6 network with mask')
) )
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
@ -314,7 +315,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
related_name='prefixes', related_name='prefixes',
blank=True, blank=True,
null=True, null=True,
verbose_name='VRF' verbose_name=_('VRF')
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -329,14 +330,14 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
related_name='prefixes', related_name='prefixes',
blank=True, blank=True,
null=True, null=True,
verbose_name='VLAN' verbose_name=_('VLAN')
) )
status = models.CharField( status = models.CharField(
max_length=50, max_length=50,
choices=PrefixStatusChoices, choices=PrefixStatusChoices,
default=PrefixStatusChoices.STATUS_ACTIVE, default=PrefixStatusChoices.STATUS_ACTIVE,
verbose_name='Status', verbose_name=_('Status'),
help_text='Operational status of this prefix' help_text=_('Operational status of this prefix')
) )
role = models.ForeignKey( role = models.ForeignKey(
to='ipam.Role', to='ipam.Role',
@ -344,12 +345,12 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
related_name='prefixes', related_name='prefixes',
blank=True, blank=True,
null=True, null=True,
help_text='The primary function of this prefix' help_text=_('The primary function of this prefix')
) )
is_pool = models.BooleanField( is_pool = models.BooleanField(
verbose_name='Is a pool', verbose_name=_('Is a pool'),
default=False, default=False,
help_text='All IP addresses within this prefix are considered usable' help_text=_('All IP addresses within this prefix are considered usable')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -570,7 +571,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP. which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
""" """
address = IPAddressField( address = IPAddressField(
help_text='IPv4 or IPv6 address (with mask)' help_text=_('IPv4 or IPv6 address (with mask)')
) )
vrf = models.ForeignKey( vrf = models.ForeignKey(
to='ipam.VRF', to='ipam.VRF',
@ -578,7 +579,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
related_name='ip_addresses', related_name='ip_addresses',
blank=True, blank=True,
null=True, null=True,
verbose_name='VRF' verbose_name=_('VRF')
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -591,13 +592,13 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
max_length=50, max_length=50,
choices=IPAddressStatusChoices, choices=IPAddressStatusChoices,
default=IPAddressStatusChoices.STATUS_ACTIVE, default=IPAddressStatusChoices.STATUS_ACTIVE,
help_text='The operational status of this IP' help_text=_('The operational status of this IP')
) )
role = models.CharField( role = models.CharField(
max_length=50, max_length=50,
choices=IPAddressRoleChoices, choices=IPAddressRoleChoices,
blank=True, blank=True,
help_text='The functional role of this IP' help_text=_('The functional role of this IP')
) )
interface = models.ForeignKey( interface = models.ForeignKey(
to='dcim.Interface', to='dcim.Interface',
@ -612,15 +613,15 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
related_name='nat_outside', related_name='nat_outside',
blank=True, blank=True,
null=True, null=True,
verbose_name='NAT (Inside)', verbose_name=_('NAT (Inside)'),
help_text='The IP for which this address is the "outside" IP' help_text=_('The IP for which this address is the "outside" IP')
) )
dns_name = models.CharField( dns_name = models.CharField(
max_length=255, max_length=255,
blank=True, blank=True,
validators=[DNSValidator], validators=[DNSValidator],
verbose_name='DNS Name', verbose_name=_('DNS Name'),
help_text='Hostname or FQDN (not case-sensitive)' help_text=_('Hostname or FQDN (not case-sensitive)')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
@ -663,7 +664,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
class Meta: class Meta:
ordering = ('address', 'pk') # address may be non-unique ordering = ('address', 'pk') # address may be non-unique
verbose_name = 'IP address' verbose_name = _('IP address')
verbose_name_plural = 'IP addresses' verbose_name_plural = 'IP addresses'
def __str__(self): def __str__(self):
@ -836,7 +837,7 @@ class VLANGroup(ChangeLoggedModel):
['site', 'name'], ['site', 'name'],
['site', 'slug'], ['site', 'slug'],
] ]
verbose_name = 'VLAN group' verbose_name = _('VLAN group')
verbose_name_plural = 'VLAN groups' verbose_name_plural = 'VLAN groups'
def __str__(self): def __str__(self):
@ -889,7 +890,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
null=True null=True
) )
vid = models.PositiveSmallIntegerField( vid = models.PositiveSmallIntegerField(
verbose_name='ID', verbose_name=_('ID'),
validators=[MinValueValidator(1), MaxValueValidator(4094)] validators=[MinValueValidator(1), MaxValueValidator(4094)]
) )
name = models.CharField( name = models.CharField(
@ -943,7 +944,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
['group', 'vid'], ['group', 'vid'],
['group', 'name'], ['group', 'name'],
] ]
verbose_name = 'VLAN' verbose_name = _('VLAN')
verbose_name_plural = 'VLANs' verbose_name_plural = 'VLANs'
def __str__(self): def __str__(self):
@ -999,7 +1000,7 @@ class Service(ChangeLoggedModel, CustomFieldModel):
to='dcim.Device', to='dcim.Device',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='services', related_name='services',
verbose_name='device', verbose_name=_('device'),
null=True, null=True,
blank=True blank=True
) )
@ -1022,13 +1023,13 @@ class Service(ChangeLoggedModel, CustomFieldModel):
MinValueValidator(SERVICE_PORT_MIN), MinValueValidator(SERVICE_PORT_MIN),
MaxValueValidator(SERVICE_PORT_MAX) MaxValueValidator(SERVICE_PORT_MAX)
], ],
verbose_name='Port number' verbose_name=_('Port number')
) )
ipaddresses = models.ManyToManyField( ipaddresses = models.ManyToManyField(
to='ipam.IPAddress', to='ipam.IPAddress',
related_name='services', related_name='services',
blank=True, blank=True,
verbose_name='IP addresses' verbose_name=_('IP addresses')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,

View File

@ -1,5 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from django.utils.translation import gettext as _
from dcim.models import Interface from dcim.models import Interface
from tenancy.tables import COL_TENANT from tenancy.tables import COL_TENANT
@ -191,13 +192,13 @@ class VRFTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
rd = tables.Column( rd = tables.Column(
verbose_name='RD' verbose_name=_('RD')
) )
tenant = tables.TemplateColumn( tenant = tables.TemplateColumn(
template_code=COL_TENANT template_code=COL_TENANT
) )
enforce_unique = BooleanColumn( enforce_unique = BooleanColumn(
verbose_name='Unique' verbose_name=_('Unique')
) )
tags = TagColumn( tags = TagColumn(
url_name='ipam:vrf_list' url_name='ipam:vrf_list'
@ -217,10 +218,10 @@ class RIRTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
is_private = BooleanColumn( is_private = BooleanColumn(
verbose_name='Private' verbose_name=_('Private')
) )
aggregate_count = tables.Column( aggregate_count = tables.Column(
verbose_name='Aggregates' verbose_name=_('Aggregates')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=RIR_ACTIONS, template_code=RIR_ACTIONS,
@ -237,32 +238,32 @@ class RIRTable(BaseTable):
class RIRDetailTable(RIRTable): class RIRDetailTable(RIRTable):
stats_total = tables.Column( stats_total = tables.Column(
accessor='stats.total', accessor='stats.total',
verbose_name='Total', verbose_name=_('Total'),
footer=lambda table: sum(r.stats['total'] for r in table.data) footer=lambda table: sum(r.stats['total'] for r in table.data)
) )
stats_active = tables.Column( stats_active = tables.Column(
accessor='stats.active', accessor='stats.active',
verbose_name='Active', verbose_name=_('Active'),
footer=lambda table: sum(r.stats['active'] for r in table.data) footer=lambda table: sum(r.stats['active'] for r in table.data)
) )
stats_reserved = tables.Column( stats_reserved = tables.Column(
accessor='stats.reserved', accessor='stats.reserved',
verbose_name='Reserved', verbose_name=_('Reserved'),
footer=lambda table: sum(r.stats['reserved'] for r in table.data) footer=lambda table: sum(r.stats['reserved'] for r in table.data)
) )
stats_deprecated = tables.Column( stats_deprecated = tables.Column(
accessor='stats.deprecated', accessor='stats.deprecated',
verbose_name='Deprecated', verbose_name=_('Deprecated'),
footer=lambda table: sum(r.stats['deprecated'] for r in table.data) footer=lambda table: sum(r.stats['deprecated'] for r in table.data)
) )
stats_available = tables.Column( stats_available = tables.Column(
accessor='stats.available', accessor='stats.available',
verbose_name='Available', verbose_name=_('Available'),
footer=lambda table: sum(r.stats['available'] for r in table.data) footer=lambda table: sum(r.stats['available'] for r in table.data)
) )
utilization = tables.TemplateColumn( utilization = tables.TemplateColumn(
template_code=RIR_UTILIZATION, template_code=RIR_UTILIZATION,
verbose_name='Utilization' verbose_name=_('Utilization')
) )
class Meta(RIRTable.Meta): class Meta(RIRTable.Meta):
@ -283,11 +284,11 @@ class RIRDetailTable(RIRTable):
class AggregateTable(BaseTable): class AggregateTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
prefix = tables.LinkColumn( prefix = tables.LinkColumn(
verbose_name='Aggregate' verbose_name=_('Aggregate')
) )
date_added = tables.DateColumn( date_added = tables.DateColumn(
format="Y-m-d", format="Y-m-d",
verbose_name='Added' verbose_name=_('Added')
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -297,7 +298,7 @@ class AggregateTable(BaseTable):
class AggregateDetailTable(AggregateTable): class AggregateDetailTable(AggregateTable):
child_count = tables.Column( child_count = tables.Column(
verbose_name='Prefixes' verbose_name=_('Prefixes')
) )
utilization = tables.TemplateColumn( utilization = tables.TemplateColumn(
template_code=UTILIZATION_GRAPH, template_code=UTILIZATION_GRAPH,
@ -320,11 +321,11 @@ class RoleTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
prefix_count = tables.TemplateColumn( prefix_count = tables.TemplateColumn(
template_code=ROLE_PREFIX_COUNT, template_code=ROLE_PREFIX_COUNT,
verbose_name='Prefixes' verbose_name=_('Prefixes')
) )
vlan_count = tables.TemplateColumn( vlan_count = tables.TemplateColumn(
template_code=ROLE_VLAN_COUNT, template_code=ROLE_VLAN_COUNT,
verbose_name='VLANs' verbose_name=_('VLANs')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=ROLE_ACTIONS, template_code=ROLE_ACTIONS,
@ -353,7 +354,7 @@ class PrefixTable(BaseTable):
) )
vrf = tables.TemplateColumn( vrf = tables.TemplateColumn(
template_code=VRF_LINK, template_code=VRF_LINK,
verbose_name='VRF' verbose_name=_('VRF')
) )
tenant = tables.TemplateColumn( tenant = tables.TemplateColumn(
template_code=TENANT_LINK template_code=TENANT_LINK
@ -365,13 +366,13 @@ class PrefixTable(BaseTable):
vlan = tables.LinkColumn( vlan = tables.LinkColumn(
viewname='ipam:vlan', viewname='ipam:vlan',
args=[Accessor('vlan.pk')], args=[Accessor('vlan.pk')],
verbose_name='VLAN' verbose_name=_('VLAN')
) )
role = tables.TemplateColumn( role = tables.TemplateColumn(
template_code=PREFIX_ROLE_LINK template_code=PREFIX_ROLE_LINK
) )
is_pool = BooleanColumn( is_pool = BooleanColumn(
verbose_name='Pool' verbose_name=_('Pool')
) )
add_prefetch = False add_prefetch = False
@ -415,11 +416,11 @@ class IPAddressTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
address = tables.TemplateColumn( address = tables.TemplateColumn(
template_code=IPADDRESS_LINK, template_code=IPADDRESS_LINK,
verbose_name='IP Address' verbose_name=_('IP Address')
) )
vrf = tables.TemplateColumn( vrf = tables.TemplateColumn(
template_code=VRF_LINK, template_code=VRF_LINK,
verbose_name='VRF' verbose_name=_('VRF')
) )
status = tables.TemplateColumn( status = tables.TemplateColumn(
template_code=STATUS_LABEL template_code=STATUS_LABEL
@ -450,7 +451,7 @@ class IPAddressDetailTable(IPAddressTable):
viewname='ipam:ipaddress', viewname='ipam:ipaddress',
args=[Accessor('nat_inside.pk')], args=[Accessor('nat_inside.pk')],
orderable=False, orderable=False,
verbose_name='NAT (Inside)' verbose_name=_('NAT (Inside)')
) )
tenant = tables.TemplateColumn( tenant = tables.TemplateColumn(
template_code=COL_TENANT template_code=COL_TENANT
@ -472,7 +473,7 @@ class IPAddressDetailTable(IPAddressTable):
class IPAddressAssignTable(BaseTable): class IPAddressAssignTable(BaseTable):
address = tables.TemplateColumn( address = tables.TemplateColumn(
template_code=IPADDRESS_ASSIGN_LINK, template_code=IPADDRESS_ASSIGN_LINK,
verbose_name='IP Address' verbose_name=_('IP Address')
) )
status = tables.TemplateColumn( status = tables.TemplateColumn(
template_code=STATUS_LABEL template_code=STATUS_LABEL
@ -496,11 +497,11 @@ class InterfaceIPAddressTable(BaseTable):
List IP addresses assigned to a specific Interface. List IP addresses assigned to a specific Interface.
""" """
address = tables.LinkColumn( address = tables.LinkColumn(
verbose_name='IP Address' verbose_name=_('IP Address')
) )
vrf = tables.TemplateColumn( vrf = tables.TemplateColumn(
template_code=VRF_LINK, template_code=VRF_LINK,
verbose_name='VRF' verbose_name=_('VRF')
) )
status = tables.TemplateColumn( status = tables.TemplateColumn(
template_code=STATUS_LABEL template_code=STATUS_LABEL
@ -526,7 +527,7 @@ class VLANGroupTable(BaseTable):
args=[Accessor('site.slug')] args=[Accessor('site.slug')]
) )
vlan_count = tables.Column( vlan_count = tables.Column(
verbose_name='VLANs' verbose_name=_('VLANs')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=VLANGROUP_ACTIONS, template_code=VLANGROUP_ACTIONS,
@ -548,7 +549,7 @@ class VLANTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
vid = tables.TemplateColumn( vid = tables.TemplateColumn(
template_code=VLAN_LINK, template_code=VLAN_LINK,
verbose_name='ID' verbose_name=_('ID')
) )
site = tables.LinkColumn( site = tables.LinkColumn(
viewname='dcim:site', viewname='dcim:site',
@ -580,7 +581,7 @@ class VLANDetailTable(VLANTable):
prefixes = tables.TemplateColumn( prefixes = tables.TemplateColumn(
template_code=VLAN_PREFIXES, template_code=VLAN_PREFIXES,
orderable=False, orderable=False,
verbose_name='Prefixes' verbose_name=_('Prefixes')
) )
tenant = tables.TemplateColumn( tenant = tables.TemplateColumn(
template_code=COL_TENANT template_code=COL_TENANT
@ -599,7 +600,7 @@ class VLANMemberTable(BaseTable):
order_by=['device', 'virtual_machine'] order_by=['device', 'virtual_machine']
) )
name = tables.LinkColumn( name = tables.LinkColumn(
verbose_name='Interface' verbose_name=_('Interface')
) )
untagged = tables.TemplateColumn( untagged = tables.TemplateColumn(
template_code=VLAN_MEMBER_UNTAGGED, template_code=VLAN_MEMBER_UNTAGGED,
@ -623,7 +624,7 @@ class InterfaceVLANTable(BaseTable):
vid = tables.LinkColumn( vid = tables.LinkColumn(
viewname='ipam:vlan', viewname='ipam:vlan',
args=[Accessor('pk')], args=[Accessor('pk')],
verbose_name='ID' verbose_name=_('ID')
) )
tagged = BooleanColumn() tagged = BooleanColumn()
site = tables.LinkColumn( site = tables.LinkColumn(
@ -632,7 +633,7 @@ class InterfaceVLANTable(BaseTable):
) )
group = tables.Column( group = tables.Column(
accessor=Accessor('group.name'), accessor=Accessor('group.name'),
verbose_name='Group' verbose_name=_('Group')
) )
tenant = tables.TemplateColumn( tenant = tables.TemplateColumn(
template_code=COL_TENANT template_code=COL_TENANT

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +1,48 @@
from django import forms from django import forms
from django.utils.translation import gettext as _
from utilities.forms import BootstrapMixin from utilities.forms import BootstrapMixin
OBJ_TYPE_CHOICES = ( OBJ_TYPE_CHOICES = (
('', 'All Objects'), ('', _('All Objects')),
('Circuits', ( (_('Circuits'), (
('provider', 'Providers'), ('provider', _('Providers')),
('circuit', 'Circuits'), ('circuit', _('Circuits')),
)), )),
('DCIM', ( (_('DCIM'), (
('site', 'Sites'), ('site', _('Sites')),
('rack', 'Racks'), ('rack', _('Racks')),
('rackgroup', 'Rack Groups'), ('rackgroup', _('Rack Groups')),
('devicetype', 'Device types'), ('devicetype', _('Device types')),
('device', 'Devices'), ('device', _('Devices')),
('virtualchassis', 'Virtual Chassis'), ('virtualchassis', _('Virtual Chassis')),
('cable', 'Cables'), ('cable', _('Cables')),
('powerfeed', 'Power Feeds'), ('powerfeed', _('Power Feeds')),
)), )),
('IPAM', ( (_('IPAM'), (
('vrf', 'VRFs'), ('vrf', _('VRFs')),
('aggregate', 'Aggregates'), ('aggregate', _('Aggregates')),
('prefix', 'Prefixes'), ('prefix', _('Prefixes')),
('ipaddress', 'IP addresses'), ('ipaddress', _('IP addresses')),
('vlan', 'VLANs'), ('vlan', _('VLANs')),
)), )),
('Secrets', ( (_('Secrets'), (
('secret', 'Secrets'), ('secret', _('Secrets')),
)), )),
('Tenancy', ( (_('Tenancy'), (
('tenant', 'Tenants'), ('tenant', _('Tenants')),
)), )),
('Virtualization', ( (_('Virtualization'), (
('cluster', 'Clusters'), ('cluster', _('Clusters')),
('virtualmachine', 'Virtual machines'), ('virtualmachine', _('Virtual machines')),
)), )),
) )
class SearchForm(BootstrapMixin, forms.Form): class SearchForm(BootstrapMixin, forms.Form):
q = forms.CharField( q = forms.CharField(
label='Search' label=_('Search')
) )
obj_type = forms.ChoiceField( obj_type = forms.ChoiceField(
choices=OBJ_TYPE_CHOICES, required=False, label='Type' choices=OBJ_TYPE_CHOICES, required=False, label=_('Type')
) )

View File

@ -681,3 +681,12 @@ for plugin_name in PLUGINS:
CACHEOPS.update({ CACHEOPS.update({
"{}.{}".format(plugin_name, key): value for key, value in plugin_config.caching_config.items() "{}.{}".format(plugin_name, key): value for key, value in plugin_config.caching_config.items()
}) })
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
LANGUAGES = (
('en', 'English'),
('zh-Hans', '中文简体'),
)

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _
from dcim.models import Device from dcim.models import Device
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
@ -20,30 +21,31 @@ class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class SecretFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class SecretFilterSet(
BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=SecretRole.objects.all(), queryset=SecretRole.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug', field_name='role__slug',
queryset=SecretRole.objects.all(), queryset=SecretRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
device_id = django_filters.ModelMultipleChoiceFilter( device_id = django_filters.ModelMultipleChoiceFilter(
queryset=Device.objects.all(), queryset=Device.objects.all(),
label='Device (ID)', label=_('Device (ID)'),
) )
device = django_filters.ModelMultipleChoiceFilter( device = django_filters.ModelMultipleChoiceFilter(
field_name='device__name', field_name='device__name',
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
label='Device (name)', label=_('Device (name)'),
) )
tag = TagFilter() tag = TagFilter()

View File

@ -1,6 +1,7 @@
from Crypto.Cipher import PKCS1_OAEP from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from django import forms from django import forms
from django.utils.translation import gettext as _
from dcim.models import Device from dcim.models import Device
from extras.forms import ( from extras.forms import (
@ -20,21 +21,21 @@ def validate_rsa_key(key, is_secret=True):
Validate the format and type of an RSA key. Validate the format and type of an RSA key.
""" """
if key.startswith('ssh-rsa '): if key.startswith('ssh-rsa '):
raise forms.ValidationError("OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.") raise forms.ValidationError(_('OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.'))
try: try:
key = RSA.importKey(key) key = RSA.importKey(key)
except ValueError: except ValueError:
raise forms.ValidationError("Invalid RSA key. Please ensure that your key is in PEM (base64) format.") raise forms.ValidationError(_('Invalid RSA key. Please ensure that your key is in PEM (base64) format.'))
except Exception as e: except Exception as e:
raise forms.ValidationError("Invalid key detected: {}".format(e)) raise forms.ValidationError("Invalid key detected: {}".format(e))
if is_secret and not key.has_private(): if is_secret and not key.has_private():
raise forms.ValidationError("This looks like a public key. Please provide your private RSA key.") raise forms.ValidationError(_('This looks like a public key. Please provide your private RSA key.'))
elif not is_secret and key.has_private(): elif not is_secret and key.has_private():
raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.") raise forms.ValidationError(_('This looks like a private key. Please provide your public RSA key.'))
try: try:
PKCS1_OAEP.new(key) PKCS1_OAEP.new(key)
except Exception: except Exception:
raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.") raise forms.ValidationError(_('Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.'))
# #
@ -74,7 +75,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm):
plaintext = forms.CharField( plaintext = forms.CharField(
max_length=SECRET_PLAINTEXT_MAX_LENGTH, max_length=SECRET_PLAINTEXT_MAX_LENGTH,
required=False, required=False,
label='Plaintext', label=_('Plaintext'),
widget=forms.PasswordInput( widget=forms.PasswordInput(
attrs={ attrs={
'class': 'requires-session-key', 'class': 'requires-session-key',
@ -84,7 +85,7 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm):
plaintext2 = forms.CharField( plaintext2 = forms.CharField(
max_length=SECRET_PLAINTEXT_MAX_LENGTH, max_length=SECRET_PLAINTEXT_MAX_LENGTH,
required=False, required=False,
label='Plaintext (verify)', label=_('Plaintext (verify)'),
widget=forms.PasswordInput() widget=forms.PasswordInput()
) )
role = DynamicModelChoiceField( role = DynamicModelChoiceField(
@ -130,22 +131,22 @@ class SecretCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned device' help_text=_('Assigned device')
) )
role = CSVModelChoiceField( role = CSVModelChoiceField(
queryset=SecretRole.objects.all(), queryset=SecretRole.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned role' help_text=_('Assigned role')
) )
plaintext = forms.CharField( plaintext = forms.CharField(
help_text='Plaintext secret data' help_text=_('Plaintext secret data')
) )
class Meta: class Meta:
model = Secret model = Secret
fields = Secret.csv_headers fields = Secret.csv_headers
help_texts = { help_texts = {
'name': 'Name or username', 'name': _('Name or username'),
} }
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -154,7 +155,8 @@ class SecretCSVForm(CustomFieldModelCSVForm):
return s return s
class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): class SecretBulkEditForm(
BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField( pk = forms.ModelMultipleChoiceField(
queryset=Secret.objects.all(), queryset=Secret.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
@ -178,7 +180,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Secret model = Secret
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
role = DynamicModelMultipleChoiceField( role = DynamicModelMultipleChoiceField(
queryset=SecretRole.objects.all(), queryset=SecretRole.objects.all(),
@ -220,7 +222,7 @@ class UserKeyForm(BootstrapMixin, forms.ModelForm):
class ActivateUserKeyForm(forms.Form): class ActivateUserKeyForm(forms.Form):
_selected_action = forms.ModelMultipleChoiceField( _selected_action = forms.ModelMultipleChoiceField(
queryset=UserKey.objects.all(), queryset=UserKey.objects.all(),
label='User Keys' label=_('User Keys')
) )
secret_key = forms.CharField( secret_key = forms.CharField(
widget=forms.Textarea( widget=forms.Textarea(
@ -228,5 +230,5 @@ class ActivateUserKeyForm(forms.Form):
'class': 'vLargeTextField', 'class': 'vLargeTextField',
} }
), ),
label='Your private key' label=_('Your private key')
) )

View File

@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.translation import gettext as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.models import Device from dcim.models import Device
@ -51,7 +52,7 @@ class UserKey(models.Model):
editable=False editable=False
) )
public_key = models.TextField( public_key = models.TextField(
verbose_name='RSA public key' verbose_name=_('RSA public key')
) )
master_key_cipher = models.BinaryField( master_key_cipher = models.BinaryField(
max_length=512, max_length=512,

View File

@ -1,4 +1,5 @@
import django_tables2 as tables import django_tables2 as tables
from django.utils.translation import gettext as _
from utilities.tables import BaseTable, TagColumn, ToggleColumn from utilities.tables import BaseTable, TagColumn, ToggleColumn
from .models import SecretRole, Secret from .models import SecretRole, Secret
@ -21,7 +22,7 @@ class SecretRoleTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
secret_count = tables.Column( secret_count = tables.Column(
verbose_name='Secrets' verbose_name=_('Secrets')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=SECRETROLE_ACTIONS, template_code=SECRETROLE_ACTIONS,

View File

@ -6,6 +6,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic import View from django.views.generic import View
from utilities.views import ( from utilities.views import (
@ -117,7 +118,7 @@ def secret_add(request):
secret.save() secret.save()
form.save_m2m() form.save_m2m()
messages.success(request, "Added new secret: {}.".format(secret)) messages.success(request, _("Added new secret: {}.").format(secret))
if '_addanother' in request.POST: if '_addanother' in request.POST:
return redirect('secrets:secret_add') return redirect('secrets:secret_add')
else: else:
@ -164,7 +165,7 @@ def secret_edit(request, pk):
secret.plaintext = form.cleaned_data['plaintext'] secret.plaintext = form.cleaned_data['plaintext']
secret.encrypt(master_key) secret.encrypt(master_key)
secret.save() secret.save()
messages.success(request, "Modified secret {}.".format(secret)) messages.success(request, _("Modified secret {}.").format(secret))
return redirect('secrets:secret', pk=secret.pk) return redirect('secrets:secret', pk=secret.pk)
else: else:
form.add_error(None, "Invalid session key. Unable to encrypt secret data.") form.add_error(None, "Invalid session key. Unable to encrypt secret data.")

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
@ -16,13 +17,13 @@ __all__ = (
class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Tenant group (ID)', label=_('Tenant group (ID)'),
) )
parent = django_filters.ModelMultipleChoiceFilter( parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug', field_name='parent__slug',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant group group (slug)', label=_('Tenant group group (slug)'),
) )
class Meta: class Meta:
@ -33,20 +34,20 @@ class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class TenantFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class TenantFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
group_id = TreeNodeMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='group', field_name='group',
lookup_expr='in', lookup_expr='in',
label='Tenant group (ID)', label=_('Tenant group (ID)'),
) )
group = TreeNodeMultipleChoiceFilter( group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='group', field_name='group',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Tenant group (slug)', label=_('Tenant group (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -73,22 +74,22 @@ class TenancyFilterSet(django_filters.FilterSet):
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='tenant__group', field_name='tenant__group',
lookup_expr='in', lookup_expr='in',
label='Tenant Group (ID)', label=_('Tenant Group (ID)'),
) )
tenant_group = TreeNodeMultipleChoiceFilter( tenant_group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='tenant__group', field_name='tenant__group',
to_field_name='slug', to_field_name='slug',
lookup_expr='in', lookup_expr='in',
label='Tenant Group (slug)', label=_('Tenant Group (slug)'),
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label=_('Tenant (ID)'),
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
field_name='tenant__slug', field_name='tenant__slug',
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label=_('Tenant (slug)'),
) )

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.utils.translation import gettext as _
from extras.forms import ( from extras.forms import (
AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm, AddRemoveTagsForm, CustomFieldModelForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelCSVForm,
@ -37,7 +38,7 @@ class TenantGroupCSVForm(CSVModelForm):
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Parent group' help_text=_('Parent group')
) )
slug = SlugField() slug = SlugField()
@ -74,7 +75,7 @@ class TenantCSVForm(CustomFieldModelCSVForm):
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Assigned group' help_text=_('Assigned group')
) )
class Meta: class Meta:
@ -102,7 +103,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Tenant model = Tenant
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
group = DynamicModelMultipleChoiceField( group = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),

View File

@ -1,4 +1,5 @@
import django_tables2 as tables import django_tables2 as tables
from django.utils.translation import gettext as _
from utilities.tables import BaseTable, TagColumn, ToggleColumn from utilities.tables import BaseTable, TagColumn, ToggleColumn
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -42,7 +43,7 @@ class TenantGroupTable(BaseTable):
orderable=False orderable=False
) )
tenant_count = tables.Column( tenant_count = tables.Column(
verbose_name='Tenants' verbose_name=_('Tenants')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=TENANTGROUP_ACTIONS, template_code=TENANTGROUP_ACTIONS,

View File

@ -2,6 +2,7 @@ from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.admin import UserAdmin as UserAdmin_
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from .models import Token, UserConfig from .models import Token, UserConfig
@ -13,7 +14,7 @@ class UserConfigInline(admin.TabularInline):
model = UserConfig model = UserConfig
readonly_fields = ('data',) readonly_fields = ('data',)
can_delete = False can_delete = False
verbose_name = 'Preferences' verbose_name = _('Preferences')
@admin.register(User) @admin.register(User)
@ -27,7 +28,7 @@ class UserAdmin(UserAdmin_):
class TokenAdminForm(forms.ModelForm): class TokenAdminForm(forms.ModelForm):
key = forms.CharField( key = forms.CharField(
required=False, required=False,
help_text="If no key is provided, one will be generated automatically." help_text=_('If no key is provided, one will be generated automatically.')
) )
class Meta: class Meta:

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
from django.utils.translation import gettext as _
from utilities.forms import BootstrapMixin, DateTimePicker from utilities.forms import BootstrapMixin, DateTimePicker
from .models import Token from .models import Token
@ -21,7 +22,7 @@ class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
class TokenForm(BootstrapMixin, forms.ModelForm): class TokenForm(BootstrapMixin, forms.ModelForm):
key = forms.CharField( key = forms.CharField(
required=False, required=False,
help_text="If no key is provided, one will be generated automatically." help_text=_('If no key is provided, one will be generated automatically.')
) )
class Meta: class Meta:

View File

@ -8,6 +8,7 @@ from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _
from utilities.utils import flatten_dict from utilities.utils import flatten_dict
@ -162,7 +163,7 @@ class Token(models.Model):
) )
write_enabled = models.BooleanField( write_enabled = models.BooleanField(
default=True, default=True,
help_text='Permit create/update/delete operations using this key' help_text=_('Permit create/update/delete operations using this key')
) )
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,

View File

@ -11,6 +11,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.http import is_safe_url from django.utils.http import is_safe_url
from django.utils.translation import gettext as _
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View from django.views.generic import View
@ -64,7 +65,7 @@ class LoginView(View):
# Authenticate user # Authenticate user
auth_login(request, form.get_user()) auth_login(request, form.get_user())
logger.info(f"User {request.user} successfully authenticated") logger.info(f"User {request.user} successfully authenticated")
messages.info(request, "Logged in as {}.".format(request.user)) messages.info(request, _("Logged in as {}.").format(request.user))
logger.debug(f"Redirecting user to {redirect_to}") logger.debug(f"Redirecting user to {redirect_to}")
return HttpResponseRedirect(redirect_to) return HttpResponseRedirect(redirect_to)
@ -88,7 +89,7 @@ class LogoutView(View):
username = request.user username = request.user
auth_logout(request) auth_logout(request)
logger.info(f"User {username} has logged out") logger.info(f"User {username} has logged out")
messages.info(request, "You have logged out.") messages.info(request, _('You have logged out.'))
# Delete session key cookie (if set) upon logout # Delete session key cookie (if set) upon logout
response = HttpResponseRedirect(reverse('home')) response = HttpResponseRedirect(reverse('home'))
@ -130,7 +131,7 @@ class UserConfigView(LoginRequiredMixin, View):
if key in data: if key in data:
userconfig.clear(key) userconfig.clear(key)
userconfig.save() userconfig.save()
messages.success(request, "Your preferences have been updated.") messages.success(request, _('Your preferences have been updated.'))
return redirect('user:preferences') return redirect('user:preferences')
@ -156,7 +157,7 @@ class ChangePasswordView(LoginRequiredMixin, View):
if form.is_valid(): if form.is_valid():
form.save() form.save()
update_session_auth_hash(request, form.user) update_session_auth_hash(request, form.user)
messages.success(request, "Your password has been changed successfully.") messages.success(request, _('Your password has been changed successfully.'))
return redirect('user:profile') return redirect('user:profile')
return render(request, self.template_name, { return render(request, self.template_name, {
@ -206,7 +207,7 @@ class UserKeyEditView(LoginRequiredMixin, View):
uk = form.save(commit=False) uk = form.save(commit=False)
uk.user = request.user uk.user = request.user
uk.save() uk.save()
messages.success(request, "Your user key has been saved.") messages.success(request, _('Your user key has been saved.'))
return redirect('user:userkey') return redirect('user:userkey')
return render(request, self.template_name, { return render(request, self.template_name, {
@ -237,7 +238,7 @@ class SessionKeyDeleteView(LoginRequiredMixin, View):
# Delete session key # Delete session key
sessionkey.delete() sessionkey.delete()
messages.success(request, "Session key deleted") messages.success(request, _('Session key deleted'))
# Delete cookie # Delete cookie
response = redirect('user:userkey') response = redirect('user:userkey')
@ -304,7 +305,7 @@ class TokenEditView(LoginRequiredMixin, View):
token.user = request.user token.user = request.user
token.save() token.save()
msg = "Modified token {}".format(token) if pk else "Created token {}".format(token) msg = _("Modified token {}").format(token) if pk else _("Created token {}").format(token)
messages.success(request, msg) messages.success(request, msg)
if '_addanother' in request.POST: if '_addanother' in request.POST:
@ -344,7 +345,7 @@ class TokenDeleteView(PermissionRequiredMixin, View):
form = ConfirmationForm(request.POST) form = ConfirmationForm(request.POST)
if form.is_valid(): if form.is_valid():
token.delete() token.delete()
messages.success(request, "Token deleted") messages.success(request, _('Token deleted'))
return redirect('user:token_list') return redirect('user:token_list')
return render(request, 'utilities/obj_delete.html', { return render(request, 'utilities/obj_delete.html', {

View File

@ -5,6 +5,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django_filters.utils import get_model_field, resolve_field from django_filters.utils import get_model_field, resolve_field
from django.utils.translation import gettext as _
from extras.models import Tag from extras.models import Tag
from utilities.constants import ( from utilities.constants import (
@ -274,7 +275,7 @@ class NameSlugSearchFilterSet(django_filters.FilterSet):
""" """
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
def search(self, queryset, name, value): def search(self, queryset, name, value):

View File

@ -14,6 +14,7 @@ from django.db.models import Count
from django.forms import BoundField from django.forms import BoundField
from django.forms.models import fields_for_model from django.forms.models import fields_for_model
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from .choices import ColorChoices, unpack_grouped_choices from .choices import ColorChoices, unpack_grouped_choices
from .validators import EnhancedURLValidator from .validators import EnhancedURLValidator
@ -587,7 +588,7 @@ class TagFilterField(forms.MultipleChoiceField):
return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags] return [(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags]
# 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 DynamicModelChoiceMixin: class DynamicModelChoiceMixin:
@ -740,7 +741,7 @@ class ImportForm(BootstrapMixin, forms.Form):
""" """
data = forms.CharField( data = forms.CharField(
widget=forms.Textarea, widget=forms.Textarea,
help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported." help_text=_('Enter object data in JSON or YAML format. Note: Only a single object/document is supported.')
) )
format = forms.ChoiceField( format = forms.ChoiceField(
choices=( choices=(
@ -791,7 +792,7 @@ class TableConfigForm(BootstrapMixin, forms.Form):
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={'size': 10} attrs={'size': 10}
), ),
help_text="Use the buttons below to arrange columns in the desired order, then select all columns to display." help_text=_('Use the buttons below to arrange columns in the desired order, then select all columns to display.')
) )
def __init__(self, table, *args, **kwargs): def __init__(self, table, *args, **kwargs):

View File

@ -18,6 +18,7 @@ from django.utils.decorators import method_decorator
from django.utils.html import escape from django.utils.html import escape
from django.utils.http import is_safe_url from django.utils.http import is_safe_url
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django.views.decorators.csrf import requires_csrf_token from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import ERROR_500_TEMPLATE_NAME from django.views.defaults import ERROR_500_TEMPLATE_NAME
from django.views.generic import View from django.views.generic import View
@ -206,7 +207,7 @@ class ObjectListView(View):
request.user.config.set(preference_name, form.cleaned_data['columns'], commit=True) request.user.config.set(preference_name, form.cleaned_data['columns'], commit=True)
elif 'clear' in request.POST: elif 'clear' in request.POST:
request.user.config.clear(preference_name, commit=True) request.user.config.clear(preference_name, commit=True)
messages.success(request, "Your preferences have been updated.") messages.success(request, _('Your preferences have been updated.'))
return redirect(request.get_full_path()) return redirect(request.get_full_path())
@ -532,7 +533,7 @@ class ObjectImportView(GetReturnURLMixin, View):
if not model_form.errors: if not model_form.errors:
logger.info(f"Import object {obj} (PK: {obj.pk})") logger.info(f"Import object {obj} (PK: {obj.pk})")
messages.success(request, mark_safe('Imported object: <a href="{}">{}</a>'.format( messages.success(request, mark_safe(_('Imported object: <a href="{}">{}</a>').format(
obj.get_absolute_url(), obj obj.get_absolute_url(), obj
))) )))
@ -946,7 +947,7 @@ class ComponentCreateView(GetReturnURLMixin, View):
for component_form in new_components: for component_form in new_components:
component_form.save() component_form.save()
messages.success(request, "Added {} {}".format( messages.success(request, _('Added {} {}').format(
len(new_components), self.model._meta.verbose_name_plural len(new_components), self.model._meta.verbose_name_plural
)) ))
if '_addanother' in request.POST: if '_addanother' in request.POST:

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _
from dcim.models import DeviceRole, Interface, Platform, Region, Site from dcim.models import DeviceRole, Interface, Platform, Region, Site
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet
@ -37,50 +38,50 @@ class ClusterGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug', field_name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Parent group (ID)', label=_('Parent group (ID)'),
) )
group = django_filters.ModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
field_name='group__slug', field_name='group__slug',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent group (slug)', label=_('Parent group (slug)'),
) )
type_id = django_filters.ModelMultipleChoiceFilter( type_id = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
label='Cluster type (ID)', label=_('Cluster type (ID)'),
) )
type = django_filters.ModelMultipleChoiceFilter( type = django_filters.ModelMultipleChoiceFilter(
field_name='type__slug', field_name='type__slug',
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Cluster type (slug)', label=_('Cluster type (slug)'),
) )
tag = TagFilter() tag = TagFilter()
@ -106,7 +107,7 @@ class VirtualMachineFilterSet(
): ):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=VirtualMachineStatusChoices, choices=VirtualMachineStatusChoices,
@ -115,76 +116,76 @@ class VirtualMachineFilterSet(
cluster_group_id = django_filters.ModelMultipleChoiceFilter( cluster_group_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__group', field_name='cluster__group',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Cluster group (ID)', label=_('Cluster group (ID)'),
) )
cluster_group = django_filters.ModelMultipleChoiceFilter( cluster_group = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__group__slug', field_name='cluster__group__slug',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Cluster group (slug)', label=_('Cluster group (slug)'),
) )
cluster_type_id = django_filters.ModelMultipleChoiceFilter( cluster_type_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__type', field_name='cluster__type',
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
label='Cluster type (ID)', label=_('Cluster type (ID)'),
) )
cluster_type = django_filters.ModelMultipleChoiceFilter( cluster_type = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__type__slug', field_name='cluster__type__slug',
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Cluster type (slug)', label=_('Cluster type (slug)'),
) )
cluster_id = django_filters.ModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='Cluster (ID)', label=_('Cluster (ID)'),
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='cluster__site__region', field_name='cluster__site__region',
lookup_expr='in', lookup_expr='in',
label='Region (ID)', label=_('Region (ID)'),
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='cluster__site__region', field_name='cluster__site__region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label=_('Region (slug)'),
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__site', field_name='cluster__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label=_('Site (ID)'),
) )
site = django_filters.ModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__site__slug', field_name='cluster__site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label=_('Site (slug)'),
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
label='Role (ID)', label=_('Role (ID)'),
) )
role = django_filters.ModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug', field_name='role__slug',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label=_('Role (slug)'),
) )
platform_id = django_filters.ModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label=_('Platform (ID)'),
) )
platform = django_filters.ModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
field_name='platform__slug', field_name='platform__slug',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label=_('Platform (slug)'),
) )
mac_address = MultiValueMACAddressFilter( mac_address = MultiValueMACAddressFilter(
field_name='interfaces__mac_address', field_name='interfaces__mac_address',
label='MAC address', label=_('MAC address'),
) )
tag = TagFilter() tag = TagFilter()
@ -204,21 +205,21 @@ class VirtualMachineFilterSet(
class InterfaceFilterSet(BaseFilterSet): class InterfaceFilterSet(BaseFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label=_('Search'),
) )
virtual_machine_id = django_filters.ModelMultipleChoiceFilter( virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_machine', field_name='virtual_machine',
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
label='Virtual machine (ID)', label=_('Virtual machine (ID)'),
) )
virtual_machine = django_filters.ModelMultipleChoiceFilter( virtual_machine = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_machine__name', field_name='virtual_machine__name',
queryset=VirtualMachine.objects.all(), queryset=VirtualMachine.objects.all(),
to_field_name='name', to_field_name='name',
label='Virtual machine', label=_('Virtual machine'),
) )
mac_address = MultiValueMACAddressFilter( mac_address = MultiValueMACAddressFilter(
label='MAC address', label=_('MAC address'),
) )
tag = TagFilter() tag = TagFilter()

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext 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
@ -98,25 +99,25 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
type = CSVModelChoiceField( type = CSVModelChoiceField(
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(
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')
) )
site = CSVModelChoiceField( site = CSVModelChoiceField(
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(
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')
) )
class Meta: class Meta:
@ -147,7 +148,7 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
) )
comments = CommentField( comments = CommentField(
widget=SmallTextarea, widget=SmallTextarea,
label='Comments' label=_('Comments')
) )
class Meta: class Meta:
@ -387,12 +388,12 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
status = CSVChoiceField( status = CSVChoiceField(
choices=VirtualMachineStatusChoices, choices=VirtualMachineStatusChoices,
required=False, required=False,
help_text='Operational status of device' help_text=_('Operational status of device')
) )
cluster = CSVModelChoiceField( cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
to_field_name='name', to_field_name='name',
help_text='Assigned cluster' help_text=_('Assigned cluster')
) )
role = CSVModelChoiceField( role = CSVModelChoiceField(
queryset=DeviceRole.objects.filter( queryset=DeviceRole.objects.filter(
@ -400,19 +401,19 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
), ),
required=False, required=False,
to_field_name='name', to_field_name='name',
help_text='Functional role' help_text=_('Functional role')
) )
tenant = CSVModelChoiceField( tenant = CSVModelChoiceField(
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(
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')
) )
class Meta: class Meta:
@ -456,19 +457,19 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
) )
vcpus = forms.IntegerField( vcpus = forms.IntegerField(
required=False, required=False,
label='vCPUs' label=_('vCPUs')
) )
memory = forms.IntegerField( memory = forms.IntegerField(
required=False, required=False,
label='Memory (MB)' label=_('Memory (MB)')
) )
disk = forms.IntegerField( disk = forms.IntegerField(
required=False, required=False,
label='Disk (GB)' label=_('Disk (GB)')
) )
comments = CommentField( comments = CommentField(
widget=SmallTextarea, widget=SmallTextarea,
label='Comments' label=_('Comments')
) )
class Meta: class Meta:
@ -485,7 +486,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label=_('Search')
) )
cluster_group = DynamicModelMultipleChoiceField( cluster_group = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
@ -508,7 +509,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
cluster_id = DynamicModelMultipleChoiceField( cluster_id = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
required=False, required=False,
label='Cluster' label=_('Cluster')
) )
region = DynamicModelMultipleChoiceField( region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -558,7 +559,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
) )
mac_address = forms.CharField( mac_address = forms.CharField(
required=False, required=False,
label='MAC address' label=_('MAC address')
) )
tag = TagFilterField(model) tag = TagFilterField(model)
@ -606,7 +607,7 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
'mode': StaticSelect2() 'mode': StaticSelect2()
} }
labels = { labels = {
'mode': '802.1Q Mode', 'mode': _('802.1Q Mode'),
} }
help_texts = { help_texts = {
'mode': INTERFACE_MODE_HELP_TEXT, 'mode': INTERFACE_MODE_HELP_TEXT,
@ -645,7 +646,7 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form):
widget=forms.HiddenInput() widget=forms.HiddenInput()
) )
name_pattern = ExpandableNameField( name_pattern = ExpandableNameField(
label='Name' label=_('Name')
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=VMInterfaceTypeChoices, choices=VMInterfaceTypeChoices,
@ -660,11 +661,11 @@ class InterfaceCreateForm(BootstrapMixin, forms.Form):
required=False, required=False,
min_value=INTERFACE_MTU_MIN, min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX, max_value=INTERFACE_MTU_MAX,
label='MTU' label=_('MTU')
) )
mac_address = forms.CharField( mac_address = forms.CharField(
required=False, required=False,
label='MAC Address' label=_('MAC Address')
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -732,7 +733,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
required=False, required=False,
min_value=INTERFACE_MTU_MIN, min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX, max_value=INTERFACE_MTU_MAX,
label='MTU' label=_('MTU')
) )
description = forms.CharField( description = forms.CharField(
max_length=100, max_length=100,
@ -795,7 +796,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
name_pattern = ExpandableNameField( name_pattern = ExpandableNameField(
label='Name' label=_('Name')
) )
def clean_tags(self): def clean_tags(self):

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.models import Device from dcim.models import Device
@ -220,7 +221,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
max_length=50, max_length=50,
choices=VirtualMachineStatusChoices, choices=VirtualMachineStatusChoices,
default=VirtualMachineStatusChoices.STATUS_ACTIVE, default=VirtualMachineStatusChoices.STATUS_ACTIVE,
verbose_name='Status' verbose_name=_('Status')
) )
role = models.ForeignKey( role = models.ForeignKey(
to='dcim.DeviceRole', to='dcim.DeviceRole',
@ -236,7 +237,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
related_name='+', related_name='+',
blank=True, blank=True,
null=True, null=True,
verbose_name='Primary IPv4' verbose_name=_('Primary IPv4')
) )
primary_ip6 = models.OneToOneField( primary_ip6 = models.OneToOneField(
to='ipam.IPAddress', to='ipam.IPAddress',
@ -244,22 +245,22 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
related_name='+', related_name='+',
blank=True, blank=True,
null=True, null=True,
verbose_name='Primary IPv6' verbose_name=_('Primary IPv6')
) )
vcpus = models.PositiveSmallIntegerField( vcpus = models.PositiveSmallIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name='vCPUs' verbose_name=_('vCPUs')
) )
memory = models.PositiveIntegerField( memory = models.PositiveIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name='Memory (MB)' verbose_name=_('Memory (MB)')
) )
disk = models.PositiveIntegerField( disk = models.PositiveIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name='Disk (GB)' verbose_name=_('Disk (GB)')
) )
comments = models.TextField( comments = models.TextField(
blank=True blank=True

View File

@ -1,5 +1,6 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from django.utils.translation import gettext as _
from dcim.models import Interface from dcim.models import Interface
from tenancy.tables import COL_TENANT from tenancy.tables import COL_TENANT
@ -51,7 +52,7 @@ class ClusterTypeTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
cluster_count = tables.Column( cluster_count = tables.Column(
verbose_name='Clusters' verbose_name=_('Clusters')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=CLUSTERTYPE_ACTIONS, template_code=CLUSTERTYPE_ACTIONS,
@ -73,7 +74,7 @@ class ClusterGroupTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn() name = tables.LinkColumn()
cluster_count = tables.Column( cluster_count = tables.Column(
verbose_name='Clusters' verbose_name=_('Clusters')
) )
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=CLUSTERGROUP_ACTIONS, template_code=CLUSTERGROUP_ACTIONS,
@ -104,11 +105,11 @@ class ClusterTable(BaseTable):
) )
device_count = tables.TemplateColumn( device_count = tables.TemplateColumn(
template_code=CLUSTER_DEVICE_COUNT, template_code=CLUSTER_DEVICE_COUNT,
verbose_name='Devices' verbose_name=_('Devices')
) )
vm_count = tables.TemplateColumn( vm_count = tables.TemplateColumn(
template_code=CLUSTER_VM_COUNT, template_code=CLUSTER_VM_COUNT,
verbose_name='VMs' verbose_name=_('VMs')
) )
tags = TagColumn( tags = TagColumn(
url_name='virtualization:cluster_list' url_name='virtualization:cluster_list'
@ -148,16 +149,16 @@ class VirtualMachineDetailTable(VirtualMachineTable):
primary_ip4 = tables.LinkColumn( primary_ip4 = tables.LinkColumn(
viewname='ipam:ipaddress', viewname='ipam:ipaddress',
args=[Accessor('primary_ip4.pk')], args=[Accessor('primary_ip4.pk')],
verbose_name='IPv4 Address' verbose_name=_('IPv4 Address')
) )
primary_ip6 = tables.LinkColumn( primary_ip6 = tables.LinkColumn(
viewname='ipam:ipaddress', viewname='ipam:ipaddress',
args=[Accessor('primary_ip6.pk')], args=[Accessor('primary_ip6.pk')],
verbose_name='IPv6 Address' verbose_name=_('IPv6 Address')
) )
primary_ip = tables.TemplateColumn( primary_ip = tables.TemplateColumn(
orderable=False, orderable=False,
verbose_name='IP Address', verbose_name=_('IP Address'),
template_code=VIRTUALMACHINE_PRIMARY_IP template_code=VIRTUALMACHINE_PRIMARY_IP
) )
tags = TagColumn( tags = TagColumn(

View File

@ -4,6 +4,7 @@ from django.db import transaction
from django.db.models import Count from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic import View from django.views.generic import View
from dcim.models import Device, Interface from dcim.models import Device, Interface
@ -195,7 +196,7 @@ class ClusterAddDevicesView(PermissionRequiredMixin, View):
device.cluster = cluster device.cluster = cluster
device.save() device.save()
messages.success(request, "Added {} devices to cluster {}".format( messages.success(request, _('Added {} devices to cluster {}').format(
len(device_pks), cluster len(device_pks), cluster
)) ))
return redirect(cluster.get_absolute_url()) return redirect(cluster.get_absolute_url())
@ -228,7 +229,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View):
device.cluster = None device.cluster = None
device.save() device.save()
messages.success(request, "Removed {} devices from cluster {}".format( messages.success(request, _('Removed {} devices from cluster {}').format(
len(device_pks), cluster len(device_pks), cluster
)) ))
return redirect(cluster.get_absolute_url()) return redirect(cluster.get_absolute_url())