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:
Jeremy Stretch 2023-04-14 10:33:53 -04:00 committed by GitHub
parent 59a6b3e71b
commit d470848b29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 585 additions and 406 deletions

View File

@ -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

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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__ = (

View File

@ -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__ = (

View File

@ -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',

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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__ = (

View File

@ -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

View File

@ -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__ = (

View File

@ -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 = """

View File

@ -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',

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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__ = (

View File

@ -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__ = (

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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',

View File

@ -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__ = (

View File

@ -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

View File

@ -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__ = (

View File

@ -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,
}

View File

@ -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__ = (

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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

View File

@ -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=''):
"""

View File

@ -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
#

View File

@ -3,6 +3,7 @@ from rest_framework.exceptions import APIException
__all__ = (
'AbortRequest',
'AbortScript',
'AbortTransaction',
'PermissionsViolation',
'RQWorkerNotRunningException',

View File

@ -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 ''"

View File

@ -1,5 +1,9 @@
import hashlib
__all__ = (
'sha256_hash',
)
def sha256_hash(filepath):
"""

View File

@ -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):
"""

View File

@ -1,5 +1,4 @@
from .constants import *
from .fields import *
from .forms import *
from .mixins import *
from .utils import *
from .widgets import *

View File

@ -1,3 +1,4 @@
from .array import *
from .content_types import *
from .csv import *
from .dynamic import *

View 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)

View File

@ -1,6 +1,5 @@
from django import forms
from utilities.forms import widgets
from utilities.utils import content_type_name
__all__ = (

View File

@ -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__ = (

View File

@ -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):

View 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

View File

@ -0,0 +1,4 @@
from .apiselect import *
from .datetime import *
from .misc import *
from .select import *

View File

@ -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)

View 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'

View 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'

View 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'

View File

@ -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)

View File

@ -1,5 +1,10 @@
from urllib.parse import urlparse
__all__ = (
'is_embedded',
'is_htmx',
)
def is_htmx(request):
"""

View File

@ -1,6 +1,10 @@
import markdown
from markdown.inlinepatterns import SimpleTagPattern
__all__ = (
'StrikethroughExtension',
)
STRIKE_RE = r'(~{2})(.+?)(~{2})'

View File

@ -3,6 +3,10 @@ from timezone_field import TimeZoneField
from netbox.config import ConfigItem
__all__ = (
'custom_deconstruct',
)
SKIP_FIELDS = (
TimeZoneField,

View File

@ -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):
"""

View File

@ -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+)/)?' \

View File

@ -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 = (

View File

@ -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):

View File

@ -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):
"""

View File

@ -1,3 +1,8 @@
__all__ = (
'linkify_phone',
)
def linkify_phone(value):
"""
Render a telephone number as a hyperlink.

View File

@ -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()

View File

@ -1,5 +1,12 @@
from django import template
__all__ = (
'badge',
'checkmark',
'customfield_value',
'tag',
)
register = template.Library()

View File

@ -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()

View File

@ -1,5 +1,14 @@
from django import template
__all__ = (
'getfield',
'render_custom_fields',
'render_errors',
'render_field',
'render_form',
'widget_type',
)
register = template.Library()

View File

@ -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):
"""

View File

@ -4,6 +4,10 @@ from django.template import Context
from netbox.navigation.menu import MENUS
__all__ = (
'nav',
)
register = template.Library()

View File

@ -1,5 +1,13 @@
from django import template
__all__ = (
'can_add',
'can_change',
'can_delete',
'can_sync',
'can_view',
)
register = template.Library()

View File

@ -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()

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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__ = (

View File

@ -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 *

View File

@ -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 *

View File

@ -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 *

View File

@ -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 *

View File

@ -1,4 +1,4 @@
from utilities.forms import ExpandableNameField
from utilities.forms.fields import ExpandableNameField
from .model_forms import VMInterfaceForm
__all__ = (

View File

@ -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 *

View File

@ -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 *

View File

@ -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 *

View File

@ -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__ = (