mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Closes #12246: General cleanup of utilities modules
* Clean up base modules * Clean up forms modules * Clean up templatetags modules * Replace custom simplify_decimal filter with floatformat * Misc cleanup * Merge ReturnURLForm into ConfirmationForm * Clean up import statements for utilities.forms * Fix field class references in docs
This commit is contained in:
parent
59a6b3e71b
commit
d470848b29
@ -145,23 +145,23 @@ class MyModelFilterForm(NetBoxModelFilterSetForm):
|
||||
|
||||
In addition to the [form fields provided by Django](https://docs.djangoproject.com/en/stable/ref/forms/fields/), NetBox provides several field classes for use within forms to handle specific types of data. These can be imported from `utilities.forms.fields` and are documented below.
|
||||
|
||||
::: utilities.forms.ColorField
|
||||
::: utilities.forms.fields.ColorField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CommentField
|
||||
::: utilities.forms.fields.CommentField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.JSONField
|
||||
::: utilities.forms.fields.JSONField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.MACAddressField
|
||||
::: utilities.forms.fields.MACAddressField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.SlugField
|
||||
::: utilities.forms.fields.SlugField
|
||||
options:
|
||||
members: false
|
||||
|
||||
@ -170,52 +170,52 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
|
||||
!!! warning "Obsolete Fields"
|
||||
NetBox's custom `ChoiceField` and `MultipleChoiceField` classes are no longer necessary thanks to improvements made to the user interface. Django's native form fields can be used instead. These custom field classes will be removed in NetBox v3.6.
|
||||
|
||||
::: utilities.forms.ChoiceField
|
||||
::: utilities.forms.fields.ChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.MultipleChoiceField
|
||||
::: utilities.forms.fields.MultipleChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
## Dynamic Object Fields
|
||||
|
||||
::: utilities.forms.DynamicModelChoiceField
|
||||
::: utilities.forms.fields.DynamicModelChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.DynamicModelMultipleChoiceField
|
||||
::: utilities.forms.fields.DynamicModelMultipleChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
## Content Type Fields
|
||||
|
||||
::: utilities.forms.ContentTypeChoiceField
|
||||
::: utilities.forms.fields.ContentTypeChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.ContentTypeMultipleChoiceField
|
||||
::: utilities.forms.fields.ContentTypeMultipleChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
## CSV Import Fields
|
||||
|
||||
::: utilities.forms.CSVChoiceField
|
||||
::: utilities.forms.fields.CSVChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVMultipleChoiceField
|
||||
::: utilities.forms.fields.CSVMultipleChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVModelChoiceField
|
||||
::: utilities.forms.fields.CSVModelChoiceField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVContentTypeField
|
||||
::: utilities.forms.fields.CSVContentTypeField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVMultipleContentTypeField
|
||||
::: utilities.forms.fields.CSVMultipleContentTypeField
|
||||
options:
|
||||
members: false
|
||||
|
@ -6,9 +6,9 @@ from circuits.models import *
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
)
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.widgets import DatePicker
|
||||
|
||||
__all__ = (
|
||||
'CircuitBulkEditForm',
|
||||
|
@ -6,7 +6,8 @@ from dcim.models import Site
|
||||
from django.utils.translation import gettext as _
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import BootstrapMixin, CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
|
||||
__all__ = (
|
||||
'CircuitImportForm',
|
||||
|
@ -7,7 +7,8 @@ from dcim.models import Region, Site, SiteGroup
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
|
||||
from utilities.forms import DatePicker, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.widgets import DatePicker
|
||||
|
||||
__all__ = (
|
||||
'CircuitFilterForm',
|
||||
|
@ -5,9 +5,8 @@ from dcim.models import Site
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SlugField,
|
||||
)
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
||||
from utilities.forms.widgets import DatePicker, SelectSpeedWidget
|
||||
|
||||
__all__ = (
|
||||
'CircuitForm',
|
||||
|
@ -4,7 +4,9 @@ from django.utils.translation import gettext as _
|
||||
from core.choices import DataSourceTypeChoices
|
||||
from core.models import *
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from utilities.forms import add_blank_choice, BulkEditNullBooleanSelect, CommentField
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
__all__ = (
|
||||
'DataSourceBulkEditForm',
|
||||
|
@ -8,10 +8,9 @@ from core.models import *
|
||||
from extras.forms.mixins import SavedFiltersMixin
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from utilities.forms import (
|
||||
APISelectMultiple, BOOLEAN_WITH_BLANK_CHOICES, ContentTypeChoiceField, DateTimePicker,
|
||||
DynamicModelMultipleChoiceField, FilterForm,
|
||||
)
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
||||
from utilities.forms.fields import ContentTypeChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
||||
|
||||
__all__ = (
|
||||
'DataFileFilterForm',
|
||||
|
@ -6,7 +6,8 @@ from core.models import *
|
||||
from extras.forms.mixins import SyncedDataMixin
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from netbox.registry import registry
|
||||
from utilities.forms import CommentField, get_field_value
|
||||
from utilities.forms import get_field_value
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
|
||||
__all__ = (
|
||||
|
@ -4,7 +4,8 @@ from dcim.models import *
|
||||
from django.utils.translation import gettext as _
|
||||
from extras.forms import CustomFieldsMixin
|
||||
from extras.models import Tag
|
||||
from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
|
||||
from utilities.forms import BootstrapMixin, form_from_model
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
from .object_create import ComponentCreateForm
|
||||
|
||||
__all__ = (
|
||||
|
@ -10,10 +10,9 @@ from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, form_from_model, SelectSpeedWidget,
|
||||
)
|
||||
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, SelectSpeedWidget
|
||||
|
||||
__all__ = (
|
||||
'CableBulkEditForm',
|
||||
|
@ -12,8 +12,9 @@ from extras.models import ConfigTemplate
|
||||
from ipam.models import VRF
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField, CSVModelMultipleChoiceField
|
||||
from utilities.forms.fields import (
|
||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
|
||||
SlugField,
|
||||
)
|
||||
from virtualization.models import Cluster
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from utilities.forms.utils import get_field_value
|
||||
from utilities.forms import get_field_value
|
||||
|
||||
__all__ = (
|
||||
'InterfaceCommonForm',
|
||||
|
@ -1,9 +1,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination, Provider
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
from dcim.models import *
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from .model_forms import CableForm
|
||||
|
||||
|
||||
|
@ -10,10 +10,9 @@ from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, L2VPN, VRF
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm,
|
||||
TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, SelectSpeedWidget,
|
||||
)
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.widgets import APISelectMultiple, SelectSpeedWidget
|
||||
from wireless.choices import *
|
||||
|
||||
__all__ = (
|
||||
|
@ -11,11 +11,12 @@ from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
|
||||
from utilities.forms import BootstrapMixin, add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||
NumericArrayField, SlugField,
|
||||
)
|
||||
from utilities.forms.widgets import APISelect, HTMXSelect, SelectSpeedWidget, SelectWithPK
|
||||
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, SelectSpeedWidget, SelectWithPK
|
||||
from virtualization.models import Cluster
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
from .common import InterfaceCommonForm, ModuleCommonForm
|
||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.models import *
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
from . import model_forms
|
||||
|
||||
__all__ = (
|
||||
|
@ -12,12 +12,12 @@ LINKTERMINATION = """
|
||||
|
||||
CABLE_LENGTH = """
|
||||
{% load helpers %}
|
||||
{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
|
||||
{% if record.length %}{{ record.length|floatformat:"-2" }} {{ record.length_unit }}{% endif %}
|
||||
"""
|
||||
|
||||
WEIGHT = """
|
||||
{% load helpers %}
|
||||
{% if value %}{{ value|simplify_decimal }} {{ record.weight_unit }}{% endif %}
|
||||
{% if value %}{{ value|floatformat:"-2" }} {{ record.weight_unit }}{% endif %}
|
||||
"""
|
||||
|
||||
DEVICE_LINK = """
|
||||
|
@ -3,9 +3,9 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField,
|
||||
)
|
||||
from utilities.forms import BulkEditForm, add_blank_choice
|
||||
from utilities.forms.fields import ColorField
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextBulkEditForm',
|
||||
|
@ -7,7 +7,8 @@ from django.utils.translation import gettext as _
|
||||
from extras.choices import CustomFieldVisibilityChoices, CustomFieldTypeChoices
|
||||
from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField
|
||||
from utilities.forms import CSVModelForm
|
||||
from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVMultipleContentTypeField, SlugField
|
||||
|
||||
__all__ = (
|
||||
'ConfigTemplateImportForm',
|
||||
|
@ -10,10 +10,9 @@ from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.forms.base import NetBoxModelFilterSetForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelectMultiple, BOOLEAN_WITH_BLANK_CHOICES, ContentTypeMultipleChoiceField, DateTimePicker,
|
||||
DynamicModelMultipleChoiceField, FilterForm, TagFilterField,
|
||||
)
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||
from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
from .mixins import SavedFiltersMixin
|
||||
|
||||
|
@ -12,9 +12,10 @@ from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField,
|
||||
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||
from utilities.forms import BootstrapMixin, add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||
SlugField,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from utilities.forms import BootstrapMixin, DateTimePicker, SelectDurationWidget
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.widgets import DateTimePicker, SelectDurationWidget
|
||||
from utilities.utils import local_now
|
||||
|
||||
__all__ = (
|
||||
|
@ -1,7 +1,8 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from utilities.forms import BootstrapMixin, DateTimePicker, SelectDurationWidget
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.widgets import DateTimePicker, SelectDurationWidget
|
||||
from utilities.utils import local_now
|
||||
|
||||
__all__ = (
|
||||
|
@ -21,7 +21,8 @@ from extras.signals import clear_webhooks
|
||||
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
||||
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
||||
from utilities.exceptions import AbortScript, AbortTransaction
|
||||
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from .context_managers import change_logging
|
||||
from .forms import ScriptForm
|
||||
|
||||
|
@ -2,7 +2,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django_pglocks import advisory_lock
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import APIRootView
|
||||
@ -15,7 +15,7 @@ from ipam.models import *
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
from netbox.api.viewsets.mixins import ObjectValidationMixin
|
||||
from netbox.config import get_config
|
||||
from utilities.constants import ADVISORY_LOCK_KEYS
|
||||
from netbox.constants import ADVISORY_LOCK_KEYS
|
||||
from utilities.utils import count_related
|
||||
from . import serializers
|
||||
from ipam.models import L2VPN, L2VPNTermination
|
||||
|
@ -1,7 +1,8 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from utilities.forms import BootstrapMixin, ExpandableIPAddressField
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.fields import ExpandableIPAddressField
|
||||
|
||||
__all__ = (
|
||||
'IPAddressBulkCreateForm',
|
||||
|
@ -8,10 +8,11 @@ from ipam.models import *
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditNullBooleanSelect, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
NumericArrayField,
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||
)
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
__all__ = (
|
||||
'AggregateBulkEditForm',
|
||||
|
@ -9,7 +9,7 @@ from ipam.constants import *
|
||||
from ipam.models import *
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
||||
from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
__all__ = (
|
||||
|
@ -8,9 +8,9 @@ from ipam.constants import *
|
||||
from ipam.models import *
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
add_blank_choice, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
|
||||
|
@ -11,10 +11,12 @@ from ipam.models import *
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.exceptions import PermissionsViolation
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, NumericArrayField, SlugField,
|
||||
from utilities.forms import BootstrapMixin, add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||
SlugField,
|
||||
)
|
||||
from utilities.forms.widgets import DatePicker
|
||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||
|
||||
__all__ = (
|
||||
|
@ -5,3 +5,14 @@ NESTED_SERIALIZER_PREFIX = 'Nested'
|
||||
RQ_QUEUE_DEFAULT = 'default'
|
||||
RQ_QUEUE_HIGH = 'high'
|
||||
RQ_QUEUE_LOW = 'low'
|
||||
|
||||
# Keys for PostgreSQL advisory locks. These are arbitrary bigints used by the advisory_lock
|
||||
# context manager. When a lock is acquired, one of these keys will be used to identify said lock.
|
||||
# When adding a new key, pick something arbitrary and unique so that it is easily searchable in
|
||||
# query logs.
|
||||
ADVISORY_LOCK_KEYS = {
|
||||
'available-prefixes': 100100,
|
||||
'available-ips': 100200,
|
||||
'available-vlans': 100300,
|
||||
'available-asns': 100400,
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ from utilities.constants import (
|
||||
FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
|
||||
FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||
)
|
||||
from utilities.forms import MACAddressField
|
||||
from utilities.forms.fields import MACAddressField
|
||||
from utilities import filters
|
||||
|
||||
__all__ = (
|
||||
|
@ -242,7 +242,7 @@
|
||||
<th scope="row">Channel Frequency</th>
|
||||
<td>
|
||||
{% if object.rf_channel_frequency %}
|
||||
{{ object.rf_channel_frequency|simplify_decimal }} MHz
|
||||
{{ object.rf_channel_frequency|floatformat:"-2" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
@ -250,7 +250,7 @@
|
||||
{% if peer %}
|
||||
<td{% if peer.rf_channel_frequency != object.rf_channel_frequency %} class="text-danger"{% endif %}>
|
||||
{% if peer.rf_channel_frequency %}
|
||||
{{ peer.rf_channel_frequency|simplify_decimal }} MHz
|
||||
{{ peer.rf_channel_frequency|floatformat:"-2" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
@ -261,7 +261,7 @@
|
||||
<th scope="row">Channel Width</th>
|
||||
<td>
|
||||
{% if object.rf_channel_width %}
|
||||
{{ object.rf_channel_width|simplify_decimal }} MHz
|
||||
{{ object.rf_channel_width|floatformat:"-3" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
@ -269,7 +269,7 @@
|
||||
{% if peer %}
|
||||
<td{% if peer.rf_channel_width != object.rf_channel_width %} class="text-danger"{% endif %}>
|
||||
{% if peer.rf_channel_width %}
|
||||
{{ peer.rf_channel_width|simplify_decimal }} MHz
|
||||
{{ peer.rf_channel_width|floatformat:"-3" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
|
@ -31,7 +31,7 @@
|
||||
<th scope="row">Channel Frequency</th>
|
||||
<td>
|
||||
{% if interface.rf_channel_frequency %}
|
||||
{{ interface.rf_channel_frequency|simplify_decimal }} MHz
|
||||
{{ interface.rf_channel_frequency|floatformat:"-2" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
@ -41,7 +41,7 @@
|
||||
<th scope="row">Channel Width</th>
|
||||
<td>
|
||||
{% if interface.rf_channel_width %}
|
||||
{{ interface.rf_channel_width|simplify_decimal }} MHz
|
||||
{{ interface.rf_channel_width|floatformat:"-3" }} MHz
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
|
@ -3,7 +3,8 @@ from django import forms
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.choices import ContactPriorityChoices
|
||||
from tenancy.models import *
|
||||
from utilities.forms import CommentField, DynamicModelChoiceField, add_blank_choice
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentBulkEditForm',
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import CSVModelChoiceField, SlugField
|
||||
from utilities.forms.fields import CSVModelChoiceField, SlugField
|
||||
|
||||
__all__ = (
|
||||
'ContactImportForm',
|
||||
|
@ -2,7 +2,7 @@ from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from tenancy.models import *
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
|
||||
__all__ = (
|
||||
'ContactModelFilterForm',
|
||||
|
@ -2,9 +2,8 @@ from django import forms
|
||||
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, DynamicModelChoiceField, SlugField,
|
||||
)
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, SlugField
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentForm',
|
||||
|
@ -7,7 +7,8 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from ipam.formfields import IPNetworkFormField
|
||||
from netbox.preferences import PREFERENCES
|
||||
from utilities.forms import BootstrapMixin, DateTimePicker
|
||||
from utilities.forms import BootstrapMixin
|
||||
from utilities.forms.widgets import DateTimePicker
|
||||
from utilities.utils import flatten_dict
|
||||
from .models import Token, UserConfig
|
||||
|
||||
|
@ -10,6 +10,14 @@ from rest_framework.utils import formatting
|
||||
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
|
||||
from .utils import dynamic_import
|
||||
|
||||
__all__ = (
|
||||
'get_graphql_type_for_model',
|
||||
'get_serializer_for_model',
|
||||
'get_view_name',
|
||||
'is_api_request',
|
||||
'rest_api_server_error',
|
||||
)
|
||||
|
||||
|
||||
def get_serializer_for_model(model, prefix=''):
|
||||
"""
|
||||
|
@ -31,21 +31,6 @@ FILTER_TREENODE_NEGATION_LOOKUP_MAP = dict(
|
||||
n='in'
|
||||
)
|
||||
|
||||
|
||||
# Keys for PostgreSQL advisory locks. These are arbitrary bigints used by
|
||||
# the advisory_lock contextmanager. When a lock is acquired,
|
||||
# one of these keys will be used to identify said lock.
|
||||
#
|
||||
# When adding a new key, pick something arbitrary and unique so
|
||||
# that it is easily searchable in query logs.
|
||||
|
||||
ADVISORY_LOCK_KEYS = {
|
||||
'available-prefixes': 100100,
|
||||
'available-ips': 100200,
|
||||
'available-vlans': 100300,
|
||||
'available-asns': 100400,
|
||||
}
|
||||
|
||||
#
|
||||
# HTTP Request META safe copy
|
||||
#
|
||||
|
@ -3,6 +3,7 @@ from rest_framework.exceptions import APIException
|
||||
|
||||
__all__ = (
|
||||
'AbortRequest',
|
||||
'AbortScript',
|
||||
'AbortTransaction',
|
||||
'PermissionsViolation',
|
||||
'RQWorkerNotRunningException',
|
||||
|
@ -1,21 +1,23 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
|
||||
from utilities.ordering import naturalize
|
||||
from .forms import ColorSelect
|
||||
from .forms.widgets import ColorSelect
|
||||
from .validators import ColorValidator
|
||||
|
||||
ColorValidator = RegexValidator(
|
||||
regex='^[0-9a-f]{6}$',
|
||||
message='Enter a valid hexadecimal RGB color code.',
|
||||
code='invalid'
|
||||
__all__ = (
|
||||
'ColorField',
|
||||
'NaturalOrderingField',
|
||||
'NullableCharField',
|
||||
'RestrictedGenericForeignKey',
|
||||
)
|
||||
|
||||
|
||||
# Deprecated: Retained only to ensure successful migration from early releases
|
||||
# Use models.CharField(null=True) instead
|
||||
# TODO: Remove in v4.0
|
||||
class NullableCharField(models.CharField):
|
||||
description = "Stores empty values as NULL rather than ''"
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
import hashlib
|
||||
|
||||
__all__ = (
|
||||
'sha256_hash',
|
||||
)
|
||||
|
||||
|
||||
def sha256_hash(filepath):
|
||||
"""
|
||||
|
@ -6,6 +6,22 @@ from django_filters.constants import EMPTY_VALUES
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
__all__ = (
|
||||
'ContentTypeFilter',
|
||||
'MACAddressFilter',
|
||||
'MultiValueCharFilter',
|
||||
'MultiValueDateFilter',
|
||||
'MultiValueDateTimeFilter',
|
||||
'MultiValueDecimalFilter',
|
||||
'MultiValueMACAddressFilter',
|
||||
'MultiValueNumberFilter',
|
||||
'MultiValueTimeFilter',
|
||||
'MultiValueWWNFilter',
|
||||
'NullableCharFieldFilter',
|
||||
'NumericArrayFilter',
|
||||
'TreeNodeMultipleChoiceFilter',
|
||||
)
|
||||
|
||||
|
||||
def multivalue_field_factory(field_class):
|
||||
"""
|
||||
|
@ -1,5 +1,4 @@
|
||||
from .constants import *
|
||||
from .fields import *
|
||||
from .forms import *
|
||||
from .mixins import *
|
||||
from .utils import *
|
||||
from .widgets import *
|
||||
|
@ -1,3 +1,4 @@
|
||||
from .array import *
|
||||
from .content_types import *
|
||||
from .csv import *
|
||||
from .dynamic import *
|
||||
|
24
netbox/utilities/forms/fields/array.py
Normal file
24
netbox/utilities/forms/fields/array.py
Normal file
@ -0,0 +1,24 @@
|
||||
from django import forms
|
||||
from django.contrib.postgres.forms import SimpleArrayField
|
||||
|
||||
from ..utils import parse_numeric_range
|
||||
|
||||
__all__ = (
|
||||
'NumericArrayField',
|
||||
)
|
||||
|
||||
|
||||
class NumericArrayField(SimpleArrayField):
|
||||
|
||||
def clean(self, value):
|
||||
if value and not self.to_python(value):
|
||||
raise forms.ValidationError(f'Invalid list ({value}). '
|
||||
f'Must be numeric and ranges must be in ascending order')
|
||||
return super().clean(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
value = ','.join([str(n) for n in parse_numeric_range(value)])
|
||||
return super().to_python(value)
|
@ -1,6 +1,5 @@
|
||||
from django import forms
|
||||
|
||||
from utilities.forms import widgets
|
||||
from utilities.utils import content_type_name
|
||||
|
||||
__all__ = (
|
||||
|
@ -1,14 +1,9 @@
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from utilities.choices import unpack_grouped_choices
|
||||
from utilities.forms.utils import parse_csv, validate_csv
|
||||
from utilities.utils import content_type_identifier
|
||||
|
||||
__all__ = (
|
||||
|
@ -2,96 +2,31 @@ import re
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from .widgets import APISelect, APISelectMultiple, ClearableFileInput
|
||||
from .mixins import BootstrapMixin
|
||||
|
||||
__all__ = (
|
||||
'BootstrapMixin',
|
||||
'BulkEditForm',
|
||||
'BulkRenameForm',
|
||||
'ConfirmationForm',
|
||||
'CSVModelForm',
|
||||
'FilterForm',
|
||||
'ReturnURLForm',
|
||||
'TableConfigForm',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Mixins
|
||||
#
|
||||
|
||||
class BootstrapMixin:
|
||||
class ConfirmationForm(BootstrapMixin, forms.Form):
|
||||
"""
|
||||
Add the base Bootstrap CSS classes to form elements.
|
||||
A generic confirmation form. The form is not valid unless the `confirm` field is checked.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
exempt_widgets = [
|
||||
forms.FileInput,
|
||||
forms.RadioSelect,
|
||||
APISelect,
|
||||
APISelectMultiple,
|
||||
ClearableFileInput,
|
||||
]
|
||||
|
||||
for field_name, field in self.fields.items():
|
||||
css = field.widget.attrs.get('class', '')
|
||||
|
||||
if field.widget.__class__ in exempt_widgets:
|
||||
continue
|
||||
|
||||
elif isinstance(field.widget, forms.CheckboxInput):
|
||||
field.widget.attrs['class'] = f'{css} form-check-input'
|
||||
|
||||
elif isinstance(field.widget, forms.SelectMultiple):
|
||||
if 'size' not in field.widget.attrs:
|
||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
||||
|
||||
elif isinstance(field.widget, forms.Select):
|
||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
||||
|
||||
else:
|
||||
field.widget.attrs['class'] = f'{css} form-control'
|
||||
|
||||
if field.required and not isinstance(field.widget, forms.FileInput):
|
||||
field.widget.attrs['required'] = 'required'
|
||||
|
||||
if 'placeholder' not in field.widget.attrs and field.label is not None:
|
||||
field.widget.attrs['placeholder'] = field.label
|
||||
|
||||
def is_valid(self):
|
||||
is_valid = super().is_valid()
|
||||
|
||||
# Apply is-invalid CSS class to fields with errors
|
||||
if not is_valid:
|
||||
for field_name in self.errors:
|
||||
# Ignore e.g. __all__
|
||||
if field := self.fields.get(field_name):
|
||||
css = field.widget.attrs.get('class', '')
|
||||
field.widget.attrs['class'] = f'{css} is-invalid'
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
#
|
||||
# Form classes
|
||||
#
|
||||
|
||||
class ReturnURLForm(forms.Form):
|
||||
"""
|
||||
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
||||
"""
|
||||
return_url = forms.CharField(required=False, widget=forms.HiddenInput())
|
||||
|
||||
|
||||
class ConfirmationForm(BootstrapMixin, ReturnURLForm):
|
||||
"""
|
||||
A generic confirmation form. The form is not valid unless the confirm field is checked.
|
||||
"""
|
||||
confirm = forms.BooleanField(required=True, widget=forms.HiddenInput(), initial=True)
|
||||
return_url = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
confirm = forms.BooleanField(
|
||||
required=True,
|
||||
widget=forms.HiddenInput(),
|
||||
initial=True
|
||||
)
|
||||
|
||||
|
||||
class BulkEditForm(BootstrapMixin, forms.Form):
|
||||
|
62
netbox/utilities/forms/mixins.py
Normal file
62
netbox/utilities/forms/mixins.py
Normal file
@ -0,0 +1,62 @@
|
||||
from django import forms
|
||||
|
||||
from .widgets import APISelect, APISelectMultiple, ClearableFileInput
|
||||
|
||||
__all__ = (
|
||||
'BootstrapMixin',
|
||||
)
|
||||
|
||||
|
||||
class BootstrapMixin:
|
||||
"""
|
||||
Add the base Bootstrap CSS classes to form elements.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
exempt_widgets = [
|
||||
forms.FileInput,
|
||||
forms.RadioSelect,
|
||||
APISelect,
|
||||
APISelectMultiple,
|
||||
ClearableFileInput,
|
||||
]
|
||||
|
||||
for field_name, field in self.fields.items():
|
||||
css = field.widget.attrs.get('class', '')
|
||||
|
||||
if field.widget.__class__ in exempt_widgets:
|
||||
continue
|
||||
|
||||
elif isinstance(field.widget, forms.CheckboxInput):
|
||||
field.widget.attrs['class'] = f'{css} form-check-input'
|
||||
|
||||
elif isinstance(field.widget, forms.SelectMultiple):
|
||||
if 'size' not in field.widget.attrs:
|
||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
||||
|
||||
elif isinstance(field.widget, forms.Select):
|
||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
||||
|
||||
else:
|
||||
field.widget.attrs['class'] = f'{css} form-control'
|
||||
|
||||
if field.required and not isinstance(field.widget, forms.FileInput):
|
||||
field.widget.attrs['required'] = 'required'
|
||||
|
||||
if 'placeholder' not in field.widget.attrs and field.label is not None:
|
||||
field.widget.attrs['placeholder'] = field.label
|
||||
|
||||
def is_valid(self):
|
||||
is_valid = super().is_valid()
|
||||
|
||||
# Apply is-invalid CSS class to fields with errors
|
||||
if not is_valid:
|
||||
for field_name in self.errors:
|
||||
# Ignore e.g. __all__
|
||||
if field := self.fields.get(field_name):
|
||||
css = field.widget.attrs.get('class', '')
|
||||
field.widget.attrs['class'] = f'{css} is-invalid'
|
||||
|
||||
return is_valid
|
4
netbox/utilities/forms/widgets/__init__.py
Normal file
4
netbox/utilities/forms/widgets/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .apiselect import *
|
||||
from .datetime import *
|
||||
from .misc import *
|
||||
from .select import *
|
@ -1,120 +1,14 @@
|
||||
import json
|
||||
from typing import Dict, Sequence, List, Tuple, Union
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.forms import SimpleArrayField
|
||||
|
||||
from utilities.choices import ColorChoices
|
||||
from .utils import add_blank_choice, parse_numeric_range
|
||||
|
||||
__all__ = (
|
||||
'APISelect',
|
||||
'APISelectMultiple',
|
||||
'BulkEditNullBooleanSelect',
|
||||
'ClearableFileInput',
|
||||
'ColorSelect',
|
||||
'DatePicker',
|
||||
'DateTimePicker',
|
||||
'HTMXSelect',
|
||||
'MarkdownWidget',
|
||||
'NumericArrayField',
|
||||
'SelectDurationWidget',
|
||||
'SelectSpeedWidget',
|
||||
'SelectWithPK',
|
||||
'SlugWidget',
|
||||
'TimePicker',
|
||||
)
|
||||
|
||||
JSONPrimitive = Union[str, bool, int, float, None]
|
||||
QueryParamValue = Union[JSONPrimitive, Sequence[JSONPrimitive]]
|
||||
QueryParam = Dict[str, QueryParamValue]
|
||||
ProcessedParams = Sequence[Dict[str, Sequence[JSONPrimitive]]]
|
||||
|
||||
|
||||
class SlugWidget(forms.TextInput):
|
||||
"""
|
||||
Subclass TextInput and add a slug regeneration button next to the form field.
|
||||
"""
|
||||
template_name = 'widgets/sluginput.html'
|
||||
|
||||
|
||||
class ColorSelect(forms.Select):
|
||||
"""
|
||||
Extends the built-in Select widget to colorize each <option>.
|
||||
"""
|
||||
option_template_name = 'widgets/colorselect_option.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['choices'] = add_blank_choice(ColorChoices)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'netbox-color-select'
|
||||
|
||||
|
||||
class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
|
||||
"""
|
||||
A Select widget for NullBooleanFields
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Override the built-in choice labels
|
||||
self.choices = (
|
||||
('1', '---------'),
|
||||
('2', 'Yes'),
|
||||
('3', 'No'),
|
||||
)
|
||||
self.attrs['class'] = 'netbox-static-select'
|
||||
|
||||
|
||||
class SelectWithPK(forms.Select):
|
||||
"""
|
||||
Include the primary key of each option in the option label (e.g. "Router7 (4721)").
|
||||
"""
|
||||
option_template_name = 'widgets/select_option_with_pk.html'
|
||||
|
||||
|
||||
class SelectSpeedWidget(forms.NumberInput):
|
||||
"""
|
||||
Speed field with dropdown selections for convenience.
|
||||
"""
|
||||
template_name = 'widgets/select_speed.html'
|
||||
|
||||
|
||||
class SelectDurationWidget(forms.NumberInput):
|
||||
"""
|
||||
Dropdown to select one of several common options for a time duration (in minutes).
|
||||
"""
|
||||
template_name = 'widgets/select_duration.html'
|
||||
|
||||
|
||||
class MarkdownWidget(forms.Textarea):
|
||||
template_name = 'widgets/markdown_input.html'
|
||||
|
||||
|
||||
class NumericArrayField(SimpleArrayField):
|
||||
|
||||
def clean(self, value):
|
||||
if value and not self.to_python(value):
|
||||
raise forms.ValidationError(f'Invalid list ({value}). '
|
||||
f'Must be numeric and ranges must be in ascending order')
|
||||
return super().clean(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
value = ','.join([str(n) for n in parse_numeric_range(value)])
|
||||
return super().to_python(value)
|
||||
|
||||
|
||||
class ClearableFileInput(forms.ClearableFileInput):
|
||||
"""
|
||||
Override Django's stock ClearableFileInput with a custom template.
|
||||
"""
|
||||
template_name = 'widgets/clearable_file_input.html'
|
||||
|
||||
|
||||
class APISelect(forms.Select):
|
||||
"""
|
||||
@ -144,7 +38,7 @@ class APISelect(forms.Select):
|
||||
result.static_params = {}
|
||||
return result
|
||||
|
||||
def _process_query_param(self, key: str, value: JSONPrimitive) -> None:
|
||||
def _process_query_param(self, key, value) -> None:
|
||||
"""
|
||||
Based on query param value's type and value, update instance's dynamic/static params.
|
||||
"""
|
||||
@ -187,7 +81,7 @@ class APISelect(forms.Select):
|
||||
else:
|
||||
self.static_params[key] = [value]
|
||||
|
||||
def _process_query_params(self, query_params: QueryParam) -> None:
|
||||
def _process_query_params(self, query_params):
|
||||
"""
|
||||
Process an entire query_params dictionary, and handle primitive or list values.
|
||||
"""
|
||||
@ -199,7 +93,7 @@ class APISelect(forms.Select):
|
||||
else:
|
||||
self._process_query_param(key, value)
|
||||
|
||||
def _serialize_params(self, key: str, params: ProcessedParams) -> None:
|
||||
def _serialize_params(self, key, params):
|
||||
"""
|
||||
Serialize dynamic or static query params to JSON and add the serialized value to
|
||||
the widget attributes by `key`.
|
||||
@ -214,7 +108,7 @@ class APISelect(forms.Select):
|
||||
# attributes to HTML elements and parsed on the client.
|
||||
self.attrs[key] = json.dumps([*current, *params], separators=(',', ':'))
|
||||
|
||||
def _add_dynamic_params(self) -> None:
|
||||
def _add_dynamic_params(self):
|
||||
"""
|
||||
Convert post-processed dynamic query params to data structure expected by front-
|
||||
end, serialize the value to JSON, and add it to the widget attributes.
|
||||
@ -227,7 +121,7 @@ class APISelect(forms.Select):
|
||||
except IndexError as error:
|
||||
raise RuntimeError(f"Missing required value for dynamic query param: '{self.dynamic_params}'") from error
|
||||
|
||||
def _add_static_params(self) -> None:
|
||||
def _add_static_params(self):
|
||||
"""
|
||||
Convert post-processed static query params to data structure expected by front-
|
||||
end, serialize the value to JSON, and add it to the widget attributes.
|
||||
@ -240,7 +134,7 @@ class APISelect(forms.Select):
|
||||
except IndexError as error:
|
||||
raise RuntimeError(f"Missing required value for static query param: '{self.static_params}'") from error
|
||||
|
||||
def add_query_params(self, query_params: QueryParam) -> None:
|
||||
def add_query_params(self, query_params):
|
||||
"""
|
||||
Proccess & add a dictionary of URL query parameters to the widget attributes.
|
||||
"""
|
||||
@ -251,7 +145,7 @@ class APISelect(forms.Select):
|
||||
# Add processed static parameters to widget attributes.
|
||||
self._add_static_params()
|
||||
|
||||
def add_query_param(self, key: str, value: QueryParamValue) -> None:
|
||||
def add_query_param(self, key, value) -> None:
|
||||
"""
|
||||
Process & add a key/value pair of URL query parameters to the widget attributes.
|
||||
"""
|
||||
@ -264,49 +158,3 @@ class APISelectMultiple(APISelect, forms.SelectMultiple):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.attrs['data-multiple'] = 1
|
||||
|
||||
|
||||
class DatePicker(forms.TextInput):
|
||||
"""
|
||||
Date picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'date-picker'
|
||||
self.attrs['placeholder'] = 'YYYY-MM-DD'
|
||||
|
||||
|
||||
class DateTimePicker(forms.TextInput):
|
||||
"""
|
||||
DateTime picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'datetime-picker'
|
||||
self.attrs['placeholder'] = 'YYYY-MM-DD hh:mm:ss'
|
||||
|
||||
|
||||
class TimePicker(forms.TextInput):
|
||||
"""
|
||||
Time picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'time-picker'
|
||||
self.attrs['placeholder'] = 'hh:mm:ss'
|
||||
|
||||
|
||||
class HTMXSelect(forms.Select):
|
||||
"""
|
||||
Selection widget that will re-generate the HTML form upon the selection of a new option.
|
||||
"""
|
||||
def __init__(self, hx_url='.', hx_target_id='form_fields', attrs=None, **kwargs):
|
||||
_attrs = {
|
||||
'hx-get': hx_url,
|
||||
'hx-include': f'#{hx_target_id}',
|
||||
'hx-target': f'#{hx_target_id}',
|
||||
}
|
||||
if attrs:
|
||||
_attrs.update(attrs)
|
||||
|
||||
super().__init__(attrs=_attrs, **kwargs)
|
37
netbox/utilities/forms/widgets/datetime.py
Normal file
37
netbox/utilities/forms/widgets/datetime.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django import forms
|
||||
|
||||
__all__ = (
|
||||
'DatePicker',
|
||||
'DateTimePicker',
|
||||
'TimePicker',
|
||||
)
|
||||
|
||||
|
||||
class DatePicker(forms.TextInput):
|
||||
"""
|
||||
Date picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'date-picker'
|
||||
self.attrs['placeholder'] = 'YYYY-MM-DD'
|
||||
|
||||
|
||||
class DateTimePicker(forms.TextInput):
|
||||
"""
|
||||
DateTime picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'datetime-picker'
|
||||
self.attrs['placeholder'] = 'YYYY-MM-DD hh:mm:ss'
|
||||
|
||||
|
||||
class TimePicker(forms.TextInput):
|
||||
"""
|
||||
Time picker using Flatpickr.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'time-picker'
|
||||
self.attrs['placeholder'] = 'hh:mm:ss'
|
28
netbox/utilities/forms/widgets/misc.py
Normal file
28
netbox/utilities/forms/widgets/misc.py
Normal file
@ -0,0 +1,28 @@
|
||||
from django import forms
|
||||
|
||||
__all__ = (
|
||||
'ClearableFileInput',
|
||||
'MarkdownWidget',
|
||||
'SlugWidget',
|
||||
)
|
||||
|
||||
|
||||
class ClearableFileInput(forms.ClearableFileInput):
|
||||
"""
|
||||
Override Django's stock ClearableFileInput with a custom template.
|
||||
"""
|
||||
template_name = 'widgets/clearable_file_input.html'
|
||||
|
||||
|
||||
class MarkdownWidget(forms.Textarea):
|
||||
"""
|
||||
Provide a live preview for Markdown-formatted content.
|
||||
"""
|
||||
template_name = 'widgets/markdown_input.html'
|
||||
|
||||
|
||||
class SlugWidget(forms.TextInput):
|
||||
"""
|
||||
Subclass TextInput and add a slug regeneration button next to the form field.
|
||||
"""
|
||||
template_name = 'widgets/sluginput.html'
|
79
netbox/utilities/forms/widgets/select.py
Normal file
79
netbox/utilities/forms/widgets/select.py
Normal file
@ -0,0 +1,79 @@
|
||||
from django import forms
|
||||
|
||||
from utilities.choices import ColorChoices
|
||||
from ..utils import add_blank_choice
|
||||
|
||||
__all__ = (
|
||||
'BulkEditNullBooleanSelect',
|
||||
'ColorSelect',
|
||||
'HTMXSelect',
|
||||
'SelectDurationWidget',
|
||||
'SelectSpeedWidget',
|
||||
'SelectWithPK',
|
||||
)
|
||||
|
||||
|
||||
class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
|
||||
"""
|
||||
A Select widget for NullBooleanFields
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Override the built-in choice labels
|
||||
self.choices = (
|
||||
('1', '---------'),
|
||||
('2', 'Yes'),
|
||||
('3', 'No'),
|
||||
)
|
||||
self.attrs['class'] = 'netbox-static-select'
|
||||
|
||||
|
||||
class ColorSelect(forms.Select):
|
||||
"""
|
||||
Extends the built-in Select widget to colorize each <option>.
|
||||
"""
|
||||
option_template_name = 'widgets/colorselect_option.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['choices'] = add_blank_choice(ColorChoices)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attrs['class'] = 'netbox-color-select'
|
||||
|
||||
|
||||
class HTMXSelect(forms.Select):
|
||||
"""
|
||||
Selection widget that will re-generate the HTML form upon the selection of a new option.
|
||||
"""
|
||||
def __init__(self, hx_url='.', hx_target_id='form_fields', attrs=None, **kwargs):
|
||||
_attrs = {
|
||||
'hx-get': hx_url,
|
||||
'hx-include': f'#{hx_target_id}',
|
||||
'hx-target': f'#{hx_target_id}',
|
||||
}
|
||||
if attrs:
|
||||
_attrs.update(attrs)
|
||||
|
||||
super().__init__(attrs=_attrs, **kwargs)
|
||||
|
||||
|
||||
class SelectWithPK(forms.Select):
|
||||
"""
|
||||
Include the primary key of each option in the option label (e.g. "Router7 (4721)").
|
||||
"""
|
||||
option_template_name = 'widgets/select_option_with_pk.html'
|
||||
|
||||
|
||||
class SelectDurationWidget(forms.NumberInput):
|
||||
"""
|
||||
Dropdown to select one of several common options for a time duration (in minutes).
|
||||
"""
|
||||
template_name = 'widgets/select_duration.html'
|
||||
|
||||
|
||||
class SelectSpeedWidget(forms.NumberInput):
|
||||
"""
|
||||
Speed field with dropdown selections for convenience.
|
||||
"""
|
||||
template_name = 'widgets/select_speed.html'
|
@ -1,20 +1,23 @@
|
||||
import functools
|
||||
|
||||
import graphql
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.db.models import ForeignKey, Prefetch
|
||||
from django.db.models import ForeignKey
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields.reverse_related import ManyToOneRel
|
||||
from graphene import InputObjectType
|
||||
from graphene.types.generic import GenericScalar
|
||||
from graphene.types.resolver import default_resolver
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphql import FieldNode, GraphQLObjectType, GraphQLResolveInfo, GraphQLSchema
|
||||
from graphql import GraphQLResolveInfo, GraphQLSchema
|
||||
from graphql.execution.execute import get_field_def
|
||||
from graphql.language.ast import FragmentSpreadNode, InlineFragmentNode, VariableNode
|
||||
from graphql.pyutils import Path
|
||||
from graphql.type.definition import GraphQLInterfaceType, GraphQLUnionType
|
||||
|
||||
__all__ = (
|
||||
'gql_query_optimizer',
|
||||
)
|
||||
|
||||
|
||||
def gql_query_optimizer(queryset, info, **options):
|
||||
return QueryOptimizer(info).optimize(queryset)
|
||||
|
@ -1,5 +1,10 @@
|
||||
from urllib.parse import urlparse
|
||||
|
||||
__all__ = (
|
||||
'is_embedded',
|
||||
'is_htmx',
|
||||
)
|
||||
|
||||
|
||||
def is_htmx(request):
|
||||
"""
|
||||
|
@ -1,6 +1,10 @@
|
||||
import markdown
|
||||
from markdown.inlinepatterns import SimpleTagPattern
|
||||
|
||||
__all__ = (
|
||||
'StrikethroughExtension',
|
||||
)
|
||||
|
||||
STRIKE_RE = r'(~{2})(.+?)(~{2})'
|
||||
|
||||
|
||||
|
@ -3,6 +3,10 @@ from timezone_field import TimeZoneField
|
||||
|
||||
from netbox.config import ConfigItem
|
||||
|
||||
__all__ = (
|
||||
'custom_deconstruct',
|
||||
)
|
||||
|
||||
|
||||
SKIP_FIELDS = (
|
||||
TimeZoneField,
|
||||
|
@ -4,6 +4,11 @@ from mptt.querysets import TreeQuerySet as TreeQuerySet_
|
||||
from django.db.models import Manager
|
||||
from .querysets import RestrictedQuerySet
|
||||
|
||||
__all__ = (
|
||||
'TreeManager',
|
||||
'TreeQuerySet',
|
||||
)
|
||||
|
||||
|
||||
class TreeQuerySet(TreeQuerySet_, RestrictedQuerySet):
|
||||
"""
|
||||
|
@ -1,5 +1,10 @@
|
||||
import re
|
||||
|
||||
__all__ = (
|
||||
'naturalize',
|
||||
'naturalize_interface',
|
||||
)
|
||||
|
||||
INTERFACE_NAME_REGEX = r'(^(?P<type>[^\d\.:]+)?)' \
|
||||
r'((?P<slot>\d+)/)?' \
|
||||
r'((?P<subslot>\d+)/)?' \
|
||||
|
@ -2,6 +2,12 @@ from django.core.paginator import Paginator, Page
|
||||
|
||||
from netbox.config import get_config
|
||||
|
||||
__all__ = (
|
||||
'EnhancedPage',
|
||||
'EnhancedPaginator',
|
||||
'get_paginate_count',
|
||||
)
|
||||
|
||||
|
||||
class EnhancedPaginator(Paginator):
|
||||
default_page_lengths = (
|
||||
|
@ -1,5 +1,10 @@
|
||||
from django.contrib.postgres.aggregates import JSONBAgg
|
||||
from django.db.models import F, Func
|
||||
from django.db.models import Func
|
||||
|
||||
__all__ = (
|
||||
'CollateAsChar',
|
||||
'EmptyGroupByJSONBAgg',
|
||||
)
|
||||
|
||||
|
||||
class CollateAsChar(Func):
|
||||
|
@ -3,6 +3,11 @@ from django.db.models import Prefetch, QuerySet
|
||||
from users.constants import CONSTRAINT_TOKEN_USER
|
||||
from utilities.permissions import permission_is_exempt, qs_filter_from_constraints
|
||||
|
||||
__all__ = (
|
||||
'RestrictedPrefetch',
|
||||
'RestrictedQuerySet',
|
||||
)
|
||||
|
||||
|
||||
class RestrictedPrefetch(Prefetch):
|
||||
"""
|
||||
|
@ -1,3 +1,8 @@
|
||||
__all__ = (
|
||||
'linkify_phone',
|
||||
)
|
||||
|
||||
|
||||
def linkify_phone(value):
|
||||
"""
|
||||
Render a telephone number as a hyperlink.
|
||||
|
@ -13,6 +13,21 @@ from netbox.config import get_config
|
||||
from utilities.markdown import StrikethroughExtension
|
||||
from utilities.utils import clean_html, foreground_color, title
|
||||
|
||||
__all__ = (
|
||||
'bettertitle',
|
||||
'content_type',
|
||||
'content_type_id',
|
||||
'fgcolor',
|
||||
'linkify',
|
||||
'meta',
|
||||
'placeholder',
|
||||
'render_json',
|
||||
'render_markdown',
|
||||
'render_yaml',
|
||||
'split',
|
||||
'tzoffset',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
from django import template
|
||||
|
||||
__all__ = (
|
||||
'badge',
|
||||
'checkmark',
|
||||
'customfield_value',
|
||||
'tag',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -5,6 +5,18 @@ from django.urls import NoReverseMatch, reverse
|
||||
from extras.models import ExportTemplate
|
||||
from utilities.utils import get_viewname, prepare_cloned_fields
|
||||
|
||||
__all__ = (
|
||||
'add_button',
|
||||
'bulk_delete_button',
|
||||
'bulk_edit_button',
|
||||
'clone_button',
|
||||
'delete_button',
|
||||
'edit_button',
|
||||
'export_button',
|
||||
'import_button',
|
||||
'sync_button',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
from django import template
|
||||
|
||||
__all__ = (
|
||||
'getfield',
|
||||
'render_custom_fields',
|
||||
'render_errors',
|
||||
'render_field',
|
||||
'render_form',
|
||||
'widget_type',
|
||||
)
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
from typing import Dict, Any
|
||||
@ -15,6 +14,29 @@ from django.utils.safestring import mark_safe
|
||||
from utilities.forms import get_selected_values, TableConfigForm
|
||||
from utilities.utils import get_viewname
|
||||
|
||||
__all__ = (
|
||||
'annotated_date',
|
||||
'annotated_now',
|
||||
'applied_filters',
|
||||
'as_range',
|
||||
'divide',
|
||||
'get_item',
|
||||
'get_key',
|
||||
'humanize_megabytes',
|
||||
'humanize_speed',
|
||||
'icon_from_status',
|
||||
'kg_to_pounds',
|
||||
'meters_to_feet',
|
||||
'percentage',
|
||||
'querystring',
|
||||
'startswith',
|
||||
'status_from_tag',
|
||||
'table_config_form',
|
||||
'utilization_graph',
|
||||
'validated_viewname',
|
||||
'viewname',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@ -83,19 +105,6 @@ def humanize_megabytes(mb):
|
||||
return f'{mb} MB'
|
||||
|
||||
|
||||
@register.filter()
|
||||
def simplify_decimal(value):
|
||||
"""
|
||||
Return the simplest expression of a decimal value. Examples:
|
||||
1.00 => '1'
|
||||
1.20 => '1.2'
|
||||
1.23 => '1.23'
|
||||
"""
|
||||
if type(value) is not decimal.Decimal:
|
||||
return value
|
||||
return str(value).rstrip('0').rstrip('.')
|
||||
|
||||
|
||||
@register.filter(expects_localtime=True)
|
||||
def annotated_date(date_value):
|
||||
"""
|
||||
@ -145,14 +154,6 @@ def percentage(x, y):
|
||||
return round(x / y * 100, 1)
|
||||
|
||||
|
||||
@register.filter()
|
||||
def has_perms(user, permissions_list):
|
||||
"""
|
||||
Return True if the user has *all* permissions in the list.
|
||||
"""
|
||||
return user.has_perms(permissions_list)
|
||||
|
||||
|
||||
@register.filter()
|
||||
def as_range(n):
|
||||
"""
|
||||
|
@ -4,6 +4,10 @@ from django.template import Context
|
||||
|
||||
from netbox.navigation.menu import MENUS
|
||||
|
||||
__all__ = (
|
||||
'nav',
|
||||
)
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -1,5 +1,13 @@
|
||||
from django import template
|
||||
|
||||
__all__ = (
|
||||
'can_add',
|
||||
'can_change',
|
||||
'can_delete',
|
||||
'can_sync',
|
||||
'can_view',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -6,6 +6,10 @@ from django.utils.module_loading import import_string
|
||||
from netbox.registry import registry
|
||||
from utilities.utils import get_viewname
|
||||
|
||||
__all__ = (
|
||||
'model_view_tabs',
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -4,6 +4,10 @@ from django.views.generic import View
|
||||
|
||||
from netbox.registry import registry
|
||||
|
||||
__all__ = (
|
||||
'get_model_urls',
|
||||
)
|
||||
|
||||
|
||||
def get_model_urls(app_label, model_name):
|
||||
"""
|
||||
|
@ -1,10 +1,24 @@
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import _lazy_re_compile, BaseValidator, URLValidator
|
||||
from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile
|
||||
|
||||
from netbox.config import get_config
|
||||
|
||||
__all__ = (
|
||||
'ColorValidator',
|
||||
'EnhancedURLValidator',
|
||||
'ExclusionValidator',
|
||||
'validate_regex',
|
||||
)
|
||||
|
||||
|
||||
ColorValidator = RegexValidator(
|
||||
regex='^[0-9a-f]{6}$',
|
||||
message='Enter a valid hexadecimal RGB color code.',
|
||||
code='invalid'
|
||||
)
|
||||
|
||||
|
||||
class EnhancedURLValidator(URLValidator):
|
||||
"""
|
||||
|
@ -1,7 +1,8 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from utilities.forms import BootstrapMixin, ExpandableNameField, form_from_model
|
||||
from utilities.forms import BootstrapMixin, form_from_model
|
||||
from utilities.forms.fields import ExpandableNameField
|
||||
from virtualization.models import VMInterface, VirtualMachine
|
||||
|
||||
__all__ = (
|
||||
|
@ -7,10 +7,9 @@ from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
|
||||
from ipam.models import VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditNullBooleanSelect, BulkRenameForm, CommentField, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField
|
||||
)
|
||||
from utilities.forms import BulkRenameForm, add_blank_choice
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.models import Device, DeviceRole, Platform, Site
|
||||
from django.utils.translation import gettext as _
|
||||
from ipam.models import VRF
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
|
||||
|
@ -6,9 +6,8 @@ from extras.forms import LocalConfigContextFilterForm
|
||||
from ipam.models import L2VPN, VRF
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
DynamicModelMultipleChoiceField, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
|
||||
|
@ -8,9 +8,9 @@ from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGr
|
||||
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, ConfirmationForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
JSONField, SlugField,
|
||||
from utilities.forms import BootstrapMixin, ConfirmationForm
|
||||
from utilities.forms.fields import (
|
||||
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||
)
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
from virtualization.models import *
|
||||
|
@ -1,4 +1,4 @@
|
||||
from utilities.forms import ExpandableNameField
|
||||
from utilities.forms.fields import ExpandableNameField
|
||||
from .model_forms import VMInterfaceForm
|
||||
|
||||
__all__ = (
|
||||
|
@ -5,7 +5,8 @@ from dcim.choices import LinkStatusChoices
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import add_blank_choice, CommentField, DynamicModelChoiceField
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
||||
from wireless.choices import *
|
||||
from wireless.constants import SSID_MAX_LENGTH
|
||||
from wireless.models import *
|
||||
|
@ -1,10 +1,11 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from dcim.models import Interface
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from wireless.choices import *
|
||||
from wireless.models import *
|
||||
|
||||
|
@ -4,7 +4,8 @@ from django.utils.translation import gettext as _
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
||||
from wireless.choices import *
|
||||
from wireless.models import *
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
|
||||
from ipam.models import VLAN, VLANGroup
|
||||
|
||||
from dcim.models import Device, Interface, Location, Site
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import CommentField, DynamicModelChoiceField, SlugField
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, SlugField
|
||||
from wireless.models import *
|
||||
|
||||
__all__ = (
|
||||
|
Loading…
Reference in New Issue
Block a user