Jeremy Stretch 7 лет назад
Родитель
Сommit
4da755e75f
8 измененных файлов с 1210 добавлено и 339 удалено
  1. 119 32
      netbox/circuits/forms.py
  2. 422 117
      netbox/dcim/forms.py
  3. 13 4
      netbox/extras/forms.py
  4. 347 100
      netbox/ipam/forms.py
  5. 48 13
      netbox/secrets/forms.py
  6. 31 9
      netbox/tenancy/forms.py
  7. 7 2
      netbox/users/forms.py
  8. 223 62
      netbox/virtualization/forms.py

+ 119 - 32
netbox/circuits/forms.py

@@ -21,14 +21,22 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
 class ProviderForm(BootstrapMixin, CustomFieldForm):
     slug = SlugField()
     comments = CommentField()
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Provider
-        fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags']
+        fields = [
+            'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
+        ]
         widgets = {
-            'noc_contact': SmallTextarea(attrs={'rows': 5}),
-            'admin_contact': SmallTextarea(attrs={'rows': 5}),
+            'noc_contact': SmallTextarea(
+                attrs={'rows': 5}
+            ),
+            'admin_contact': SmallTextarea(
+                attrs={'rows': 5}
+            ),
         }
         help_texts = {
             'name': "Full name of the provider",
@@ -54,23 +62,57 @@ class ProviderCSVForm(forms.ModelForm):
 
 
 class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
-    asn = forms.IntegerField(required=False, label='ASN')
-    account = forms.CharField(max_length=30, required=False, label='Account number')
-    portal_url = forms.URLField(required=False, label='Portal')
-    noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
-    admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
-    comments = CommentField(widget=SmallTextarea)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Provider.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    asn = forms.IntegerField(
+        required=False,
+        label='ASN'
+    )
+    account = forms.CharField(
+        max_length=30,
+        required=False,
+        label='Account number'
+    )
+    portal_url = forms.URLField(
+        required=False,
+        label='Portal'
+    )
+    noc_contact = forms.CharField(
+        required=False,
+        widget=SmallTextarea,
+        label='NOC contact'
+    )
+    admin_contact = forms.CharField(
+        required=False,
+        widget=SmallTextarea,
+        label='Admin contact'
+    )
+    comments = CommentField(
+        widget=SmallTextarea()
+    )
 
     class Meta:
-        nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
+        nullable_fields = [
+            'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
+        ]
 
 
 class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Provider
-    q = forms.CharField(required=False, label='Search')
-    site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
-    asn = forms.IntegerField(required=False, label='ASN')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+    site = FilterChoiceField(
+        queryset=Site.objects.all(),
+        to_field_name='slug'
+    )
+    asn = forms.IntegerField(
+        required=False,
+        label='ASN'
+    )
 
 
 #
@@ -82,7 +124,9 @@ class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = CircuitType
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class CircuitTypeCSVForm(forms.ModelForm):
@@ -102,7 +146,9 @@ class CircuitTypeCSVForm(forms.ModelForm):
 
 class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     comments = CommentField()
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Circuit
@@ -157,28 +203,61 @@ class CircuitCSVForm(forms.ModelForm):
 
 
 class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
-    type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
-    provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
-    status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='')
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
-    description = forms.CharField(max_length=100, required=False)
-    comments = CommentField(widget=SmallTextarea)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Circuit.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    type = forms.ModelChoiceField(
+        queryset=CircuitType.objects.all(),
+        required=False
+    )
+    provider = forms.ModelChoiceField(
+        queryset=Provider.objects.all(),
+        required=False
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(CIRCUIT_STATUS_CHOICES),
+        required=False,
+        initial=''
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
+    commit_rate = forms.IntegerField(
+        required=False,
+        label='Commit rate (Kbps)'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea
+    )
 
     class Meta:
-        nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
+        nullable_fields = [
+            'tenant', 'commit_rate', 'description', 'comments',
+        ]
 
 
 class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Circuit
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     type = FilterChoiceField(
-        queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
+        queryset=CircuitType.objects.annotate(
+            filter_count=Count('circuits')
+        ),
         to_field_name='slug'
     )
     provider = FilterChoiceField(
-        queryset=Provider.objects.annotate(filter_count=Count('circuits')),
+        queryset=Provider.objects.annotate(
+            filter_count=Count('circuits')
+        ),
         to_field_name='slug'
     )
     status = AnnotatedMultipleChoiceField(
@@ -188,15 +267,23 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('circuits')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('circuit_terminations')
+        ),
         to_field_name='slug'
     )
-    commit_rate = forms.IntegerField(required=False, min_value=0, label='Commit rate (Kbps)')
+    commit_rate = forms.IntegerField(
+        required=False,
+        min_value=0,
+        label='Commit rate (Kbps)'
+    )
 
 
 #

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


+ 13 - 4
netbox/extras/forms.py

@@ -193,7 +193,9 @@ class TagForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = Tag
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class AddRemoveTagsForm(forms.Form):
@@ -208,7 +210,10 @@ class AddRemoveTagsForm(forms.Form):
 
 class TagFilterForm(BootstrapMixin, forms.Form):
     model = Tag
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
 
 
 #
@@ -249,7 +254,9 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
     )
 
     class Meta:
-        nullable_fields = ['description']
+        nullable_fields = [
+            'description',
+        ]
 
 
 class ConfigContextFilterForm(BootstrapMixin, forms.Form):
@@ -291,7 +298,9 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = ImageAttachment
-        fields = ['name', 'image']
+        fields = [
+            'name', 'image',
+        ]
 
 
 #

+ 347 - 100
netbox/ipam/forms.py

@@ -34,11 +34,15 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([(i, i) for i in range(1, 129)]
 #
 
 class VRFForm(BootstrapMixin, TenancyForm, CustomFieldForm):
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = VRF
-        fields = ['name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant', 'tags']
+        fields = [
+            'name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant', 'tags',
+        ]
         labels = {
             'rd': "RD",
         }
@@ -67,22 +71,40 @@ class VRFCSVForm(forms.ModelForm):
 
 
 class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=VRF.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
     enforce_unique = forms.NullBooleanField(
-        required=False, widget=BulkEditNullBooleanSelect, label='Enforce unique space'
+        required=False,
+        widget=BulkEditNullBooleanSelect(),
+        label='Enforce unique space'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
     )
-    description = forms.CharField(max_length=100, required=False)
 
     class Meta:
-        nullable_fields = ['tenant', 'description']
+        nullable_fields = [
+            'tenant', 'description',
+        ]
 
 
 class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = VRF
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('vrfs')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('vrfs')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -97,7 +119,9 @@ class RIRForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = RIR
-        fields = ['name', 'slug', 'is_private']
+        fields = [
+            'name', 'slug', 'is_private',
+        ]
 
 
 class RIRCSVForm(forms.ModelForm):
@@ -112,11 +136,17 @@ class RIRCSVForm(forms.ModelForm):
 
 
 class RIRFilterForm(BootstrapMixin, forms.Form):
-    is_private = forms.NullBooleanField(required=False, label='Private', widget=forms.Select(choices=[
-        ('', '---------'),
-        ('True', 'Yes'),
-        ('False', 'No'),
-    ]))
+    is_private = forms.NullBooleanField(
+        required=False,
+        label='Private',
+        widget=forms.Select(
+            choices=[
+                ('', '---------'),
+                ('True', 'Yes'),
+                ('False', 'No'),
+            ]
+        )
+    )
 
 
 #
@@ -124,11 +154,15 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
 #
 
 class AggregateForm(BootstrapMixin, CustomFieldForm):
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Aggregate
-        fields = ['prefix', 'rir', 'date_added', 'description', 'tags']
+        fields = [
+            'prefix', 'rir', 'date_added', 'description', 'tags',
+        ]
         help_texts = {
             'prefix': "IPv4 or IPv6 network",
             'rir': "Regional Internet Registry responsible for this prefix",
@@ -152,19 +186,40 @@ class AggregateCSVForm(forms.ModelForm):
 
 
 class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
-    rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR')
-    date_added = forms.DateField(required=False)
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Aggregate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    rir = forms.ModelChoiceField(
+        queryset=RIR.objects.all(),
+        required=False,
+        label='RIR'
+    )
+    date_added = forms.DateField(
+        required=False
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['date_added', 'description']
+        nullable_fields = [
+            'date_added', 'description',
+        ]
 
 
 class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Aggregate
-    q = forms.CharField(required=False, label='Search')
-    family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+    family = forms.ChoiceField(
+        required=False,
+        choices=IP_FAMILY_CHOICES,
+        label='Address family'
+    )
     rir = FilterChoiceField(
         queryset=RIR.objects.annotate(filter_count=Count('aggregates')),
         to_field_name='slug',
@@ -181,7 +236,9 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = Role
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class RoleCSVForm(forms.ModelForm):
@@ -205,7 +262,10 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         required=False,
         label='Site',
         widget=forms.Select(
-            attrs={'filter-for': 'vlan_group', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'vlan_group',
+                'nullable': 'true',
+            }
         )
     )
     vlan_group = ChainedModelChoiceField(
@@ -217,7 +277,10 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         label='VLAN group',
         widget=APISelect(
             api_url='/api/ipam/vlan-groups/?site_id={{site}}',
-            attrs={'filter-for': 'vlan', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'vlan',
+                'nullable': 'true',
+            }
         )
     )
     vlan = ChainedModelChoiceField(
@@ -229,7 +292,8 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         required=False,
         label='VLAN',
         widget=APISelect(
-            api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}', display_field='display_name'
+            api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}',
+            display_field='display_name'
         )
     )
     tags = TagField(required=False)
@@ -345,35 +409,84 @@ class PrefixCSVForm(forms.ModelForm):
 
 
 class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
-    site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
-    vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    status = forms.ChoiceField(choices=add_blank_choice(PREFIX_STATUS_CHOICES), required=False)
-    role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
-    is_pool = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a pool')
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Prefix.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    site = forms.ModelChoiceField(
+        queryset=Site.objects.all(),
+        required=False
+    )
+    vrf = forms.ModelChoiceField(
+        queryset=VRF.objects.all(),
+        required=False,
+        label='VRF'
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(PREFIX_STATUS_CHOICES),
+        required=False
+    )
+    role = forms.ModelChoiceField(
+        queryset=Role.objects.all(),
+        required=False
+    )
+    is_pool = forms.NullBooleanField(
+        required=False,
+        widget=BulkEditNullBooleanSelect(),
+        label='Is a pool'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['site', 'vrf', 'tenant', 'role', 'description']
+        nullable_fields = [
+            'site', 'vrf', 'tenant', 'role', 'description',
+        ]
 
 
 class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Prefix
-    q = forms.CharField(required=False, label='Search')
-    within_include = forms.CharField(required=False, label='Search within', widget=forms.TextInput(attrs={
-        'placeholder': 'Prefix',
-    }))
-    family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address family')
-    mask_length = forms.ChoiceField(required=False, choices=PREFIX_MASK_LENGTH_CHOICES, label='Mask length')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+    within_include = forms.CharField(
+        required=False,
+        widget=forms.TextInput(
+            attrs={
+                'placeholder': 'Prefix',
+            }
+        ),
+        label='Search within'
+    )
+    family = forms.ChoiceField(
+        required=False,
+        choices=IP_FAMILY_CHOICES,
+        label='Address family'
+    )
+    mask_length = forms.ChoiceField(
+        required=False,
+        choices=PREFIX_MASK_LENGTH_CHOICES,
+        label='Mask length'
+    )
     vrf = FilterChoiceField(
-        queryset=VRF.objects.annotate(filter_count=Count('prefixes')),
+        queryset=VRF.objects.annotate(
+            filter_count=Count('prefixes')
+        ),
         to_field_name='rd',
         label='VRF',
         null_label='-- Global --'
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('prefixes')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('prefixes')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -384,16 +497,23 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('prefixes')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('prefixes')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
     role = FilterChoiceField(
-        queryset=Role.objects.annotate(filter_count=Count('prefixes')),
+        queryset=Role.objects.annotate(
+            filter_count=Count('prefixes')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
-    expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
+    expand = forms.BooleanField(
+        required=False,
+        label='Expand prefix hierarchy'
+    )
 
 
 #
@@ -410,7 +530,9 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         required=False,
         label='Site',
         widget=forms.Select(
-            attrs={'filter-for': 'nat_rack'}
+            attrs={
+                'filter-for': 'nat_rack'
+            }
         )
     )
     nat_rack = ChainedModelChoiceField(
@@ -423,7 +545,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         widget=APISelect(
             api_url='/api/dcim/racks/?site_id={{nat_site}}',
             display_field='display_name',
-            attrs={'filter-for': 'nat_device', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'nat_device',
+                'nullable': 'true'
+            }
         )
     )
     nat_device = ChainedModelChoiceField(
@@ -462,8 +587,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
             obj_label='address'
         )
     )
-    primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM')
-    tags = TagField(required=False)
+    primary_for_parent = forms.BooleanField(
+        required=False,
+        label='Make this the primary IP for the device/VM'
+    )
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = IPAddress
@@ -538,14 +668,18 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
 
 
 class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
-    pattern = ExpandableIPAddressField(label='Address pattern')
+    pattern = ExpandableIPAddressField(
+        label='Address pattern'
+    )
 
 
 class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
 
     class Meta:
         model = IPAddress
-        fields = ['address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant']
+        fields = [
+            'address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant',
+        ]
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -612,7 +746,6 @@ class IPAddressCSVForm(forms.ModelForm):
         fields = IPAddress.csv_headers
 
     def clean(self):
-
         super().clean()
 
         device = self.cleaned_data.get('device')
@@ -677,38 +810,86 @@ class IPAddressCSVForm(forms.ModelForm):
 
 
 class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
-    vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    status = forms.ChoiceField(choices=add_blank_choice(IPADDRESS_STATUS_CHOICES), required=False)
-    role = forms.ChoiceField(choices=add_blank_choice(IPADDRESS_ROLE_CHOICES), required=False)
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=IPAddress.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    vrf = forms.ModelChoiceField(
+        queryset=VRF.objects.all(),
+        required=False,
+        label='VRF'
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
+        required=False
+    )
+    role = forms.ChoiceField(
+        choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
+        required=False
+    )
+    description = forms.CharField(
+        max_length=100, required=False
+    )
 
     class Meta:
-        nullable_fields = ['vrf', 'role', 'tenant', 'description']
+        nullable_fields = [
+            'vrf', 'role', 'tenant', 'description',
+        ]
 
 
 class IPAddressAssignForm(BootstrapMixin, forms.Form):
-    vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF', empty_label='Global')
-    address = forms.CharField(label='IP Address')
+    vrf = forms.ModelChoiceField(
+        queryset=VRF.objects.all(),
+        required=False,
+        label='VRF',
+        empty_label='Global'
+    )
+    address = forms.CharField(
+        label='IP Address'
+    )
 
 
 class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = IPAddress
-    q = forms.CharField(required=False, label='Search')
-    parent = forms.CharField(required=False, label='Parent Prefix', widget=forms.TextInput(attrs={
-        'placeholder': 'Prefix',
-    }))
-    family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address family')
-    mask_length = forms.ChoiceField(required=False, choices=IPADDRESS_MASK_LENGTH_CHOICES, label='Mask length')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+    parent = forms.CharField(
+        required=False,
+        widget=forms.TextInput(
+            attrs={
+                'placeholder': 'Prefix',
+            }
+        ),
+        label='Parent Prefix'
+    )
+    family = forms.ChoiceField(
+        required=False,
+        choices=IP_FAMILY_CHOICES,
+        label='Address family'
+    )
+    mask_length = forms.ChoiceField(
+        required=False,
+        choices=IPADDRESS_MASK_LENGTH_CHOICES,
+        label='Mask length'
+    )
     vrf = FilterChoiceField(
-        queryset=VRF.objects.annotate(filter_count=Count('ip_addresses')),
+        queryset=VRF.objects.annotate(
+            filter_count=Count('ip_addresses')
+        ),
         to_field_name='rd',
         label='VRF',
         null_label='-- Global --'
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('ip_addresses')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -735,7 +916,9 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = VLANGroup
-        fields = ['site', 'name', 'slug']
+        fields = [
+            'site', 'name', 'slug',
+        ]
 
 
 class VLANGroupCSVForm(forms.ModelForm):
@@ -760,7 +943,9 @@ class VLANGroupCSVForm(forms.ModelForm):
 
 class VLANGroupFilterForm(BootstrapMixin, forms.Form):
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('vlan_groups')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('vlan_groups')
+        ),
         to_field_name='slug',
         null_label='-- Global --'
     )
@@ -775,7 +960,10 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         queryset=Site.objects.all(),
         required=False,
         widget=forms.Select(
-            attrs={'filter-for': 'group', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'group',
+                'nullable': 'true',
+            }
         )
     )
     group = ChainedModelChoiceField(
@@ -793,7 +981,9 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
 
     class Meta:
         model = VLAN
-        fields = ['site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags']
+        fields = [
+            'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
+        ]
         help_texts = {
             'site': "Leave blank if this VLAN spans multiple sites",
             'group': "VLAN group (optional)",
@@ -850,7 +1040,6 @@ class VLANCSVForm(forms.ModelForm):
         }
 
     def clean(self):
-
         super().clean()
 
         site = self.cleaned_data.get('site')
@@ -862,39 +1051,75 @@ class VLANCSVForm(forms.ModelForm):
                 self.instance.group = VLANGroup.objects.get(site=site, name=group_name)
             except VLANGroup.DoesNotExist:
                 if site:
-                    raise forms.ValidationError("VLAN group {} not found for site {}".format(group_name, site))
+                    raise forms.ValidationError(
+                        "VLAN group {} not found for site {}".format(group_name, site)
+                    )
                 else:
-                    raise forms.ValidationError("Global VLAN group {} not found".format(group_name))
+                    raise forms.ValidationError(
+                        "Global VLAN group {} not found".format(group_name)
+                    )
 
 
 class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
-    site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
-    group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    status = forms.ChoiceField(choices=add_blank_choice(VLAN_STATUS_CHOICES), required=False)
-    role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=VLAN.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    site = forms.ModelChoiceField(
+        queryset=Site.objects.all(),
+        required=False
+    )
+    group = forms.ModelChoiceField(
+        queryset=VLANGroup.objects.all(),
+        required=False
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(VLAN_STATUS_CHOICES),
+        required=False
+    )
+    role = forms.ModelChoiceField(
+        queryset=Role.objects.all(),
+        required=False
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['site', 'group', 'tenant', 'role', 'description']
+        nullable_fields = [
+            'site', 'group', 'tenant', 'role', 'description',
+        ]
 
 
 class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = VLAN
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('vlans')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('vlans')
+        ),
         to_field_name='slug',
         null_label='-- Global --'
     )
     group_id = FilterChoiceField(
-        queryset=VLANGroup.objects.annotate(filter_count=Count('vlans')),
+        queryset=VLANGroup.objects.annotate(
+            filter_count=Count('vlans')
+        ),
         label='VLAN group',
         null_label='-- None --'
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('vlans')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('vlans')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -905,7 +1130,9 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False
     )
     role = FilterChoiceField(
-        queryset=Role.objects.annotate(filter_count=Count('vlans')),
+        queryset=Role.objects.annotate(
+            filter_count=Count('vlans')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -916,18 +1143,21 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
 #
 
 class ServiceForm(BootstrapMixin, CustomFieldForm):
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Service
-        fields = ['name', 'protocol', 'port', 'ipaddresses', 'description', 'tags']
+        fields = [
+            'name', 'protocol', 'port', 'ipaddresses', 'description', 'tags',
+        ]
         help_texts = {
             'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
                            "reachable via all IPs assigned to the device.",
         }
 
     def __init__(self, *args, **kwargs):
-
         super().__init__(*args, **kwargs)
 
         # Limit IP address choices to those assigned to interfaces of the parent device/VM
@@ -960,10 +1190,27 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
 
 
 class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), widget=forms.MultipleHiddenInput)
-    protocol = forms.ChoiceField(choices=add_blank_choice(IP_PROTOCOL_CHOICES), required=False)
-    port = forms.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(65535)], required=False)
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Service.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    protocol = forms.ChoiceField(
+        choices=add_blank_choice(IP_PROTOCOL_CHOICES),
+        required=False
+    )
+    port = forms.IntegerField(
+        validators=[
+            MinValueValidator(1),
+            MaxValueValidator(65535),
+        ],
+        required=False
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['site', 'group', 'tenant', 'role', 'description']
+        nullable_fields = [
+            'site', 'group', 'tenant', 'role', 'description',
+        ]

+ 48 - 13
netbox/secrets/forms.py

@@ -39,7 +39,9 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = SecretRole
-        fields = ['name', 'slug', 'users', 'groups']
+        fields = [
+            'name', 'slug', 'users', 'groups',
+        ]
 
 
 class SecretRoleCSVForm(forms.ModelForm):
@@ -62,7 +64,11 @@ class SecretForm(BootstrapMixin, CustomFieldForm):
         max_length=65535,
         required=False,
         label='Plaintext',
-        widget=forms.PasswordInput(attrs={'class': 'requires-session-key'})
+        widget=forms.PasswordInput(
+            attrs={
+                'class': 'requires-session-key',
+            }
+        )
     )
     plaintext2 = forms.CharField(
         max_length=65535,
@@ -70,14 +76,17 @@ class SecretForm(BootstrapMixin, CustomFieldForm):
         label='Plaintext (verify)',
         widget=forms.PasswordInput()
     )
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Secret
-        fields = ['role', 'name', 'plaintext', 'plaintext2', 'tags']
+        fields = [
+            'role', 'name', 'plaintext', 'plaintext2', 'tags',
+        ]
 
     def __init__(self, *args, **kwargs):
-
         super().__init__(*args, **kwargs)
 
         # A plaintext value is required when creating a new Secret
@@ -128,19 +137,35 @@ class SecretCSVForm(forms.ModelForm):
 
 
 class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
-    role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)
-    name = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Secret.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    role = forms.ModelChoiceField(
+        queryset=SecretRole.objects.all(),
+        required=False
+    )
+    name = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['name']
+        nullable_fields = [
+            'name',
+        ]
 
 
 class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Secret
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     role = FilterChoiceField(
-        queryset=SecretRole.objects.annotate(filter_count=Count('secrets')),
+        queryset=SecretRole.objects.annotate(
+            filter_count=Count('secrets')
+        ),
         to_field_name='slug'
     )
 
@@ -169,5 +194,15 @@ class UserKeyForm(BootstrapMixin, forms.ModelForm):
 
 
 class ActivateUserKeyForm(forms.Form):
-    _selected_action = forms.ModelMultipleChoiceField(queryset=UserKey.objects.all(), label='User Keys')
-    secret_key = forms.CharField(label='Your private key', widget=forms.Textarea(attrs={'class': 'vLargeTextField'}))
+    _selected_action = forms.ModelMultipleChoiceField(
+        queryset=UserKey.objects.all(),
+        label='User Keys'
+    )
+    secret_key = forms.CharField(
+        widget=forms.Textarea(
+            attrs={
+                'class': 'vLargeTextField',
+            }
+        ),
+        label='Your private key'
+    )

+ 31 - 9
netbox/tenancy/forms.py

@@ -18,7 +18,9 @@ class TenantGroupForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = TenantGroup
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class TenantGroupCSVForm(forms.ModelForm):
@@ -39,11 +41,15 @@ class TenantGroupCSVForm(forms.ModelForm):
 class TenantForm(BootstrapMixin, CustomFieldForm):
     slug = SlugField()
     comments = CommentField()
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Tenant
-        fields = ['name', 'slug', 'group', 'description', 'comments', 'tags']
+        fields = [
+            'name', 'slug', 'group', 'description', 'comments', 'tags',
+        ]
 
 
 class TenantCSVForm(forms.ModelForm):
@@ -68,18 +74,31 @@ class TenantCSVForm(forms.ModelForm):
 
 
 class TenantBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), widget=forms.MultipleHiddenInput)
-    group = forms.ModelChoiceField(queryset=TenantGroup.objects.all(), required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Tenant.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    group = forms.ModelChoiceField(
+        queryset=TenantGroup.objects.all(),
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['group']
+        nullable_fields = [
+            'group',
+        ]
 
 
 class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Tenant
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     group = FilterChoiceField(
-        queryset=TenantGroup.objects.annotate(filter_count=Count('tenants')),
+        queryset=TenantGroup.objects.annotate(
+            filter_count=Count('tenants')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -94,7 +113,10 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
         queryset=TenantGroup.objects.all(),
         required=False,
         widget=forms.Select(
-            attrs={'filter-for': 'tenant', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'tenant',
+                'nullable': 'true',
+            }
         )
     )
     tenant = ChainedModelChoiceField(

+ 7 - 2
netbox/users/forms.py

@@ -19,11 +19,16 @@ class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
 
 
 class TokenForm(BootstrapMixin, forms.ModelForm):
-    key = forms.CharField(required=False, help_text="If no key is provided, one will be generated automatically.")
+    key = forms.CharField(
+        required=False,
+        help_text="If no key is provided, one will be generated automatically."
+    )
 
     class Meta:
         model = Token
-        fields = ['key', 'write_enabled', 'expires', 'description']
+        fields = [
+            'key', 'write_enabled', 'expires', 'description',
+        ]
         help_texts = {
             'expires': 'YYYY-MM-DD [HH:MM:SS]'
         }

+ 223 - 62
netbox/virtualization/forms.py

@@ -34,7 +34,9 @@ class ClusterTypeForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = ClusterType
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class ClusterTypeCSVForm(forms.ModelForm):
@@ -57,7 +59,9 @@ class ClusterGroupForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = ClusterGroup
-        fields = ['name', 'slug']
+        fields = [
+            'name', 'slug',
+        ]
 
 
 class ClusterGroupCSVForm(forms.ModelForm):
@@ -76,12 +80,18 @@ class ClusterGroupCSVForm(forms.ModelForm):
 #
 
 class ClusterForm(BootstrapMixin, CustomFieldForm):
-    comments = CommentField(widget=SmallTextarea)
-    tags = TagField(required=False)
+    comments = CommentField(
+        widget=SmallTextarea()
+    )
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Cluster
-        fields = ['name', 'type', 'group', 'site', 'comments', 'tags']
+        fields = [
+            'name', 'type', 'group', 'site', 'comments', 'tags',
+        ]
 
 
 class ClusterCSVForm(forms.ModelForm):
@@ -118,32 +128,54 @@ class ClusterCSVForm(forms.ModelForm):
 
 
 class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Cluster.objects.all(), widget=forms.MultipleHiddenInput)
-    type = forms.ModelChoiceField(queryset=ClusterType.objects.all(), required=False)
-    group = forms.ModelChoiceField(queryset=ClusterGroup.objects.all(), required=False)
-    site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
-    comments = CommentField(widget=SmallTextarea)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Cluster.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ModelChoiceField(
+        queryset=ClusterType.objects.all(),
+        required=False
+    )
+    group = forms.ModelChoiceField(
+        queryset=ClusterGroup.objects.all(),
+        required=False
+    )
+    site = forms.ModelChoiceField(
+        queryset=Site.objects.all(),
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea()
+    )
 
     class Meta:
-        nullable_fields = ['group', 'site', 'comments']
+        nullable_fields = [
+            'group', 'site', 'comments',
+        ]
 
 
 class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Cluster
     q = forms.CharField(required=False, label='Search')
     type = FilterChoiceField(
-        queryset=ClusterType.objects.annotate(filter_count=Count('clusters')),
+        queryset=ClusterType.objects.annotate(
+            filter_count=Count('clusters')
+        ),
         to_field_name='slug',
         required=False,
     )
     group = FilterChoiceField(
-        queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),
+        queryset=ClusterGroup.objects.annotate(
+            filter_count=Count('clusters')
+        ),
         to_field_name='slug',
         null_label='-- None --',
         required=False,
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('clusters')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('clusters')
+        ),
         to_field_name='slug',
         null_label='-- None --',
         required=False,
@@ -155,7 +187,10 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         queryset=Region.objects.all(),
         required=False,
         widget=forms.Select(
-            attrs={'filter-for': 'site', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'site',
+                'nullable': 'true',
+            }
         )
     )
     site = ChainedModelChoiceField(
@@ -166,7 +201,9 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         required=False,
         widget=APISelect(
             api_url='/api/dcim/sites/?region_id={{region}}',
-            attrs={'filter-for': 'rack'}
+            attrs={
+                'filter-for': 'rack',
+            }
         )
     )
     rack = ChainedModelChoiceField(
@@ -177,7 +214,10 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         required=False,
         widget=APISelect(
             api_url='/api/dcim/racks/?site_id={{site}}',
-            attrs={'filter-for': 'devices', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'devices',
+                'nullable': 'true',
+            }
         )
     )
     devices = ChainedModelMultipleChoiceField(
@@ -194,7 +234,9 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
     )
 
     class Meta:
-        fields = ['region', 'site', 'rack', 'devices']
+        fields = [
+            'region', 'site', 'rack', 'devices',
+        ]
 
     def __init__(self, cluster, *args, **kwargs):
 
@@ -205,7 +247,6 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
         self.fields['devices'].choices = []
 
     def clean(self):
-
         super().clean()
 
         # If the Cluster is assigned to a Site, all Devices must be assigned to that Site.
@@ -220,7 +261,10 @@ class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
 
 
 class ClusterRemoveDevicesForm(ConfirmationForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Device.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
 
 
 #
@@ -232,7 +276,10 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         queryset=ClusterGroup.objects.all(),
         required=False,
         widget=forms.Select(
-            attrs={'filter-for': 'cluster', 'nullable': 'true'}
+            attrs={
+                'filter-for': 'cluster',
+                'nullable': 'true',
+            }
         )
     )
     cluster = ChainedModelChoiceField(
@@ -244,8 +291,12 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'
         )
     )
-    tags = TagField(required=False)
-    local_context_data = JSONField(required=False)
+    tags = TagField(
+        required=False
+    )
+    local_context_data = JSONField(
+        required=False
+    )
 
     class Meta:
         model = VirtualMachine
@@ -254,7 +305,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
             'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
         ]
         help_texts = {
-            'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context",
+            'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
+                                  "config context",
         }
 
     def __init__(self, *args, **kwargs):
@@ -319,7 +371,9 @@ class VirtualMachineCSVForm(forms.ModelForm):
         }
     )
     role = forms.ModelChoiceField(
-        queryset=DeviceRole.objects.filter(vm_role=True),
+        queryset=DeviceRole.objects.filter(
+            vm_role=True
+        ),
         required=False,
         to_field_name='name',
         help_text='Name of functional role',
@@ -352,24 +406,61 @@ class VirtualMachineCSVForm(forms.ModelForm):
 
 
 class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
-    status = forms.ChoiceField(choices=add_blank_choice(VM_STATUS_CHOICES), required=False, initial='')
-    cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)
-    role = forms.ModelChoiceField(queryset=DeviceRole.objects.filter(vm_role=True), required=False)
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
-    vcpus = forms.IntegerField(required=False, label='vCPUs')
-    memory = forms.IntegerField(required=False, label='Memory (MB)')
-    disk = forms.IntegerField(required=False, label='Disk (GB)')
-    comments = CommentField(widget=SmallTextarea)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=VirtualMachine.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(VM_STATUS_CHOICES),
+        required=False,
+        initial=''
+    )
+    cluster = forms.ModelChoiceField(
+        queryset=Cluster.objects.all(),
+        required=False
+    )
+    role = forms.ModelChoiceField(
+        queryset=DeviceRole.objects.filter(
+            vm_role=True
+        ),
+        required=False
+    )
+    tenant = forms.ModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
+    platform = forms.ModelChoiceField(
+        queryset=Platform.objects.all(),
+        required=False
+    )
+    vcpus = forms.IntegerField(
+        required=False,
+        label='vCPUs'
+    )
+    memory = forms.IntegerField(
+        required=False,
+        label='Memory (MB)'
+    )
+    disk = forms.IntegerField(
+        required=False,
+        label='Disk (GB)'
+    )
+    comments = CommentField(
+        widget=SmallTextarea()
+    )
 
     class Meta:
-        nullable_fields = ['role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
+        nullable_fields = [
+            'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
+        ]
 
 
 class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = VirtualMachine
-    q = forms.CharField(required=False, label='Search')
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
     cluster_group = FilterChoiceField(
         queryset=ClusterGroup.objects.all(),
         to_field_name='slug',
@@ -381,7 +472,9 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         null_label='-- None --'
     )
     cluster_id = FilterChoiceField(
-        queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
+        queryset=Cluster.objects.annotate(
+            filter_count=Count('virtual_machines')
+        ),
         label='Cluster'
     )
     region = FilterTreeNodeMultipleChoiceField(
@@ -390,12 +483,18 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False,
     )
     site = FilterChoiceField(
-        queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
+        queryset=Site.objects.annotate(
+            filter_count=Count('clusters__virtual_machines')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
     role = FilterChoiceField(
-        queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
+        queryset=DeviceRole.objects.filter(
+            vm_role=True
+        ).annotate(
+            filter_count=Count('virtual_machines')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -406,12 +505,16 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False
     )
     tenant = FilterChoiceField(
-        queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')),
+        queryset=Tenant.objects.annotate(
+            filter_count=Count('virtual_machines')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
     platform = FilterChoiceField(
-        queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')),
+        queryset=Platform.objects.annotate(
+            filter_count=Count('virtual_machines')
+        ),
         to_field_name='slug',
         null_label='-- None --'
     )
@@ -422,7 +525,9 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
 #
 
 class InterfaceForm(BootstrapMixin, forms.ModelForm):
-    tags = TagField(required=False)
+    tags = TagField(
+        required=False
+    )
 
     class Meta:
         model = Interface
@@ -442,7 +547,6 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
         }
 
     def clean(self):
-
         super().clean()
 
         # Validate VLAN assignments
@@ -460,13 +564,34 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
 
 
 class InterfaceCreateForm(ComponentForm):
-    name_pattern = ExpandableNameField(label='Name')
-    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())
-    enabled = forms.BooleanField(required=False)
-    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
-    mac_address = forms.CharField(required=False, label='MAC Address')
-    description = forms.CharField(max_length=100, required=False)
-    tags = TagField(required=False)
+    name_pattern = ExpandableNameField(
+        label='Name'
+    )
+    form_factor = forms.ChoiceField(
+        choices=VIFACE_FF_CHOICES,
+        initial=IFACE_FF_VIRTUAL,
+        widget=forms.HiddenInput()
+    )
+    enabled = forms.BooleanField(
+        required=False
+    )
+    mtu = forms.IntegerField(
+        required=False,
+        min_value=1,
+        max_value=32767,
+        label='MTU'
+    )
+    mac_address = forms.CharField(
+        required=False,
+        label='MAC Address'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
+    tags = TagField(
+        required=False
+    )
 
     def __init__(self, *args, **kwargs):
 
@@ -478,13 +603,29 @@ class InterfaceCreateForm(ComponentForm):
 
 
 class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
-    enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
-    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
-    description = forms.CharField(max_length=100, required=False)
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Interface.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    enabled = forms.NullBooleanField(
+        required=False,
+        widget=BulkEditNullBooleanSelect()
+    )
+    mtu = forms.IntegerField(
+        required=False,
+        min_value=1,
+        max_value=32767,
+        label='MTU'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
 
     class Meta:
-        nullable_fields = ['mtu', 'description']
+        nullable_fields = [
+            'mtu', 'description',
+        ]
 
 
 #
@@ -492,12 +633,32 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
 #
 
 class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
-    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
-    name_pattern = ExpandableNameField(label='Name')
+    pk = forms.ModelMultipleChoiceField(
+        queryset=VirtualMachine.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    name_pattern = ExpandableNameField(
+        label='Name'
+    )
 
 
 class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
-    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())
-    enabled = forms.BooleanField(required=False, initial=True)
-    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
-    description = forms.CharField(max_length=100, required=False)
+    form_factor = forms.ChoiceField(
+        choices=VIFACE_FF_CHOICES,
+        initial=IFACE_FF_VIRTUAL,
+        widget=forms.HiddenInput()
+    )
+    enabled = forms.BooleanField(
+        required=False,
+        initial=True
+    )
+    mtu = forms.IntegerField(
+        required=False,
+        min_value=1,
+        max_value=32767,
+        label='MTU'
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )

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