from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from timezone_field import TimeZoneFormField

from dcim.choices import *
from dcim.constants import *
from dcim.models import *
from extras.models import ConfigTemplate
from ipam.choices import VLANQinQRoleChoices
from ipam.models import ASN, VLAN, VRF, VLANGroup
from netbox.choices import *
from netbox.forms import (
    NestedGroupModelBulkEditForm,
    NetBoxModelBulkEditForm,
    OrganizationalModelBulkEditForm,
    PrimaryModelBulkEditForm,
)
from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
from tenancy.models import Tenant
from users.models import User
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
from virtualization.models import Cluster
from wireless.choices import WirelessRoleChoices
from wireless.models import WirelessLAN, WirelessLANGroup

__all__ = (
    'CableBulkEditForm',
    'CableBundleBulkEditForm',
    'ConsolePortBulkEditForm',
    'ConsolePortTemplateBulkEditForm',
    'ConsoleServerPortBulkEditForm',
    'ConsoleServerPortTemplateBulkEditForm',
    'DeviceBayBulkEditForm',
    'DeviceBayTemplateBulkEditForm',
    'DeviceBulkEditForm',
    'DeviceRoleBulkEditForm',
    'DeviceTypeBulkEditForm',
    'FrontPortBulkEditForm',
    'FrontPortTemplateBulkEditForm',
    'InterfaceBulkEditForm',
    'InterfaceTemplateBulkEditForm',
    'InventoryItemBulkEditForm',
    'InventoryItemRoleBulkEditForm',
    'InventoryItemTemplateBulkEditForm',
    'LocationBulkEditForm',
    'MACAddressBulkEditForm',
    'ManufacturerBulkEditForm',
    'ModuleBayBulkEditForm',
    'ModuleBayTemplateBulkEditForm',
    'ModuleBulkEditForm',
    'ModuleTypeBulkEditForm',
    'ModuleTypeProfileBulkEditForm',
    'PlatformBulkEditForm',
    'PowerFeedBulkEditForm',
    'PowerOutletBulkEditForm',
    'PowerOutletTemplateBulkEditForm',
    'PowerPanelBulkEditForm',
    'PowerPortBulkEditForm',
    'PowerPortTemplateBulkEditForm',
    'RackBulkEditForm',
    'RackGroupBulkEditForm',
    'RackReservationBulkEditForm',
    'RackRoleBulkEditForm',
    'RackTypeBulkEditForm',
    'RearPortBulkEditForm',
    'RearPortTemplateBulkEditForm',
    'RegionBulkEditForm',
    'SiteBulkEditForm',
    'SiteGroupBulkEditForm',
    'VirtualChassisBulkEditForm',
    'VirtualDeviceContextBulkEditForm'
)


class RegionBulkEditForm(NestedGroupModelBulkEditForm):
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=Region.objects.all(),
        required=False
    )

    model = Region
    fieldsets = (
        FieldSet('parent', 'description'),
    )
    nullable_fields = ('parent', 'description', 'comments')


class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm):
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=SiteGroup.objects.all(),
        required=False
    )

    model = SiteGroup
    fieldsets = (
        FieldSet('parent', 'description'),
    )
    nullable_fields = ('parent', 'description', 'comments')


class SiteBulkEditForm(PrimaryModelBulkEditForm):
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(SiteStatusChoices),
        required=False,
        initial=''
    )
    region = DynamicModelChoiceField(
        label=_('Region'),
        queryset=Region.objects.all(),
        required=False
    )
    group = DynamicModelChoiceField(
        label=_('Group'),
        queryset=SiteGroup.objects.all(),
        required=False
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    facility = forms.CharField(
        label=_('Facility'),
        max_length=50,
        required=False
    )
    asns = DynamicModelMultipleChoiceField(
        queryset=ASN.objects.all(),
        label=_('ASNs'),
        required=False
    )
    contact_name = forms.CharField(
        label=_('Contact name'),
        max_length=50,
        required=False
    )
    contact_phone = forms.CharField(
        label=_('Contact phone'),
        max_length=20,
        required=False
    )
    contact_email = forms.EmailField(
        required=False,
        label=_('Contact E-mail')
    )
    time_zone = TimeZoneFormField(
        label=_('Time zone'),
        choices=add_blank_choice(TimeZoneFormField().choices),
        required=False
    )

    model = Site
    fieldsets = (
        FieldSet('status', 'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description'),
    )
    nullable_fields = (
        'region', 'group', 'tenant', 'facility', 'asns', 'time_zone', 'description', 'comments',
    )


class LocationBulkEditForm(NestedGroupModelBulkEditForm):
    site = DynamicModelChoiceField(
        label=_('Site'),
        queryset=Site.objects.all(),
        required=False
    )
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=Location.objects.all(),
        required=False,
        query_params={
            'site_id': '$site'
        }
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(LocationStatusChoices),
        required=False,
        initial=''
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    facility = forms.CharField(
        label=_('Facility'),
        max_length=50,
        required=False
    )

    model = Location
    fieldsets = (
        FieldSet('site', 'parent', 'status', 'tenant', 'facility', 'description'),
    )
    nullable_fields = ('parent', 'tenant', 'facility', 'description', 'comments')


class RackGroupBulkEditForm(OrganizationalModelBulkEditForm):
    model = RackGroup
    fieldsets = (
        FieldSet('description'),
    )
    nullable_fields = ('description', 'comments')


class RackRoleBulkEditForm(OrganizationalModelBulkEditForm):
    color = ColorField(
        label=_('Color'),
        required=False
    )

    model = RackRole
    fieldsets = (
        FieldSet('color', 'description'),
    )
    nullable_fields = ('color', 'description', 'comments')


class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    form_factor = forms.ChoiceField(
        label=_('Form factor'),
        choices=add_blank_choice(RackFormFactorChoices),
        required=False
    )
    width = forms.ChoiceField(
        label=_('Width'),
        choices=add_blank_choice(RackWidthChoices),
        required=False
    )
    u_height = forms.IntegerField(
        required=False,
        label=_('Height (U)')
    )
    starting_unit = forms.IntegerField(
        required=False,
        min_value=1
    )
    desc_units = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect,
        label=_('Descending units')
    )
    outer_width = forms.IntegerField(
        label=_('Outer width'),
        required=False,
        min_value=1
    )
    outer_height = forms.IntegerField(
        label=_('Outer height'),
        required=False,
        min_value=1
    )
    outer_depth = forms.IntegerField(
        label=_('Outer depth'),
        required=False,
        min_value=1
    )
    outer_unit = forms.ChoiceField(
        label=_('Outer unit'),
        choices=add_blank_choice(RackDimensionUnitChoices),
        required=False
    )
    mounting_depth = forms.IntegerField(
        label=_('Mounting depth'),
        required=False,
        min_value=1
    )
    weight = forms.DecimalField(
        label=_('Weight'),
        min_value=0,
        required=False
    )
    max_weight = forms.IntegerField(
        label=_('Max weight'),
        min_value=0,
        required=False
    )
    weight_unit = forms.ChoiceField(
        label=_('Weight unit'),
        choices=add_blank_choice(WeightUnitChoices),
        required=False,
        initial=''
    )

    model = RackType
    fieldsets = (
        FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
        FieldSet(
            InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
            InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
            'mounting_depth',
            name=_('Dimensions')
        ),
        FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
    )
    nullable_fields = (
        'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
        'max_weight', 'weight_unit', 'description', 'comments',
    )


class RackBulkEditForm(PrimaryModelBulkEditForm):
    region = DynamicModelChoiceField(
        label=_('Region'),
        queryset=Region.objects.all(),
        required=False,
        initial_params={
            'sites': '$site'
        }
    )
    site_group = DynamicModelChoiceField(
        label=_('Site group'),
        queryset=SiteGroup.objects.all(),
        required=False,
        initial_params={
            'sites': '$site'
        }
    )
    site = DynamicModelChoiceField(
        label=_('Site'),
        queryset=Site.objects.all(),
        required=False,
        query_params={
            'region_id': '$region',
            'group_id': '$site_group',
        }
    )
    location = DynamicModelChoiceField(
        label=_('Location'),
        queryset=Location.objects.all(),
        required=False,
        query_params={
            'site_id': '$site'
        }
    )
    group = DynamicModelChoiceField(
        label=_('Group'),
        queryset=RackGroup.objects.all(),
        required=False
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(RackStatusChoices),
        required=False,
        initial=''
    )
    role = DynamicModelChoiceField(
        label=_('Role'),
        queryset=RackRole.objects.all(),
        required=False
    )
    rack_type = DynamicModelChoiceField(
        label=_('Rack type'),
        queryset=RackType.objects.all(),
        required=False,
    )
    serial = forms.CharField(
        max_length=50,
        required=False,
        label=_('Serial Number')
    )
    asset_tag = forms.CharField(
        label=_('Asset tag'),
        max_length=50,
        required=False
    )
    form_factor = forms.ChoiceField(
        label=_('Form factor'),
        choices=add_blank_choice(RackFormFactorChoices),
        required=False
    )
    width = forms.ChoiceField(
        label=_('Width'),
        choices=add_blank_choice(RackWidthChoices),
        required=False
    )
    u_height = forms.IntegerField(
        required=False,
        label=_('Height (U)')
    )
    desc_units = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect,
        label=_('Descending units')
    )
    outer_width = forms.IntegerField(
        label=_('Outer width'),
        required=False,
        min_value=1
    )
    outer_height = forms.IntegerField(
        label=_('Outer height'),
        required=False,
        min_value=1
    )
    outer_depth = forms.IntegerField(
        label=_('Outer depth'),
        required=False,
        min_value=1
    )
    outer_unit = forms.ChoiceField(
        label=_('Outer unit'),
        choices=add_blank_choice(RackDimensionUnitChoices),
        required=False
    )
    mounting_depth = forms.IntegerField(
        label=_('Mounting depth'),
        required=False,
        min_value=1
    )
    airflow = forms.ChoiceField(
        label=_('Airflow'),
        choices=add_blank_choice(RackAirflowChoices),
        required=False
    )
    weight = forms.DecimalField(
        label=_('Weight'),
        min_value=0,
        required=False
    )
    max_weight = forms.IntegerField(
        label=_('Max weight'),
        min_value=0,
        required=False
    )
    weight_unit = forms.ChoiceField(
        label=_('Weight unit'),
        choices=add_blank_choice(WeightUnitChoices),
        required=False,
        initial=''
    )

    model = Rack
    fieldsets = (
        FieldSet(
            'status', 'group', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')
        ),
        FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
        FieldSet('outer_width', 'outer_height', 'outer_depth', 'outer_unit', name=_('Outer Dimensions')),
        FieldSet('form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'mounting_depth', name=_('Hardware')),
        FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
    )
    nullable_fields = (
        'location', 'group', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_height', 'outer_depth',
        'outer_unit', 'weight', 'max_weight', 'weight_unit', 'description', 'comments',
    )


class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(RackReservationStatusChoices),
        required=False,
        initial=''
    )
    user = forms.ModelChoiceField(
        label=_('User'),
        queryset=User.objects.order_by('username'),
        required=False
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )

    model = RackReservation
    fieldsets = (
        FieldSet('status', 'user', 'tenant', 'description'),
    )
    nullable_fields = ('comments',)


class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm):
    model = Manufacturer
    fieldsets = (
        FieldSet('description'),
    )
    nullable_fields = ('description', 'comments')


class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    default_platform = DynamicModelChoiceField(
        label=_('Default platform'),
        queryset=Platform.objects.all(),
        required=False
    )
    part_number = forms.CharField(
        label=_('Part number'),
        required=False
    )
    u_height = forms.IntegerField(
        label=_('U height'),
        min_value=0,
        required=False
    )
    is_full_depth = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect(),
        label=_('Is full depth')
    )
    exclude_from_utilization = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect(),
        label=_('Exclude from utilization')
    )
    airflow = forms.ChoiceField(
        label=_('Airflow'),
        choices=add_blank_choice(DeviceAirflowChoices),
        required=False
    )
    weight = forms.DecimalField(
        label=_('Weight'),
        min_value=0,
        required=False
    )
    weight_unit = forms.ChoiceField(
        label=_('Weight unit'),
        choices=add_blank_choice(WeightUnitChoices),
        required=False,
        initial=''
    )

    model = DeviceType
    fieldsets = (
        FieldSet(
            'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
            'airflow', 'description', name=_('Device Type')
        ),
        FieldSet('weight', 'weight_unit', name=_('Weight')),
    )
    nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')


class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm):
    schema = JSONField(
        label=_('Schema'),
        required=False
    )

    model = ModuleTypeProfile
    fieldsets = (
        FieldSet('name', 'description', 'schema', name=_('Profile')),
    )
    nullable_fields = ('description', 'comments')


class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
    profile = DynamicModelChoiceField(
        label=_('Profile'),
        queryset=ModuleTypeProfile.objects.all(),
        required=False
    )
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    part_number = forms.CharField(
        label=_('Part number'),
        required=False
    )
    airflow = forms.ChoiceField(
        label=_('Airflow'),
        choices=add_blank_choice(ModuleAirflowChoices),
        required=False
    )
    weight = forms.DecimalField(
        label=_('Weight'),
        min_value=0,
        required=False
    )
    weight_unit = forms.ChoiceField(
        label=_('Weight unit'),
        choices=add_blank_choice(WeightUnitChoices),
        required=False,
        initial=''
    )

    model = ModuleType
    fieldsets = (
        FieldSet('profile', 'manufacturer', 'part_number', 'description', name=_('Module Type')),
        FieldSet(
            'airflow',
            InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
            name=_('Chassis')
        ),
    )
    nullable_fields = ('part_number', 'weight', 'weight_unit', 'profile', 'description', 'comments')


class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=DeviceRole.objects.all(),
        required=False,
    )
    color = ColorField(
        label=_('Color'),
        required=False
    )
    vm_role = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect,
        label=_('VM role')
    )
    config_template = DynamicModelChoiceField(
        label=_('Config template'),
        queryset=ConfigTemplate.objects.all(),
        required=False
    )

    model = DeviceRole
    fieldsets = (
        FieldSet('parent', 'color', 'vm_role', 'config_template', 'description'),
    )
    nullable_fields = ('parent', 'color', 'config_template', 'description', 'comments')


class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=Platform.objects.all(),
        required=False,
    )
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    config_template = DynamicModelChoiceField(
        label=_('Config template'),
        queryset=ConfigTemplate.objects.all(),
        required=False
    )

    model = Platform
    fieldsets = (
        FieldSet('parent', 'manufacturer', 'config_template', 'description'),
    )
    nullable_fields = ('parent', 'manufacturer', 'config_template', 'description', 'comments')


class DeviceBulkEditForm(PrimaryModelBulkEditForm):
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    device_type = DynamicModelChoiceField(
        label=_('Device type'),
        queryset=DeviceType.objects.all(),
        required=False,
        context={
            'parent': 'manufacturer',
        },
        query_params={
            'manufacturer_id': '$manufacturer'
        }
    )
    role = DynamicModelChoiceField(
        label=_('Device role'),
        queryset=DeviceRole.objects.all(),
        required=False
    )
    site = DynamicModelChoiceField(
        label=_('Site'),
        queryset=Site.objects.all(),
        required=False
    )
    location = DynamicModelChoiceField(
        label=_('Location'),
        queryset=Location.objects.all(),
        required=False,
        query_params={
            'site_id': '$site'
        }
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    platform = DynamicModelChoiceField(
        label=_('Platform'),
        queryset=Platform.objects.all(),
        required=False
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(DeviceStatusChoices),
        required=False
    )
    airflow = forms.ChoiceField(
        label=_('Airflow'),
        choices=add_blank_choice(DeviceAirflowChoices),
        required=False
    )
    serial = forms.CharField(
        max_length=50,
        required=False,
        label=_('Serial Number')
    )
    config_template = DynamicModelChoiceField(
        label=_('Config template'),
        queryset=ConfigTemplate.objects.all(),
        required=False
    )
    cluster = DynamicModelChoiceField(
        label=_('Cluster'),
        queryset=Cluster.objects.all(),
        required=False,
        query_params={
            'site_id': ['$site', 'null']
        },
    )

    model = Device
    fieldsets = (
        FieldSet('role', 'status', 'tenant', 'platform', 'description', name=_('Device')),
        FieldSet('site', 'location', name=_('Location')),
        FieldSet('manufacturer', 'device_type', 'airflow', 'serial', name=_('Hardware')),
        FieldSet('config_template', name=_('Configuration')),
        FieldSet('cluster', name=_('Virtualization')),
    )
    nullable_fields = (
        'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'cluster', 'comments',
    )


class ModuleBulkEditForm(PrimaryModelBulkEditForm):
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    module_type = DynamicModelChoiceField(
        label=_('Module type'),
        queryset=ModuleType.objects.all(),
        required=False,
        query_params={
            'manufacturer_id': '$manufacturer'
        },
        context={
            'parent': 'manufacturer',
        }
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(ModuleStatusChoices),
        required=False,
        initial=''
    )
    serial = forms.CharField(
        max_length=50,
        required=False,
        label=_('Serial Number')
    )

    model = Module
    fieldsets = (
        FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'),
    )
    nullable_fields = ('serial', 'description', 'comments')


class CableBundleBulkEditForm(PrimaryModelBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=CableBundle.objects.all(),
        widget=forms.MultipleHiddenInput
    )
    description = forms.CharField(
        label=_('Description'),
        max_length=200,
        required=False,
    )

    model = CableBundle
    fieldsets = (
        FieldSet('description',),
    )
    nullable_fields = ('description', 'comments')


class CableBulkEditForm(PrimaryModelBulkEditForm):
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(CableTypeChoices),
        required=False,
        initial=''
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(LinkStatusChoices),
        required=False,
        initial=''
    )
    profile = forms.ChoiceField(
        label=_('Profile'),
        choices=add_blank_choice(CableProfileChoices),
        required=False,
        initial=''
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )
    bundle = DynamicModelChoiceField(
        label=_('Bundle'),
        queryset=CableBundle.objects.all(),
        required=False,
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=100,
        required=False
    )
    color = ColorField(
        label=_('Color'),
        required=False
    )
    length = forms.DecimalField(
        label=_('Length'),
        min_value=0,
        required=False
    )
    length_unit = forms.ChoiceField(
        label=_('Length unit'),
        choices=add_blank_choice(CableLengthUnitChoices),
        required=False,
        initial=''
    )

    model = Cable
    fieldsets = (
        FieldSet('type', 'status', 'profile', 'tenant', 'bundle', 'label', 'description'),
        FieldSet('color', 'length', 'length_unit', name=_('Attributes')),
    )
    nullable_fields = (
        'type', 'status', 'profile', 'tenant', 'bundle', 'label', 'color', 'length', 'description', 'comments',
    )


class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm):
    domain = forms.CharField(
        label=_('Domain'),
        max_length=30,
        required=False
    )

    model = VirtualChassis
    fieldsets = (
        FieldSet('domain', 'description'),
    )
    nullable_fields = ('domain', 'description', 'comments')


class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
    region = DynamicModelChoiceField(
        label=_('Region'),
        queryset=Region.objects.all(),
        required=False,
        initial_params={
            'sites': '$site'
        }
    )
    site_group = DynamicModelChoiceField(
        label=_('Site group'),
        queryset=SiteGroup.objects.all(),
        required=False,
        initial_params={
            'sites': '$site'
        }
    )
    site = DynamicModelChoiceField(
        label=_('Site'),
        queryset=Site.objects.all(),
        required=False,
        query_params={
            'region_id': '$region',
            'group_id': '$site_group',
        }
    )
    location = DynamicModelChoiceField(
        label=_('Location'),
        queryset=Location.objects.all(),
        required=False,
        query_params={
            'site_id': '$site'
        }
    )

    model = PowerPanel
    fieldsets = (
        FieldSet('region', 'site_group', 'site', 'location', 'description'),
    )
    nullable_fields = ('location', 'description', 'comments')


class PowerFeedBulkEditForm(PrimaryModelBulkEditForm):
    power_panel = DynamicModelChoiceField(
        label=_('Power panel'),
        queryset=PowerPanel.objects.all(),
        required=False
    )
    rack = DynamicModelChoiceField(
        label=_('Rack'),
        queryset=Rack.objects.all(),
        required=False,
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(PowerFeedStatusChoices),
        required=False,
        initial=''
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(PowerFeedTypeChoices),
        required=False,
        initial=''
    )
    supply = forms.ChoiceField(
        label=_('Supply'),
        choices=add_blank_choice(PowerFeedSupplyChoices),
        required=False,
        initial=''
    )
    phase = forms.ChoiceField(
        label=_('Phase'),
        choices=add_blank_choice(PowerFeedPhaseChoices),
        required=False,
        initial=''
    )
    voltage = forms.IntegerField(
        label=_('Voltage'),
        required=False
    )
    amperage = forms.IntegerField(
        label=_('Amperage'),
        required=False
    )
    max_utilization = forms.IntegerField(
        label=_('Max utilization'),
        required=False
    )
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )
    tenant = DynamicModelChoiceField(
        queryset=Tenant.objects.all(),
        required=False
    )

    model = PowerFeed
    fieldsets = (
        FieldSet('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant'),
        FieldSet('supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Power'))
    )
    nullable_fields = ('location', 'tenant', 'description', 'comments')


#
# Device component templates
#

class ComponentTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
    pass


class ConsolePortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=ConsolePortTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(ConsolePortTypeChoices),
        required=False
    )

    nullable_fields = ('label', 'type', 'description')


class ConsoleServerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=ConsoleServerPortTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(ConsolePortTypeChoices),
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('label', 'type', 'description')


class PowerPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=PowerPortTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(PowerPortTypeChoices),
        required=False
    )
    maximum_draw = forms.IntegerField(
        label=_('Maximum draw'),
        min_value=1,
        required=False,
        help_text=_("Maximum power draw (watts)")
    )
    allocated_draw = forms.IntegerField(
        label=_('Allocated draw'),
        min_value=1,
        required=False,
        help_text=_("Allocated power draw (watts)")
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')


class PowerOutletTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=PowerOutletTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    device_type = forms.ModelChoiceField(
        label=_('Device type'),
        queryset=DeviceType.objects.all(),
        required=False,
        disabled=True,
        widget=forms.HiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(PowerOutletTypeChoices),
        required=False
    )
    color = ColorField(
        label=_('Color'),
        required=False
    )
    power_port = forms.ModelChoiceField(
        label=_('Power port'),
        queryset=PowerPortTemplate.objects.all(),
        required=False
    )
    feed_leg = forms.ChoiceField(
        label=_('Feed leg'),
        choices=add_blank_choice(PowerOutletFeedLegChoices),
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Limit power_port queryset to PowerPortTemplates which belong to the parent DeviceType
        if 'device_type' in self.initial:
            device_type = DeviceType.objects.filter(pk=self.initial['device_type']).first()
            self.fields['power_port'].queryset = PowerPortTemplate.objects.filter(device_type=device_type)
        else:
            self.fields['power_port'].choices = ()
            self.fields['power_port'].widget.attrs['disabled'] = True


class InterfaceTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=InterfaceTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(InterfaceTypeChoices),
        required=False
    )
    enabled = forms.NullBooleanField(
        label=_('Enabled'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )
    mgmt_only = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect,
        label=_('Management only')
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )
    poe_mode = forms.ChoiceField(
        choices=add_blank_choice(InterfacePoEModeChoices),
        required=False,
        initial='',
        label=_('PoE mode')
    )
    poe_type = forms.ChoiceField(
        choices=add_blank_choice(InterfacePoETypeChoices),
        required=False,
        initial='',
        label=_('PoE type')
    )
    rf_role = forms.ChoiceField(
        choices=add_blank_choice(WirelessRoleChoices),
        required=False,
        initial='',
        label=_('Wireless role')
    )

    nullable_fields = ('label', 'description', 'poe_mode', 'poe_type', 'rf_role')


class FrontPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=FrontPortTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(PortTypeChoices),
        required=False
    )
    color = ColorField(
        label=_('Color'),
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('description',)


class RearPortTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=RearPortTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    type = forms.ChoiceField(
        label=_('Type'),
        choices=add_blank_choice(PortTypeChoices),
        required=False
    )
    color = ColorField(
        label=_('Color'),
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('description',)


class ModuleBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=ModuleBayTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('label', 'position', 'description')


class DeviceBayTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=DeviceBayTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )

    nullable_fields = ('label', 'description')


class InventoryItemTemplateBulkEditForm(ComponentTemplateBulkEditForm):
    pk = forms.ModelMultipleChoiceField(
        queryset=InventoryItemTemplate.objects.all(),
        widget=forms.MultipleHiddenInput()
    )
    label = forms.CharField(
        label=_('Label'),
        max_length=64,
        required=False
    )
    description = forms.CharField(
        label=_('Description'),
        required=False
    )
    role = DynamicModelChoiceField(
        label=_('Role'),
        queryset=InventoryItemRole.objects.all(),
        required=False
    )
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )

    nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')


#
# Device components
#

class ComponentBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
    device = forms.ModelChoiceField(
        label=_('Device'),
        queryset=Device.objects.all(),
        required=False,
        disabled=True,
        widget=forms.HiddenInput()
    )
    module = forms.ModelChoiceField(
        label=_('Module'),
        queryset=Module.objects.all(),
        required=False
    )

    def __init__(self, *args, initial=None, **kwargs):
        try:
            self.device_id = int(initial.get('device'))
        except (TypeError, ValueError):
            self.device_id = None

        super().__init__(*args, initial=initial, **kwargs)

        # Limit module queryset to Modules which belong to the parent Device
        if self.device_id:
            device = Device.objects.filter(pk=self.device_id).first()
            self.fields['module'].queryset = Module.objects.filter(device=device)
        else:
            self.fields['module'].choices = ()
            self.fields['module'].widget.attrs['disabled'] = True


class ConsolePortBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description'])
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = ConsolePort
    fieldsets = (
        FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
    )
    nullable_fields = ('module', 'label', 'description')


class ConsoleServerPortBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description'])
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = ConsoleServerPort
    fieldsets = (
        FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
    )
    nullable_fields = ('module', 'label', 'description')


class PowerPortBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description'])
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = PowerPort
    fieldsets = (
        FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
        FieldSet('maximum_draw', 'allocated_draw', name=_('Power')),
    )
    nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')


class PowerOutletBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(
        PowerOutlet,
        ['label', 'type', 'status', 'color', 'feed_leg', 'power_port', 'mark_connected', 'description']
    )
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = PowerOutlet
    fieldsets = (
        FieldSet('module', 'type', 'label', 'status', 'description', 'mark_connected', 'color'),
        FieldSet('feed_leg', 'power_port', name=_('Power')),
    )
    nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Limit power_port queryset to PowerPorts which belong to the parent Device
        if self.device_id:
            device = Device.objects.filter(pk=self.device_id).first()
            self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
        else:
            self.fields['power_port'].choices = ()
            self.fields['power_port'].widget.attrs['disabled'] = True


class InterfaceBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(Interface, [
        'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
        'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
        'wireless_lans', 'vlan_translation_policy'
    ])
):
    enabled = forms.NullBooleanField(
        label=_('Enabled'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )
    parent = DynamicModelChoiceField(
        label=_('Parent'),
        queryset=Interface.objects.all(),
        required=False,
        query_params={
            'virtual_chassis_member_id': '$device',
        }
    )
    bridge = DynamicModelChoiceField(
        label=_('Bridge'),
        queryset=Interface.objects.all(),
        required=False,
        query_params={
            'virtual_chassis_member_id': '$device',
        }
    )
    lag = DynamicModelChoiceField(
        queryset=Interface.objects.all(),
        required=False,
        query_params={
            'type': 'lag',
            'virtual_chassis_member_id': '$device',
        },
        label=_('LAG')
    )
    vdcs = DynamicModelMultipleChoiceField(
        queryset=VirtualDeviceContext.objects.all(),
        required=False,
        label=_('Virtual device contexts'),
        query_params={
            'device_id': '$device',
        }
    )
    speed = forms.IntegerField(
        label=_('Speed'),
        required=False,
        widget=NumberWithOptions(
            options=InterfaceSpeedChoices
        )
    )
    mgmt_only = forms.NullBooleanField(
        required=False,
        widget=BulkEditNullBooleanSelect,
        label=_('Management only')
    )
    poe_mode = forms.ChoiceField(
        choices=add_blank_choice(InterfacePoEModeChoices),
        required=False,
        initial='',
        label=_('PoE mode')
    )
    poe_type = forms.ChoiceField(
        choices=add_blank_choice(InterfacePoETypeChoices),
        required=False,
        initial='',
        label=_('PoE type')
    )
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )
    mode = forms.ChoiceField(
        label=_('Mode'),
        choices=add_blank_choice(InterfaceModeChoices),
        required=False,
        initial=''
    )
    vlan_group = DynamicModelChoiceField(
        queryset=VLANGroup.objects.all(),
        required=False,
        label=_('VLAN group')
    )
    untagged_vlan = DynamicModelChoiceField(
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
            'available_on_device': '$device',
        },
        label=_('Untagged VLAN')
    )
    tagged_vlans = DynamicModelMultipleChoiceField(
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
            'available_on_device': '$device',
        },
        label=_('Tagged VLANs')
    )
    add_tagged_vlans = DynamicModelMultipleChoiceField(
        label=_('Add tagged VLANs'),
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
            'available_on_device': '$device',
        },
    )
    remove_tagged_vlans = DynamicModelMultipleChoiceField(
        label=_('Remove tagged VLANs'),
        queryset=VLAN.objects.all(),
        required=False,
        query_params={
            'group_id': '$vlan_group',
            'available_on_device': '$device',
        }
    )
    qinq_svlan = DynamicModelChoiceField(
        queryset=VLAN.objects.all(),
        required=False,
        label=_('Q-in-Q Service VLAN'),
        query_params={
            'group_id': '$vlan_group',
            'available_on_device': '$device',
            'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
        }
    )
    vrf = DynamicModelChoiceField(
        queryset=VRF.objects.all(),
        required=False,
        label=_('VRF')
    )
    wireless_lan_group = DynamicModelChoiceField(
        queryset=WirelessLANGroup.objects.all(),
        required=False,
        label=_('Wireless LAN group')
    )
    wireless_lans = DynamicModelMultipleChoiceField(
        queryset=WirelessLAN.objects.all(),
        required=False,
        label=_('Wireless LANs'),
        query_params={
            'group_id': '$wireless_lan_group',
        }
    )

    model = Interface
    fieldsets = (
        FieldSet('module', 'type', 'label', 'speed', 'duplex', 'description'),
        FieldSet('vrf', 'wwn', name=_('Addressing')),
        FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
        FieldSet('poe_mode', 'poe_type', name=_('PoE')),
        FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
        FieldSet(
            'mode', 'vlan_group', 'untagged_vlan', 'qinq_svlan', 'vlan_translation_policy', name=_('802.1Q Switching')
        ),
        FieldSet(
            TabbedGroups(
                FieldSet('tagged_vlans', name=_('Assignment')),
                FieldSet('add_tagged_vlans', 'remove_tagged_vlans', name=_('Add/Remove')),
            ),
        ),
        FieldSet(
            'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
            name=_('Wireless')
        ),
    )
    nullable_fields = (
        'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'vdcs', 'mtu', 'description',
        'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
        'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'wireless_lans', 'vlan_translation_policy',
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.device_id:
            # See #4523
            if 'pk' in self.initial:
                site = None
                interfaces = Interface.objects.filter(pk__in=self.initial['pk']).prefetch_related('device__site')

                # Check interface sites.  First interface should set site, further interfaces will either continue the
                # loop or reset back to no site and break the loop.
                for interface in interfaces:
                    if site is None:
                        site = interface.device.site
                    elif interface.device.site is not site:
                        site = None
                        break

                if site is not None:
                    # Query for VLANs assigned to the same site and VLANs with no site assigned (null).
                    self.fields['untagged_vlan'].widget.add_query_param(
                        'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
                    )
                    self.fields['tagged_vlans'].widget.add_query_param(
                        'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
                    )

                    self.fields['add_tagged_vlans'].widget.add_query_param(
                        'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
                    )
                    self.fields['remove_tagged_vlans'].widget.add_query_param(
                        'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
                    )

            self.fields['parent'].choices = ()
            self.fields['parent'].widget.attrs['disabled'] = True
            self.fields['bridge'].choices = ()
            self.fields['bridge'].widget.attrs['disabled'] = True
            self.fields['lag'].choices = ()
            self.fields['lag'].widget.attrs['disabled'] = True

    def clean(self):
        super().clean()

        if not self.cleaned_data['mode']:
            if self.cleaned_data['untagged_vlan']:
                raise forms.ValidationError({'untagged_vlan': _("Interface mode must be specified to assign VLANs")})
            if self.cleaned_data['tagged_vlans']:
                raise forms.ValidationError({'tagged_vlans': _("Interface mode must be specified to assign VLANs")})

        # Untagged interfaces cannot be assigned tagged VLANs
        elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
            raise forms.ValidationError({
                'mode': _("An access interface cannot have tagged VLANs assigned.")
            })

        # Remove all tagged VLAN assignments from "tagged all" interfaces
        elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
            self.cleaned_data['tagged_vlans'] = []


class FrontPortBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description'])
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = FrontPort
    fieldsets = (
        FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
    )
    nullable_fields = ('module', 'label', 'description', 'color')


class RearPortBulkEditForm(
    ComponentBulkEditForm,
    form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description'])
):
    mark_connected = forms.NullBooleanField(
        label=_('Mark connected'),
        required=False,
        widget=BulkEditNullBooleanSelect
    )

    model = RearPort
    fieldsets = (
        FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
    )
    nullable_fields = ('module', 'label', 'description', 'color')


class ModuleBayBulkEditForm(
    form_from_model(ModuleBay, ['label', 'position', 'description']),
    NetBoxModelBulkEditForm
):
    model = ModuleBay
    fieldsets = (
        FieldSet('label', 'position', 'description'),
    )
    nullable_fields = ('label', 'position', 'description')


class DeviceBayBulkEditForm(
    form_from_model(DeviceBay, ['label', 'description']),
    NetBoxModelBulkEditForm
):
    model = DeviceBay
    fieldsets = (
        FieldSet('label', 'description'),
    )
    nullable_fields = ('label', 'description')


class InventoryItemBulkEditForm(
    form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
    NetBoxModelBulkEditForm
):
    device = DynamicModelChoiceField(
        label=_('Device'),
        queryset=Device.objects.all(),
        required=False
    )
    role = DynamicModelChoiceField(
        label=_('Role'),
        queryset=InventoryItemRole.objects.all(),
        required=False
    )
    manufacturer = DynamicModelChoiceField(
        label=_('Manufacturer'),
        queryset=Manufacturer.objects.all(),
        required=False
    )
    status = forms.ChoiceField(
        label=_('Status'),
        choices=add_blank_choice(InventoryItemStatusChoices),
        required=False,
        initial=''
    )

    model = InventoryItem
    fieldsets = (
        FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'status', 'description'),
    )
    nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Remove parent device passed as context to avoid conflicts with the actual device field
        # on this form (see bug #19464)
        self.initial.pop('device', None)


#
# Device component roles
#

class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm):
    color = ColorField(
        label=_('Color'),
        required=False
    )

    model = InventoryItemRole
    fieldsets = (
        FieldSet('color', 'description'),
    )
    nullable_fields = ('color', 'description', 'comments')


class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
    device = DynamicModelChoiceField(
        label=_('Device'),
        queryset=Device.objects.all(),
        required=False
    )
    status = forms.ChoiceField(
        label=_('Status'),
        required=False,
        choices=add_blank_choice(VirtualDeviceContextStatusChoices)
    )
    tenant = DynamicModelChoiceField(
        label=_('Tenant'),
        queryset=Tenant.objects.all(),
        required=False
    )

    model = VirtualDeviceContext
    fieldsets = (
        FieldSet('device', 'status', 'tenant'),
    )
    nullable_fields = ('device', 'tenant', )


#
# Addressing
#

class MACAddressBulkEditForm(PrimaryModelBulkEditForm):
    model = MACAddress
    fieldsets = (
        FieldSet('description'),
    )
    nullable_fields = ('description', 'comments')
