jeremystretch пре 4 година
родитељ
комит
b5aecfeb91

+ 0 - 513
netbox/circuits/forms.py

@@ -1,513 +0,0 @@
-from django import forms
-from django.utils.translation import gettext as _
-
-from dcim.models import Region, Site, SiteGroup
-from extras.forms import (
-    AddRemoveTagsForm, CustomFieldModelBulkEditForm, CustomFieldModelFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm,
-)
-from extras.models import Tag
-from tenancy.forms import TenancyFilterForm, TenancyForm
-from tenancy.models import Tenant
-from utilities.forms import (
-    add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, DatePicker,
-    DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SmallTextarea, SlugField,
-    StaticSelect, StaticSelectMultiple, TagFilterField,
-)
-from .choices import CircuitStatusChoices
-from .models import *
-
-
-#
-# Providers
-#
-
-class ProviderForm(BootstrapMixin, CustomFieldModelForm):
-    slug = SlugField()
-    comments = CommentField()
-    tags = DynamicModelMultipleChoiceField(
-        queryset=Tag.objects.all(),
-        required=False
-    )
-
-    class Meta:
-        model = Provider
-        fields = [
-            'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
-        ]
-        fieldsets = (
-            ('Provider', ('name', 'slug', 'asn', 'tags')),
-            ('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
-        )
-        widgets = {
-            'noc_contact': SmallTextarea(
-                attrs={'rows': 5}
-            ),
-            'admin_contact': SmallTextarea(
-                attrs={'rows': 5}
-            ),
-        }
-        help_texts = {
-            'name': "Full name of the provider",
-            'asn': "BGP autonomous system number (if applicable)",
-            'portal_url': "URL of the provider's customer support portal",
-            'noc_contact': "NOC email address and phone number",
-            'admin_contact': "Administrative contact email address and phone number",
-        }
-
-
-class ProviderCSVForm(CustomFieldModelCSVForm):
-    slug = SlugField()
-
-    class Meta:
-        model = Provider
-        fields = (
-            'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
-        )
-
-
-class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
-    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,
-        label='Comments'
-    )
-
-    class Meta:
-        nullable_fields = [
-            'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
-        ]
-
-
-class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
-    model = Provider
-    field_groups = [
-        ['q', 'tag'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['asn'],
-    ]
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
-    region_id = DynamicModelMultipleChoiceField(
-        queryset=Region.objects.all(),
-        required=False,
-        label=_('Region'),
-        fetch_trigger='open'
-    )
-    site_group_id = DynamicModelMultipleChoiceField(
-        queryset=SiteGroup.objects.all(),
-        required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
-    )
-    site_id = DynamicModelMultipleChoiceField(
-        queryset=Site.objects.all(),
-        required=False,
-        query_params={
-            'region_id': '$region_id',
-            'site_group_id': '$site_group_id',
-        },
-        label=_('Site'),
-        fetch_trigger='open'
-    )
-    asn = forms.IntegerField(
-        required=False,
-        label=_('ASN')
-    )
-    tag = TagFilterField(model)
-
-
-#
-# Provider networks
-#
-
-class ProviderNetworkForm(BootstrapMixin, CustomFieldModelForm):
-    provider = DynamicModelChoiceField(
-        queryset=Provider.objects.all()
-    )
-    comments = CommentField()
-    tags = DynamicModelMultipleChoiceField(
-        queryset=Tag.objects.all(),
-        required=False
-    )
-
-    class Meta:
-        model = ProviderNetwork
-        fields = [
-            'provider', 'name', 'description', 'comments', 'tags',
-        ]
-        fieldsets = (
-            ('Provider Network', ('provider', 'name', 'description', 'tags')),
-        )
-
-
-class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
-    provider = CSVModelChoiceField(
-        queryset=Provider.objects.all(),
-        to_field_name='name',
-        help_text='Assigned provider'
-    )
-
-    class Meta:
-        model = ProviderNetwork
-        fields = [
-            'provider', 'name', 'description', 'comments',
-        ]
-
-
-class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(
-        queryset=ProviderNetwork.objects.all(),
-        widget=forms.MultipleHiddenInput
-    )
-    provider = DynamicModelChoiceField(
-        queryset=Provider.objects.all(),
-        required=False
-    )
-    description = forms.CharField(
-        max_length=100,
-        required=False
-    )
-    comments = CommentField(
-        widget=SmallTextarea,
-        label='Comments'
-    )
-
-    class Meta:
-        nullable_fields = [
-            'description', 'comments',
-        ]
-
-
-class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
-    model = ProviderNetwork
-    field_groups = (
-        ('q', 'tag'),
-        ('provider_id',),
-    )
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
-    provider_id = DynamicModelMultipleChoiceField(
-        queryset=Provider.objects.all(),
-        required=False,
-        label=_('Provider'),
-        fetch_trigger='open'
-    )
-    tag = TagFilterField(model)
-
-
-#
-# Circuit types
-#
-
-class CircuitTypeForm(BootstrapMixin, CustomFieldModelForm):
-    slug = SlugField()
-
-    class Meta:
-        model = CircuitType
-        fields = [
-            'name', 'slug', 'description',
-        ]
-
-
-class CircuitTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(
-        queryset=CircuitType.objects.all(),
-        widget=forms.MultipleHiddenInput
-    )
-    description = forms.CharField(
-        max_length=200,
-        required=False
-    )
-
-    class Meta:
-        nullable_fields = ['description']
-
-
-class CircuitTypeCSVForm(CustomFieldModelCSVForm):
-    slug = SlugField()
-
-    class Meta:
-        model = CircuitType
-        fields = ('name', 'slug', 'description')
-        help_texts = {
-            'name': 'Name of circuit type',
-        }
-
-
-class CircuitTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
-    model = CircuitType
-    field_groups = [
-        ['q'],
-    ]
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
-
-
-#
-# Circuits
-#
-
-class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
-    provider = DynamicModelChoiceField(
-        queryset=Provider.objects.all()
-    )
-    type = DynamicModelChoiceField(
-        queryset=CircuitType.objects.all()
-    )
-    comments = CommentField()
-    tags = DynamicModelMultipleChoiceField(
-        queryset=Tag.objects.all(),
-        required=False
-    )
-
-    class Meta:
-        model = Circuit
-        fields = [
-            'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
-            'comments', 'tags',
-        ]
-        fieldsets = (
-            ('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
-            ('Tenancy', ('tenant_group', 'tenant')),
-        )
-        help_texts = {
-            'cid': "Unique circuit ID",
-            'commit_rate': "Committed rate",
-        }
-        widgets = {
-            'status': StaticSelect(),
-            'install_date': DatePicker(),
-            'commit_rate': SelectSpeedWidget(),
-        }
-
-
-class CircuitCSVForm(CustomFieldModelCSVForm):
-    provider = CSVModelChoiceField(
-        queryset=Provider.objects.all(),
-        to_field_name='name',
-        help_text='Assigned provider'
-    )
-    type = CSVModelChoiceField(
-        queryset=CircuitType.objects.all(),
-        to_field_name='name',
-        help_text='Type of circuit'
-    )
-    status = CSVChoiceField(
-        choices=CircuitStatusChoices,
-        required=False,
-        help_text='Operational status'
-    )
-    tenant = CSVModelChoiceField(
-        queryset=Tenant.objects.all(),
-        required=False,
-        to_field_name='name',
-        help_text='Assigned tenant'
-    )
-
-    class Meta:
-        model = Circuit
-        fields = [
-            'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
-        ]
-
-
-class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
-    pk = forms.ModelMultipleChoiceField(
-        queryset=Circuit.objects.all(),
-        widget=forms.MultipleHiddenInput
-    )
-    type = DynamicModelChoiceField(
-        queryset=CircuitType.objects.all(),
-        required=False
-    )
-    provider = DynamicModelChoiceField(
-        queryset=Provider.objects.all(),
-        required=False
-    )
-    status = forms.ChoiceField(
-        choices=add_blank_choice(CircuitStatusChoices),
-        required=False,
-        initial='',
-        widget=StaticSelect()
-    )
-    tenant = DynamicModelChoiceField(
-        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,
-        label='Comments'
-    )
-
-    class Meta:
-        nullable_fields = [
-            'tenant', 'commit_rate', 'description', 'comments',
-        ]
-
-
-class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
-    model = Circuit
-    field_groups = [
-        ['q', 'tag'],
-        ['provider_id', 'provider_network_id'],
-        ['type_id', 'status', 'commit_rate'],
-        ['region_id', 'site_group_id', 'site_id'],
-        ['tenant_group_id', 'tenant_id'],
-    ]
-    q = forms.CharField(
-        required=False,
-        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
-        label=_('Search')
-    )
-    type_id = DynamicModelMultipleChoiceField(
-        queryset=CircuitType.objects.all(),
-        required=False,
-        label=_('Type'),
-        fetch_trigger='open'
-    )
-    provider_id = DynamicModelMultipleChoiceField(
-        queryset=Provider.objects.all(),
-        required=False,
-        label=_('Provider'),
-        fetch_trigger='open'
-    )
-    provider_network_id = DynamicModelMultipleChoiceField(
-        queryset=ProviderNetwork.objects.all(),
-        required=False,
-        query_params={
-            'provider_id': '$provider_id'
-        },
-        label=_('Provider network'),
-        fetch_trigger='open'
-    )
-    status = forms.MultipleChoiceField(
-        choices=CircuitStatusChoices,
-        required=False,
-        widget=StaticSelectMultiple()
-    )
-    region_id = DynamicModelMultipleChoiceField(
-        queryset=Region.objects.all(),
-        required=False,
-        label=_('Region'),
-        fetch_trigger='open'
-    )
-    site_group_id = DynamicModelMultipleChoiceField(
-        queryset=SiteGroup.objects.all(),
-        required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
-    )
-    site_id = DynamicModelMultipleChoiceField(
-        queryset=Site.objects.all(),
-        required=False,
-        query_params={
-            'region_id': '$region_id',
-            'site_group_id': '$site_group_id',
-        },
-        label=_('Site'),
-        fetch_trigger='open'
-    )
-    commit_rate = forms.IntegerField(
-        required=False,
-        min_value=0,
-        label=_('Commit rate (Kbps)')
-    )
-    tag = TagFilterField(model)
-
-
-#
-# Circuit terminations
-#
-
-class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
-    region = DynamicModelChoiceField(
-        queryset=Region.objects.all(),
-        required=False,
-        initial_params={
-            'sites': '$site'
-        }
-    )
-    site_group = DynamicModelChoiceField(
-        queryset=SiteGroup.objects.all(),
-        required=False,
-        initial_params={
-            'sites': '$site'
-        }
-    )
-    site = DynamicModelChoiceField(
-        queryset=Site.objects.all(),
-        query_params={
-            'region_id': '$region',
-            'group_id': '$site_group',
-        },
-        required=False
-    )
-    provider_network = DynamicModelChoiceField(
-        queryset=ProviderNetwork.objects.all(),
-        required=False
-    )
-
-    class Meta:
-        model = CircuitTermination
-        fields = [
-            'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected', 'port_speed',
-            'upstream_speed', 'xconnect_id', 'pp_info', 'description',
-        ]
-        help_texts = {
-            'port_speed': "Physical circuit speed",
-            'xconnect_id': "ID of the local cross-connect",
-            'pp_info': "Patch panel ID and port number(s)"
-        }
-        widgets = {
-            'term_side': forms.HiddenInput(),
-            'port_speed': SelectSpeedWidget(),
-            'upstream_speed': SelectSpeedWidget(),
-        }
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        self.fields['provider_network'].widget.add_query_param('provider_id', self.instance.circuit.provider_id)

+ 4 - 0
netbox/circuits/forms/__init__.py

@@ -0,0 +1,4 @@
+from .bulk_edit import *
+from .bulk_import import *
+from .filtersets import *
+from .models import *

+ 135 - 0
netbox/circuits/forms/bulk_edit.py

@@ -0,0 +1,135 @@
+from django import forms
+
+from circuits.choices import CircuitStatusChoices
+from circuits.models import *
+from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
+from tenancy.models import Tenant
+from utilities.forms import (
+    add_blank_choice, BootstrapMixin, CommentField, DynamicModelChoiceField, SmallTextarea, StaticSelect,
+)
+
+__all__ = (
+    'CircuitBulkEditForm',
+    'CircuitTypeBulkEditForm',
+    'ProviderBulkEditForm',
+    'ProviderNetworkBulkEditForm',
+)
+
+
+class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+    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,
+        label='Comments'
+    )
+
+    class Meta:
+        nullable_fields = [
+            'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
+        ]
+
+
+class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=ProviderNetwork.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    provider = DynamicModelChoiceField(
+        queryset=Provider.objects.all(),
+        required=False
+    )
+    description = forms.CharField(
+        max_length=100,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
+
+    class Meta:
+        nullable_fields = [
+            'description', 'comments',
+        ]
+
+
+class CircuitTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=CircuitType.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+
+    class Meta:
+        nullable_fields = ['description']
+
+
+class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Circuit.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    type = DynamicModelChoiceField(
+        queryset=CircuitType.objects.all(),
+        required=False
+    )
+    provider = DynamicModelChoiceField(
+        queryset=Provider.objects.all(),
+        required=False
+    )
+    status = forms.ChoiceField(
+        choices=add_blank_choice(CircuitStatusChoices),
+        required=False,
+        initial='',
+        widget=StaticSelect()
+    )
+    tenant = DynamicModelChoiceField(
+        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,
+        label='Comments'
+    )
+
+    class Meta:
+        nullable_fields = [
+            'tenant', 'commit_rate', 'description', 'comments',
+        ]

+ 77 - 0
netbox/circuits/forms/bulk_import.py

@@ -0,0 +1,77 @@
+from circuits.choices import CircuitStatusChoices
+from circuits.models import *
+from extras.forms import CustomFieldModelCSVForm
+from tenancy.models import Tenant
+from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
+
+__all__ = (
+    'CircuitCSVForm',
+    'CircuitTypeCSVForm',
+    'ProviderCSVForm',
+    'ProviderNetworkCSVForm',
+)
+
+
+class ProviderCSVForm(CustomFieldModelCSVForm):
+    slug = SlugField()
+
+    class Meta:
+        model = Provider
+        fields = (
+            'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
+        )
+
+
+class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
+    provider = CSVModelChoiceField(
+        queryset=Provider.objects.all(),
+        to_field_name='name',
+        help_text='Assigned provider'
+    )
+
+    class Meta:
+        model = ProviderNetwork
+        fields = [
+            'provider', 'name', 'description', 'comments',
+        ]
+
+
+class CircuitTypeCSVForm(CustomFieldModelCSVForm):
+    slug = SlugField()
+
+    class Meta:
+        model = CircuitType
+        fields = ('name', 'slug', 'description')
+        help_texts = {
+            'name': 'Name of circuit type',
+        }
+
+
+class CircuitCSVForm(CustomFieldModelCSVForm):
+    provider = CSVModelChoiceField(
+        queryset=Provider.objects.all(),
+        to_field_name='name',
+        help_text='Assigned provider'
+    )
+    type = CSVModelChoiceField(
+        queryset=CircuitType.objects.all(),
+        to_field_name='name',
+        help_text='Type of circuit'
+    )
+    status = CSVChoiceField(
+        choices=CircuitStatusChoices,
+        required=False,
+        help_text='Operational status'
+    )
+    tenant = CSVModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Assigned tenant'
+    )
+
+    class Meta:
+        model = Circuit
+        fields = [
+            'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
+        ]

+ 159 - 0
netbox/circuits/forms/filtersets.py

@@ -0,0 +1,159 @@
+from django import forms
+from django.utils.translation import gettext as _
+
+from circuits.choices import CircuitStatusChoices
+from circuits.models import *
+from dcim.models import Region, Site, SiteGroup
+from extras.forms import CustomFieldModelFilterForm
+from tenancy.forms import TenancyFilterForm
+from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
+
+__all__ = (
+    'CircuitFilterForm',
+    'CircuitTypeFilterForm',
+    'ProviderFilterForm',
+    'ProviderNetworkFilterForm',
+)
+
+
+class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
+    model = Provider
+    field_groups = [
+        ['q', 'tag'],
+        ['region_id', 'site_group_id', 'site_id'],
+        ['asn'],
+    ]
+    q = forms.CharField(
+        required=False,
+        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
+        label=_('Search')
+    )
+    region_id = DynamicModelMultipleChoiceField(
+        queryset=Region.objects.all(),
+        required=False,
+        label=_('Region'),
+        fetch_trigger='open'
+    )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
+    site_id = DynamicModelMultipleChoiceField(
+        queryset=Site.objects.all(),
+        required=False,
+        query_params={
+            'region_id': '$region_id',
+            'site_group_id': '$site_group_id',
+        },
+        label=_('Site'),
+        fetch_trigger='open'
+    )
+    asn = forms.IntegerField(
+        required=False,
+        label=_('ASN')
+    )
+    tag = TagFilterField(model)
+
+
+class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
+    model = ProviderNetwork
+    field_groups = (
+        ('q', 'tag'),
+        ('provider_id',),
+    )
+    q = forms.CharField(
+        required=False,
+        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
+        label=_('Search')
+    )
+    provider_id = DynamicModelMultipleChoiceField(
+        queryset=Provider.objects.all(),
+        required=False,
+        label=_('Provider'),
+        fetch_trigger='open'
+    )
+    tag = TagFilterField(model)
+
+
+class CircuitTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
+    model = CircuitType
+    field_groups = [
+        ['q'],
+    ]
+    q = forms.CharField(
+        required=False,
+        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
+        label=_('Search')
+    )
+
+
+class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
+    model = Circuit
+    field_groups = [
+        ['q', 'tag'],
+        ['provider_id', 'provider_network_id'],
+        ['type_id', 'status', 'commit_rate'],
+        ['region_id', 'site_group_id', 'site_id'],
+        ['tenant_group_id', 'tenant_id'],
+    ]
+    q = forms.CharField(
+        required=False,
+        widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
+        label=_('Search')
+    )
+    type_id = DynamicModelMultipleChoiceField(
+        queryset=CircuitType.objects.all(),
+        required=False,
+        label=_('Type'),
+        fetch_trigger='open'
+    )
+    provider_id = DynamicModelMultipleChoiceField(
+        queryset=Provider.objects.all(),
+        required=False,
+        label=_('Provider'),
+        fetch_trigger='open'
+    )
+    provider_network_id = DynamicModelMultipleChoiceField(
+        queryset=ProviderNetwork.objects.all(),
+        required=False,
+        query_params={
+            'provider_id': '$provider_id'
+        },
+        label=_('Provider network'),
+        fetch_trigger='open'
+    )
+    status = forms.MultipleChoiceField(
+        choices=CircuitStatusChoices,
+        required=False,
+        widget=StaticSelectMultiple()
+    )
+    region_id = DynamicModelMultipleChoiceField(
+        queryset=Region.objects.all(),
+        required=False,
+        label=_('Region'),
+        fetch_trigger='open'
+    )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group'),
+        fetch_trigger='open'
+    )
+    site_id = DynamicModelMultipleChoiceField(
+        queryset=Site.objects.all(),
+        required=False,
+        query_params={
+            'region_id': '$region_id',
+            'site_group_id': '$site_group_id',
+        },
+        label=_('Site'),
+        fetch_trigger='open'
+    )
+    commit_rate = forms.IntegerField(
+        required=False,
+        min_value=0,
+        label=_('Commit rate (Kbps)')
+    )
+    tag = TagFilterField(model)

+ 168 - 0
netbox/circuits/forms/models.py

@@ -0,0 +1,168 @@
+from django import forms
+
+from circuits.models import *
+from dcim.models import Region, Site, SiteGroup
+from extras.forms import CustomFieldModelForm
+from extras.models import Tag
+from tenancy.forms import TenancyForm
+from utilities.forms import (
+    BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
+    SelectSpeedWidget, SmallTextarea, SlugField, StaticSelect,
+)
+
+__all__ = (
+    'CircuitForm',
+    'CircuitTerminationForm',
+    'CircuitTypeForm',
+    'ProviderForm',
+    'ProviderNetworkForm',
+)
+
+
+class ProviderForm(BootstrapMixin, CustomFieldModelForm):
+    slug = SlugField()
+    comments = CommentField()
+    tags = DynamicModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        required=False
+    )
+
+    class Meta:
+        model = Provider
+        fields = [
+            'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
+        ]
+        fieldsets = (
+            ('Provider', ('name', 'slug', 'asn', 'tags')),
+            ('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
+        )
+        widgets = {
+            'noc_contact': SmallTextarea(
+                attrs={'rows': 5}
+            ),
+            'admin_contact': SmallTextarea(
+                attrs={'rows': 5}
+            ),
+        }
+        help_texts = {
+            'name': "Full name of the provider",
+            'asn': "BGP autonomous system number (if applicable)",
+            'portal_url': "URL of the provider's customer support portal",
+            'noc_contact': "NOC email address and phone number",
+            'admin_contact': "Administrative contact email address and phone number",
+        }
+
+
+class ProviderNetworkForm(BootstrapMixin, CustomFieldModelForm):
+    provider = DynamicModelChoiceField(
+        queryset=Provider.objects.all()
+    )
+    comments = CommentField()
+    tags = DynamicModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        required=False
+    )
+
+    class Meta:
+        model = ProviderNetwork
+        fields = [
+            'provider', 'name', 'description', 'comments', 'tags',
+        ]
+        fieldsets = (
+            ('Provider Network', ('provider', 'name', 'description', 'tags')),
+        )
+
+
+class CircuitTypeForm(BootstrapMixin, CustomFieldModelForm):
+    slug = SlugField()
+
+    class Meta:
+        model = CircuitType
+        fields = [
+            'name', 'slug', 'description',
+        ]
+
+
+class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
+    provider = DynamicModelChoiceField(
+        queryset=Provider.objects.all()
+    )
+    type = DynamicModelChoiceField(
+        queryset=CircuitType.objects.all()
+    )
+    comments = CommentField()
+    tags = DynamicModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        required=False
+    )
+
+    class Meta:
+        model = Circuit
+        fields = [
+            'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
+            'comments', 'tags',
+        ]
+        fieldsets = (
+            ('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
+            ('Tenancy', ('tenant_group', 'tenant')),
+        )
+        help_texts = {
+            'cid': "Unique circuit ID",
+            'commit_rate': "Committed rate",
+        }
+        widgets = {
+            'status': StaticSelect(),
+            'install_date': DatePicker(),
+            'commit_rate': SelectSpeedWidget(),
+        }
+
+
+class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
+    region = DynamicModelChoiceField(
+        queryset=Region.objects.all(),
+        required=False,
+        initial_params={
+            'sites': '$site'
+        }
+    )
+    site_group = DynamicModelChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        initial_params={
+            'sites': '$site'
+        }
+    )
+    site = DynamicModelChoiceField(
+        queryset=Site.objects.all(),
+        query_params={
+            'region_id': '$region',
+            'group_id': '$site_group',
+        },
+        required=False
+    )
+    provider_network = DynamicModelChoiceField(
+        queryset=ProviderNetwork.objects.all(),
+        required=False
+    )
+
+    class Meta:
+        model = CircuitTermination
+        fields = [
+            'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected', 'port_speed',
+            'upstream_speed', 'xconnect_id', 'pp_info', 'description',
+        ]
+        help_texts = {
+            'port_speed': "Physical circuit speed",
+            'xconnect_id': "ID of the local cross-connect",
+            'pp_info': "Patch panel ID and port number(s)"
+        }
+        widgets = {
+            'term_side': forms.HiddenInput(),
+            'port_speed': SelectSpeedWidget(),
+            'upstream_speed': SelectSpeedWidget(),
+        }
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.fields['provider_network'].widget.add_query_param('provider_id', self.instance.circuit.provider_id)