From 867a01fae5e431f9a0f893b8cfbb3fc70f089bb0 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Thu, 23 Oct 2025 20:19:16 -0400 Subject: [PATCH 01/15] Clone all GraphQL objects to V1 versions --- netbox/circuits/graphql/filter_mixins_v1.py | 19 + netbox/circuits/graphql/filters_v1.py | 228 ++++ netbox/circuits/graphql/schema_v1.py | 42 + netbox/circuits/graphql/types_v1.py | 189 +++ netbox/core/graphql/filter_mixins_v1.py | 36 + netbox/core/graphql/filters_v1.py | 89 ++ netbox/core/graphql/mixins_v1.py | 35 + netbox/core/graphql/schema_v1.py | 15 + netbox/core/graphql/types_v1.py | 56 + netbox/dcim/graphql/filter_mixins_v1.py | 155 +++ netbox/dcim/graphql/filters_v1.py | 1020 +++++++++++++++++ netbox/dcim/graphql/gfk_mixins_v1.py | 137 +++ netbox/dcim/graphql/mixins_v1.py | 43 + netbox/dcim/graphql/schema_v1.py | 138 +++ netbox/dcim/graphql/types_v1.py | 906 +++++++++++++++ netbox/extras/graphql/filter_mixins_v1.py | 52 + netbox/extras/graphql/filters_v1.py | 357 ++++++ netbox/extras/graphql/mixins_v1.py | 62 + netbox/extras/graphql/schema_v1.py | 60 + netbox/extras/graphql/types_v1.py | 239 ++++ netbox/ipam/graphql/filter_mixins_v1.py | 25 + netbox/ipam/graphql/filters_v1.py | 392 +++++++ netbox/ipam/graphql/mixins_v1.py | 18 + netbox/ipam/graphql/schema_v1.py | 63 + netbox/ipam/graphql/types_v1.py | 360 ++++++ netbox/netbox/graphql/filter_mixins_v1.py | 104 ++ netbox/netbox/graphql/schema.py | 30 +- netbox/netbox/graphql/types_v1.py | 100 ++ netbox/tenancy/graphql/filter_mixins_v1.py | 38 + netbox/tenancy/graphql/filters_v1.py | 210 ++++ netbox/tenancy/graphql/mixins_v1.py | 12 + netbox/tenancy/graphql/schema_v1.py | 27 + netbox/tenancy/graphql/types_v1.py | 147 +++ netbox/users/graphql/filters_v1.py | 34 + netbox/users/graphql/schema_v1.py | 15 + netbox/users/graphql/types_v1.py | 34 + .../graphql/filter_mixins_v1.py | 28 + netbox/virtualization/graphql/filters_v1.py | 170 +++ netbox/virtualization/graphql/schema_v1.py | 27 + netbox/virtualization/graphql/types_v1.py | 146 +++ netbox/vpn/graphql/filters_v1.py | 192 ++++ netbox/vpn/graphql/schema_v1.py | 39 + netbox/vpn/graphql/types_v1.py | 157 +++ netbox/wireless/graphql/filter_mixins_v1.py | 26 + netbox/wireless/graphql/filters_v1.py | 72 ++ netbox/wireless/graphql/schema_v1.py | 18 + netbox/wireless/graphql/types_v1.py | 71 ++ 47 files changed, 6423 insertions(+), 10 deletions(-) create mode 100644 netbox/circuits/graphql/filter_mixins_v1.py create mode 100644 netbox/circuits/graphql/filters_v1.py create mode 100644 netbox/circuits/graphql/schema_v1.py create mode 100644 netbox/circuits/graphql/types_v1.py create mode 100644 netbox/core/graphql/filter_mixins_v1.py create mode 100644 netbox/core/graphql/filters_v1.py create mode 100644 netbox/core/graphql/mixins_v1.py create mode 100644 netbox/core/graphql/schema_v1.py create mode 100644 netbox/core/graphql/types_v1.py create mode 100644 netbox/dcim/graphql/filter_mixins_v1.py create mode 100644 netbox/dcim/graphql/filters_v1.py create mode 100644 netbox/dcim/graphql/gfk_mixins_v1.py create mode 100644 netbox/dcim/graphql/mixins_v1.py create mode 100644 netbox/dcim/graphql/schema_v1.py create mode 100644 netbox/dcim/graphql/types_v1.py create mode 100644 netbox/extras/graphql/filter_mixins_v1.py create mode 100644 netbox/extras/graphql/filters_v1.py create mode 100644 netbox/extras/graphql/mixins_v1.py create mode 100644 netbox/extras/graphql/schema_v1.py create mode 100644 netbox/extras/graphql/types_v1.py create mode 100644 netbox/ipam/graphql/filter_mixins_v1.py create mode 100644 netbox/ipam/graphql/filters_v1.py create mode 100644 netbox/ipam/graphql/mixins_v1.py create mode 100644 netbox/ipam/graphql/schema_v1.py create mode 100644 netbox/ipam/graphql/types_v1.py create mode 100644 netbox/netbox/graphql/filter_mixins_v1.py create mode 100644 netbox/netbox/graphql/types_v1.py create mode 100644 netbox/tenancy/graphql/filter_mixins_v1.py create mode 100644 netbox/tenancy/graphql/filters_v1.py create mode 100644 netbox/tenancy/graphql/mixins_v1.py create mode 100644 netbox/tenancy/graphql/schema_v1.py create mode 100644 netbox/tenancy/graphql/types_v1.py create mode 100644 netbox/users/graphql/filters_v1.py create mode 100644 netbox/users/graphql/schema_v1.py create mode 100644 netbox/users/graphql/types_v1.py create mode 100644 netbox/virtualization/graphql/filter_mixins_v1.py create mode 100644 netbox/virtualization/graphql/filters_v1.py create mode 100644 netbox/virtualization/graphql/schema_v1.py create mode 100644 netbox/virtualization/graphql/types_v1.py create mode 100644 netbox/vpn/graphql/filters_v1.py create mode 100644 netbox/vpn/graphql/schema_v1.py create mode 100644 netbox/vpn/graphql/types_v1.py create mode 100644 netbox/wireless/graphql/filter_mixins_v1.py create mode 100644 netbox/wireless/graphql/filters_v1.py create mode 100644 netbox/wireless/graphql/schema_v1.py create mode 100644 netbox/wireless/graphql/types_v1.py diff --git a/netbox/circuits/graphql/filter_mixins_v1.py b/netbox/circuits/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..15ebce2d4 --- /dev/null +++ b/netbox/circuits/graphql/filter_mixins_v1.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django + +from netbox.graphql.filter_mixins_v1 import OrganizationalModelFilterMixinV1 + +if TYPE_CHECKING: + from netbox.graphql.enums import ColorEnum + +__all__ = ( + 'BaseCircuitTypeFilterMixinV1', +) + + +@dataclass +class BaseCircuitTypeFilterMixinV1(OrganizationalModelFilterMixinV1): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() diff --git a/netbox/circuits/graphql/filters_v1.py b/netbox/circuits/graphql/filters_v1.py new file mode 100644 index 000000000..caf4b7c62 --- /dev/null +++ b/netbox/circuits/graphql/filters_v1.py @@ -0,0 +1,228 @@ +from datetime import date +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup, DateFilterLookup + +from circuits import models +from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 +from dcim.graphql.filter_mixins_v1 import CabledObjectModelFilterMixinV1 +from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + DistanceFilterMixinV1, + ImageAttachmentFilterMixinV1, + OrganizationalModelFilterMixinV1, + PrimaryModelFilterMixinV1, +) +from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 +from .filter_mixins_v1 import BaseCircuitTypeFilterMixinV1 + +if TYPE_CHECKING: + from core.graphql.filters_v1 import ContentTypeFilterV1 + from dcim.graphql.filters_v1 import ( + InterfaceFilterV1, LocationFilterV1, RegionFilterV1, SiteFilterV1, SiteGroupFilterV1 + ) + from ipam.graphql.filters_v1 import ASNFilterV1 + from netbox.graphql.filter_lookups import IntegerLookup + from .enums import * + +__all__ = ( + 'CircuitFilterV1', + 'CircuitGroupAssignmentFilterV1', + 'CircuitGroupFilterV1', + 'CircuitTerminationFilterV1', + 'CircuitTypeFilterV1', + 'ProviderFilterV1', + 'ProviderAccountFilterV1', + 'ProviderNetworkFilterV1', + 'VirtualCircuitFilterV1', + 'VirtualCircuitTerminationFilterV1', + 'VirtualCircuitTypeFilterV1', +) + + +@strawberry_django.filter_type(models.CircuitTermination, lookups=True) +class CircuitTerminationFilterV1( + BaseObjectTypeFilterMixinV1, + CustomFieldsFilterMixinV1, + TagsFilterMixinV1, + ChangeLogFilterMixinV1, + CabledObjectModelFilterMixinV1, +): + circuit: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + term_side: Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + termination_id: ID | None = strawberry_django.filter_field() + port_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + upstream_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + xconnect_id: FilterLookup[str] | None = strawberry_django.filter_field() + pp_info: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + # Cached relations + _provider_network: Annotated['ProviderNetworkFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field(name='provider_network') + ) + _location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field(name='location') + ) + _region: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field(name='region') + ) + _site_group: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field(name='site_group') + ) + _site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field(name='site') + ) + + +@strawberry_django.filter_type(models.Circuit, lookups=True) +class CircuitFilterV1( + ContactFilterMixinV1, + ImageAttachmentFilterMixinV1, + DistanceFilterMixinV1, + TenancyFilterMixinV1, + PrimaryModelFilterMixinV1 +): + cid: FilterLookup[str] | None = strawberry_django.filter_field() + provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + provider_account: Annotated['ProviderAccountFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_account_id: ID | None = strawberry_django.filter_field() + type: Annotated['CircuitTypeFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() + termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field() + commit_rate: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + terminations: Annotated['CircuitTerminationFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.CircuitType, lookups=True) +class CircuitTypeFilterV1(BaseCircuitTypeFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.CircuitGroup, lookups=True) +class CircuitGroupFilterV1(TenancyFilterMixinV1, OrganizationalModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.CircuitGroupAssignment, lookups=True) +class CircuitGroupAssignmentFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + member_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + member_id: ID | None = strawberry_django.filter_field() + group: Annotated['CircuitGroupFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + priority: Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Provider, lookups=True) +class ProviderFilterV1(ContactFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + circuits: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ProviderAccount, lookups=True) +class ProviderAccountFilterV1(ContactFilterMixinV1, PrimaryModelFilterMixinV1): + provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + account: FilterLookup[str] | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ProviderNetwork, lookups=True) +class ProviderNetworkFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + service_id: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.VirtualCircuitType, lookups=True) +class VirtualCircuitTypeFilterV1(BaseCircuitTypeFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.VirtualCircuit, lookups=True) +class VirtualCircuitFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + cid: FilterLookup[str] | None = strawberry_django.filter_field() + provider_network: Annotated['ProviderNetworkFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_network_id: ID | None = strawberry_django.filter_field() + provider_account: Annotated['ProviderAccountFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + provider_account_id: ID | None = strawberry_django.filter_field() + type: Annotated['VirtualCircuitTypeFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group_assignments: Annotated[ + 'CircuitGroupAssignmentFilterV1', strawberry.lazy('circuits.graphql.filters_v1') + ] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VirtualCircuitTermination, lookups=True) +class VirtualCircuitTerminationFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + virtual_circuit: Annotated['VirtualCircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + virtual_circuit_id: ID | None = strawberry_django.filter_field() + role: Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + interface: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interface_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/circuits/graphql/schema_v1.py b/netbox/circuits/graphql/schema_v1.py new file mode 100644 index 000000000..1134978f9 --- /dev/null +++ b/netbox/circuits/graphql/schema_v1.py @@ -0,0 +1,42 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class CircuitsQueryV1: + circuit: CircuitTypeV1 = strawberry_django.field() + circuit_list: List[CircuitTypeV1] = strawberry_django.field() + + circuit_termination: CircuitTerminationTypeV1 = strawberry_django.field() + circuit_termination_list: List[CircuitTerminationTypeV1] = strawberry_django.field() + + circuit_type: CircuitTypeTypeV1 = strawberry_django.field() + circuit_type_list: List[CircuitTypeTypeV1] = strawberry_django.field() + + circuit_group: CircuitGroupTypeV1 = strawberry_django.field() + circuit_group_list: List[CircuitGroupTypeV1] = strawberry_django.field() + + circuit_group_assignment: CircuitGroupAssignmentTypeV1 = strawberry_django.field() + circuit_group_assignment_list: List[CircuitGroupAssignmentTypeV1] = strawberry_django.field() + + provider: ProviderTypeV1 = strawberry_django.field() + provider_list: List[ProviderTypeV1] = strawberry_django.field() + + provider_account: ProviderAccountTypeV1 = strawberry_django.field() + provider_account_list: List[ProviderAccountTypeV1] = strawberry_django.field() + + provider_network: ProviderNetworkTypeV1 = strawberry_django.field() + provider_network_list: List[ProviderNetworkTypeV1] = strawberry_django.field() + + virtual_circuit: VirtualCircuitTypeV1 = strawberry_django.field() + virtual_circuit_list: List[VirtualCircuitTypeV1] = strawberry_django.field() + + virtual_circuit_termination: VirtualCircuitTerminationTypeV1 = strawberry_django.field() + virtual_circuit_termination_list: List[VirtualCircuitTerminationTypeV1] = strawberry_django.field() + + virtual_circuit_type: VirtualCircuitTypeTypeV1 = strawberry_django.field() + virtual_circuit_type_list: List[VirtualCircuitTypeTypeV1] = strawberry_django.field() diff --git a/netbox/circuits/graphql/types_v1.py b/netbox/circuits/graphql/types_v1.py new file mode 100644 index 000000000..3e7669df6 --- /dev/null +++ b/netbox/circuits/graphql/types_v1.py @@ -0,0 +1,189 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from circuits import models +from dcim.graphql.mixins_v1 import CabledObjectMixinV1 +from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 +from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1 +from tenancy.graphql.types_v1 import TenantTypeV1 +from .filters_v1 import * + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import InterfaceTypeV1, LocationTypeV1, RegionTypeV1, SiteGroupTypeV1, SiteTypeV1 + from ipam.graphql.types_v1 import ASNTypeV1 + +__all__ = ( + 'CircuitGroupAssignmentTypeV1', + 'CircuitGroupTypeV1', + 'CircuitTerminationTypeV1', + 'CircuitTypeV1', + 'CircuitTypeTypeV1', + 'ProviderTypeV1', + 'ProviderAccountTypeV1', + 'ProviderNetworkTypeV1', + 'VirtualCircuitTerminationTypeV1', + 'VirtualCircuitTypeV1', + 'VirtualCircuitTypeTypeV1', +) + + +@strawberry_django.type( + models.Provider, + fields='__all__', + filters=ProviderFilterV1, + pagination=True +) +class ProviderTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + + networks: List[Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + accounts: List[Annotated["ProviderAccountTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + + +@strawberry_django.type( + models.ProviderAccount, + fields='__all__', + filters=ProviderAccountFilterV1, + pagination=True +) +class ProviderAccountTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): + provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + + circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + + +@strawberry_django.type( + models.ProviderNetwork, + fields='__all__', + filters=ProviderNetworkFilterV1, + pagination=True +) +class ProviderNetworkTypeV1(NetBoxObjectTypeV1): + provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + + circuit_terminations: List[Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + + +@strawberry_django.type( + models.CircuitTermination, + exclude=['termination_type', 'termination_id', '_location', '_region', '_site', '_site_group', '_provider_network'], + filters=CircuitTerminationFilterV1, + pagination=True +) +class CircuitTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, CabledObjectMixinV1, ObjectTypeV1): + circuit: Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + + @strawberry_django.field + def termination(self) -> Annotated[Union[ + Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + ], strawberry.union("CircuitTerminationTerminationTypeV1")] | None: + return self.termination + + +@strawberry_django.type( + models.CircuitType, + fields='__all__', + filters=CircuitTypeFilterV1, + pagination=True +) +class CircuitTypeTypeV1(OrganizationalObjectTypeV1): + color: str + + circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + + +@strawberry_django.type( + models.Circuit, + fields='__all__', + filters=CircuitFilterV1, + pagination=True +) +class CircuitTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + provider: ProviderTypeV1 + provider_account: ProviderAccountTypeV1 | None + termination_a: CircuitTerminationTypeV1 | None + termination_z: CircuitTerminationTypeV1 | None + type: CircuitTypeTypeV1 + tenant: TenantTypeV1 | None + + terminations: List[CircuitTerminationTypeV1] + + +@strawberry_django.type( + models.CircuitGroup, + fields='__all__', + filters=CircuitGroupFilterV1, + pagination=True +) +class CircuitGroupTypeV1(OrganizationalObjectTypeV1): + tenant: TenantTypeV1 | None + + +@strawberry_django.type( + models.CircuitGroupAssignment, + exclude=['member_type', 'member_id'], + filters=CircuitGroupAssignmentFilterV1, + pagination=True +) +class CircuitGroupAssignmentTypeV1(TagsMixinV1, BaseObjectTypeV1): + group: Annotated["CircuitGroupTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + + @strawberry_django.field + def member(self) -> Annotated[Union[ + Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + Annotated["VirtualCircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + ], strawberry.union("CircuitGroupAssignmentMemberTypeV1")] | None: + return self.member + + +@strawberry_django.type( + models.VirtualCircuitType, + fields='__all__', + filters=VirtualCircuitTypeFilterV1, + pagination=True +) +class VirtualCircuitTypeTypeV1(OrganizationalObjectTypeV1): + color: str + + virtual_circuits: List[Annotated["VirtualCircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + + +@strawberry_django.type( + models.VirtualCircuitTermination, + fields='__all__', + filters=VirtualCircuitTerminationFilterV1, + pagination=True +) +class VirtualCircuitTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): + virtual_circuit: Annotated[ + "VirtualCircuitTypeV1", + strawberry.lazy('circuits.graphql.types_v1') + ] = strawberry_django.field(select_related=["virtual_circuit"]) + interface: Annotated[ + "InterfaceTypeV1", + strawberry.lazy('dcim.graphql.types_v1') + ] = strawberry_django.field(select_related=["interface"]) + + +@strawberry_django.type( + models.VirtualCircuit, + fields='__all__', + filters=VirtualCircuitFilterV1, + pagination=True +) +class VirtualCircuitTypeV1(NetBoxObjectTypeV1): + provider_network: ProviderNetworkTypeV1 = strawberry_django.field(select_related=["provider_network"]) + provider_account: ProviderAccountTypeV1 | None + type: Annotated["VirtualCircuitTypeTypeV1", strawberry.lazy('circuits.graphql.types_v1')] = strawberry_django.field( + select_related=["type"] + ) + tenant: TenantTypeV1 | None + + terminations: List[VirtualCircuitTerminationTypeV1] diff --git a/netbox/core/graphql/filter_mixins_v1.py b/netbox/core/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..58c39705f --- /dev/null +++ b/netbox/core/graphql/filter_mixins_v1.py @@ -0,0 +1,36 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry import ID +from strawberry_django import DatetimeFilterLookup + +if TYPE_CHECKING: + from .filters_v1 import * + +__all__ = ( + 'BaseFilterMixinV1', + 'BaseObjectTypeFilterMixinV1', + 'ChangeLogFilterMixinV1', +) + + +# @strawberry.input +class BaseFilterMixinV1: ... + + +@dataclass +class BaseObjectTypeFilterMixinV1(BaseFilterMixinV1): + id: ID | None = strawberry.UNSET + + +@dataclass +class ChangeLogFilterMixinV1(BaseFilterMixinV1): + id: ID | None = strawberry.UNSET + changelog: Annotated['ObjectChangeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filters_v1.py b/netbox/core/graphql/filters_v1.py new file mode 100644 index 000000000..c75de0c75 --- /dev/null +++ b/netbox/core/graphql/filters_v1.py @@ -0,0 +1,89 @@ +from datetime import datetime +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from django.contrib.contenttypes.models import ContentType as DjangoContentType +from strawberry.scalars import ID +from strawberry_django import DatetimeFilterLookup, FilterLookup + +from core import models +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 +from netbox.graphql.filter_mixins import PrimaryModelFilterMixin + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import IntegerLookup, JSONFilter + from users.graphql.filters import UserFilter + +__all__ = ( + 'DataFileFilterV1', + 'DataSourceFilterV1', + 'ObjectChangeFilterV1', + 'ContentTypeFilterV1', +) + + +@strawberry_django.filter_type(models.DataFile, lookups=True) +class DataFileFilterV1(BaseFilterMixinV1): + id: ID | None = strawberry_django.filter_field() + created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + source: Annotated['DataSourceFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + source_id: ID | None = strawberry_django.filter_field() + path: FilterLookup[str] | None = strawberry_django.filter_field() + size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + hash: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.DataSource, lookups=True) +class DataSourceFilterV1(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + type: FilterLookup[str] | None = strawberry_django.filter_field() + source_url: FilterLookup[str] | None = strawberry_django.filter_field() + status: FilterLookup[str] | None = strawberry_django.filter_field() + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + ignore_rules: FilterLookup[str] | None = strawberry_django.filter_field() + parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + datafiles: Annotated['DataFileFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ObjectChange, lookups=True) +class ObjectChangeFilterV1(BaseFilterMixinV1): + id: ID | None = strawberry_django.filter_field() + time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() + user_name: FilterLookup[str] | None = strawberry_django.filter_field() + request_id: FilterLookup[str] | None = strawberry_django.filter_field() + action: FilterLookup[str] | None = strawberry_django.filter_field() + changed_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + changed_object_type_id: ID | None = strawberry_django.filter_field() + changed_object_id: ID | None = strawberry_django.filter_field() + related_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + related_object_id: ID | None = strawberry_django.filter_field() + object_repr: FilterLookup[str] | None = strawberry_django.filter_field() + prechange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + postchange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(DjangoContentType, lookups=True) +class ContentTypeFilterV1(BaseFilterMixinV1): + id: ID | None = strawberry_django.filter_field() + app_label: FilterLookup[str] | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/mixins_v1.py b/netbox/core/graphql/mixins_v1.py new file mode 100644 index 000000000..722068cdd --- /dev/null +++ b/netbox/core/graphql/mixins_v1.py @@ -0,0 +1,35 @@ +from typing import Annotated, List, TYPE_CHECKING + +import strawberry +import strawberry_django +from django.contrib.contenttypes.models import ContentType +from strawberry.types import Info + +from core.models import ObjectChange + +if TYPE_CHECKING: + from core.graphql.types_v1 import DataFileTypeV1, DataSourceTypeV1, ObjectChangeTypeV1 + +__all__ = ( + 'ChangelogMixinV1', + 'SyncedDataMixinV1', +) + + +@strawberry.type +class ChangelogMixinV1: + + @strawberry_django.field + def changelog(self, info: Info) -> List[Annotated['ObjectChangeTypeV1', strawberry.lazy('.types_v1')]]: # noqa: F821 + content_type = ContentType.objects.get_for_model(self) + object_changes = ObjectChange.objects.filter( + changed_object_type=content_type, + changed_object_id=self.pk + ) + return object_changes.restrict(info.context.request.user, 'view') + + +@strawberry.type +class SyncedDataMixinV1: + data_source: Annotated['DataSourceTypeV1', strawberry.lazy('core.graphql.types_v1')] | None + data_file: Annotated['DataFileTypeV1', strawberry.lazy('core.graphql.types_v1')] | None diff --git a/netbox/core/graphql/schema_v1.py b/netbox/core/graphql/schema_v1.py new file mode 100644 index 000000000..b94d14a70 --- /dev/null +++ b/netbox/core/graphql/schema_v1.py @@ -0,0 +1,15 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class CoreQueryV1: + data_file: DataFileTypeV1 = strawberry_django.field() + data_file_list: List[DataFileTypeV1] = strawberry_django.field() + + data_source: DataSourceTypeV1 = strawberry_django.field() + data_source_list: List[DataSourceTypeV1] = strawberry_django.field() diff --git a/netbox/core/graphql/types_v1.py b/netbox/core/graphql/types_v1.py new file mode 100644 index 000000000..67effeae4 --- /dev/null +++ b/netbox/core/graphql/types_v1.py @@ -0,0 +1,56 @@ +from typing import Annotated, List + +import strawberry +import strawberry_django +from django.contrib.contenttypes.models import ContentType as DjangoContentType + +from core import models +from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1 +from .filters_v1 import * + +__all__ = ( + 'ContentTypeV1', + 'DataFileTypeV1', + 'DataSourceTypeV1', + 'ObjectChangeTypeV1', +) + + +@strawberry_django.type( + models.DataFile, + exclude=['data',], + filters=DataFileFilterV1, + pagination=True +) +class DataFileTypeV1(BaseObjectTypeV1): + source: Annotated["DataSourceTypeV1", strawberry.lazy('core.graphql.types_v1')] + + +@strawberry_django.type( + models.DataSource, + fields='__all__', + filters=DataSourceFilterV1, + pagination=True +) +class DataSourceTypeV1(NetBoxObjectTypeV1): + + datafiles: List[Annotated["DataFileTypeV1", strawberry.lazy('core.graphql.types_v1')]] + + +@strawberry_django.type( + models.ObjectChange, + fields='__all__', + filters=ObjectChangeFilterV1, + pagination=True +) +class ObjectChangeTypeV1(BaseObjectTypeV1): + pass + + +@strawberry_django.type( + DjangoContentType, + fields='__all__', + pagination=True +) +class ContentTypeV1: + pass diff --git a/netbox/dcim/graphql/filter_mixins_v1.py b/netbox/dcim/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..55f329497 --- /dev/null +++ b/netbox/dcim/graphql/filter_mixins_v1.py @@ -0,0 +1,155 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry import ID +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1, ChangeLogFilterMixinV1 +from core.graphql.filters_v1 import ContentTypeFilterV1 +from netbox.graphql.filter_mixins_v1 import NetBoxModelFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1 +from .enums import * + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import IntegerLookup + from extras.graphql.filters_v1 import ConfigTemplateFilterV1 + from ipam.graphql.filters_v1 import VLANFilterV1, VLANTranslationPolicyFilterV1 + from .filters_v1 import * + +__all__ = ( + 'CabledObjectModelFilterMixinV1', + 'ComponentModelFilterMixinV1', + 'ComponentTemplateFilterMixinV1', + 'InterfaceBaseFilterMixinV1', + 'ModularComponentModelFilterMixinV1', + 'ModularComponentTemplateFilterMixinV1', + 'RackBaseFilterMixinV1', + 'RenderConfigFilterMixinV1', + 'ScopedFilterMixinV1', +) + + +@dataclass +class ScopedFilterMixinV1(BaseFilterMixinV1): + scope_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + scope_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class ComponentModelFilterMixinV1(NetBoxModelFilterMixinV1): + device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ModularComponentModelFilterMixinV1(ComponentModelFilterMixinV1): + module: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + module_id: ID | None = strawberry_django.filter_field() + inventory_items: Annotated['InventoryItemFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class CabledObjectModelFilterMixinV1(BaseFilterMixinV1): + cable: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cable_id: ID | None = strawberry_django.filter_field() + cable_end: CableEndEnum | None = strawberry_django.filter_field() + mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@dataclass +class ComponentTemplateFilterMixinV1(ChangeLogFilterMixinV1): + device_type: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_type_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ModularComponentTemplateFilterMixinV1(ComponentTemplateFilterMixinV1): + module_type: Annotated['ModuleTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class RenderConfigFilterMixinV1(BaseFilterMixinV1): + config_template: Annotated['ConfigTemplateFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + config_template_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class InterfaceBaseFilterMixinV1(BaseFilterMixinV1): + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + mtu: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + mode: InterfaceModeEnum | None = strawberry_django.filter_field() + bridge: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + bridge_id: ID | None = strawberry_django.filter_field() + untagged_vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tagged_vlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + qinq_svlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_translation_policy: Annotated[ + 'VLANTranslationPolicyFilterV1', strawberry.lazy('ipam.graphql.filters_v1') + ] | None = strawberry_django.filter_field() + primary_mac_address: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_mac_address_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class RackBaseFilterMixinV1(WeightFilterMixinV1, PrimaryModelFilterMixinV1): + width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + u_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + starting_unit: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + desc_units: FilterLookup[bool] | None = strawberry_django.filter_field() + outer_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + outer_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + outer_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + mounting_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + max_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/dcim/graphql/filters_v1.py b/netbox/dcim/graphql/filters_v1.py new file mode 100644 index 000000000..be87c3618 --- /dev/null +++ b/netbox/dcim/graphql/filters_v1.py @@ -0,0 +1,1020 @@ +from typing import Annotated, TYPE_CHECKING + +from django.db.models import Q +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import ChangeLogFilterMixinV1 +from dcim import models +from dcim.constants import * +from dcim.graphql.enums import InterfaceKindEnum +from extras.graphql.filter_mixins_v1 import ConfigContextFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + PrimaryModelFilterMixinV1, + OrganizationalModelFilterMixinV1, + NestedGroupModelFilterMixinV1, + ImageAttachmentFilterMixinV1, + WeightFilterMixinV1, +) +from tenancy.graphql.filter_mixins_v1 import TenancyFilterMixinV1, ContactFilterMixinV1 +from .filter_mixins_v1 import ( + CabledObjectModelFilterMixinV1, + ComponentModelFilterMixinV1, + ComponentTemplateFilterMixinV1, + InterfaceBaseFilterMixinV1, + ModularComponentModelFilterMixinV1, + ModularComponentTemplateFilterMixinV1, + RackBaseFilterMixinV1, + RenderConfigFilterMixinV1, +) + +if TYPE_CHECKING: + from core.graphql.filters_v1 import ContentTypeFilterV1 + from extras.graphql.filters_v1 import ConfigTemplateFilterV1, ImageAttachmentFilterV1 + from ipam.graphql.filters_v1 import ( + ASNFilterV1, FHRPGroupAssignmentFilterV1, IPAddressFilterV1, PrefixFilterV1, VLANGroupFilterV1, VRFFilterV1, + ) + from netbox.graphql.enums import ColorEnum + from netbox.graphql.filter_lookups import FloatLookup, IntegerArrayLookup, IntegerLookup, TreeNodeFilter + from users.graphql.filters_v1 import UserFilterV1 + from virtualization.graphql.filters_v1 import ClusterFilterV1 + from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelTerminationFilterV1 + from wireless.graphql.enums import WirelessChannelEnum, WirelessRoleEnum + from wireless.graphql.filters_v1 import WirelessLANFilterV1, WirelessLinkFilterV1 + from .enums import * + +__all__ = ( + 'CableFilterV1', + 'CableTerminationFilterV1', + 'ConsolePortFilterV1', + 'ConsolePortTemplateFilterV1', + 'ConsoleServerPortFilterV1', + 'ConsoleServerPortTemplateFilterV1', + 'DeviceFilterV1', + 'DeviceBayFilterV1', + 'DeviceBayTemplateFilterV1', + 'DeviceRoleFilterV1', + 'DeviceTypeFilterV1', + 'FrontPortFilterV1', + 'FrontPortTemplateFilterV1', + 'InterfaceFilterV1', + 'InterfaceTemplateFilterV1', + 'InventoryItemFilterV1', + 'InventoryItemRoleFilterV1', + 'InventoryItemTemplateFilterV1', + 'LocationFilterV1', + 'MACAddressFilterV1', + 'ManufacturerFilterV1', + 'ModuleFilterV1', + 'ModuleBayFilterV1', + 'ModuleBayTemplateFilterV1', + 'ModuleTypeFilterV1', + 'ModuleTypeProfileFilterV1', + 'PlatformFilterV1', + 'PowerFeedFilterV1', + 'PowerOutletFilterV1', + 'PowerOutletTemplateFilterV1', + 'PowerPanelFilterV1', + 'PowerPortFilterV1', + 'PowerPortTemplateFilterV1', + 'RackFilterV1', + 'RackReservationFilterV1', + 'RackRoleFilterV1', + 'RackTypeFilterV1', + 'RearPortFilterV1', + 'RearPortTemplateFilterV1', + 'RegionFilterV1', + 'SiteFilterV1', + 'SiteGroupFilterV1', + 'VirtualChassisFilterV1', + 'VirtualDeviceContextFilterV1', +) + + +@strawberry_django.filter_type(models.Cable, lookups=True) +class CableFilterV1(PrimaryModelFilterMixinV1, TenancyFilterMixinV1): + type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + terminations: Annotated['CableTerminationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.CableTermination, lookups=True) +class CableTerminationFilterV1(ChangeLogFilterMixinV1): + cable: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cable_id: ID | None = strawberry_django.filter_field() + cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['CableTerminationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + termination_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ConsolePort, lookups=True) +class ConsolePortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True) +class ConsolePortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ConsoleServerPort, lookups=True) +class ConsoleServerPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True) +class ConsoleServerPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Device, lookups=True) +class DeviceFilterV1( + ContactFilterMixinV1, + TenancyFilterMixinV1, + ImageAttachmentFilterMixinV1, + RenderConfigFilterMixinV1, + ConfigContextFilterMixinV1, + PrimaryModelFilterMixinV1, +): + device_type: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_type_id: ID | None = strawberry_django.filter_field() + role: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rack_id: ID | None = strawberry_django.filter_field() + position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + oob_ip: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + oob_ip_id: ID | None = strawberry_django.filter_field() + cluster: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cluster_id: ID | None = strawberry_django.filter_field() + virtual_chassis: Annotated['VirtualChassisFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + virtual_chassis_id: ID | None = strawberry_django.filter_field() + vc_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + vc_priority: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + longitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + console_ports: Annotated['ConsolePortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + console_server_ports: Annotated['ConsoleServerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_outlets: Annotated['PowerOutletFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_ports: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interfaces: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + front_ports: Annotated['FrontPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rear_ports: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_bays: Annotated['DeviceBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + module_bays: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + modules: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + console_port_count: FilterLookup[int] | None = strawberry_django.filter_field() + console_server_port_count: FilterLookup[int] | None = strawberry_django.filter_field() + power_port_count: FilterLookup[int] | None = strawberry_django.filter_field() + power_outlet_count: FilterLookup[int] | None = strawberry_django.filter_field() + interface_count: FilterLookup[int] | None = strawberry_django.filter_field() + front_port_count: FilterLookup[int] | None = strawberry_django.filter_field() + rear_port_count: FilterLookup[int] | None = strawberry_django.filter_field() + device_bay_count: FilterLookup[int] | None = strawberry_django.filter_field() + module_bay_count: FilterLookup[int] | None = strawberry_django.filter_field() + inventory_item_count: FilterLookup[int] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.DeviceBay, lookups=True) +class DeviceBayFilterV1(ComponentModelFilterMixinV1): + installed_device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + installed_device_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.DeviceBayTemplate, lookups=True) +class DeviceBayTemplateFilterV1(ComponentTemplateFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.InventoryItemTemplate, lookups=True) +class InventoryItemTemplateFilterV1(ComponentTemplateFilterMixinV1): + parent: Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + component_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + component_id: ID | None = strawberry_django.filter_field() + role: Annotated['InventoryItemRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + part_id: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.DeviceRole, lookups=True) +class DeviceRoleFilterV1(OrganizationalModelFilterMixinV1, RenderConfigFilterMixinV1): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + vm_role: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.DeviceType, lookups=True) +class DeviceTypeFilterV1(ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1): + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + default_platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + default_platform_id: ID | None = strawberry_django.filter_field() + part_number: FilterLookup[str] | None = strawberry_django.filter_field() + u_height: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field() + is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field() + subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + front_image: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rear_image: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + console_port_templates: ( + Annotated['ConsolePortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + console_server_port_templates: ( + Annotated['ConsoleServerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + power_port_templates: ( + Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + power_outlet_templates: ( + Annotated['PowerOutletTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + interface_templates: ( + Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + front_port_templates: ( + Annotated['FrontPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + rear_port_templates: ( + Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + device_bay_templates: ( + Annotated['DeviceBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + module_bay_templates: ( + Annotated['ModuleBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + inventory_item_templates: ( + Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + console_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + console_server_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + power_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + power_outlet_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + interface_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + front_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + rear_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + device_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + module_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + inventory_item_template_count: FilterLookup[int] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.FrontPort, lookups=True) +class FrontPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + rear_port: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rear_port_id: ID | None = strawberry_django.filter_field() + rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.FrontPortTemplate, lookups=True) +class FrontPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + rear_port: Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rear_port_id: ID | None = strawberry_django.filter_field() + rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.MACAddress, lookups=True) +class MACAddressFilterV1(PrimaryModelFilterMixinV1): + mac_address: FilterLookup[str] | None = strawberry_django.filter_field() + assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Interface, lookups=True) +class InterfaceFilterV1(ModularComponentModelFilterMixinV1, InterfaceBaseFilterMixinV1, CabledObjectModelFilterMixinV1): + vcdcs: Annotated['VirtualDeviceContextFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + lag: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + lag_id: ID | None = strawberry_django.filter_field() + type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() + speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + wwn: FilterLookup[str] | None = strawberry_django.filter_field() + parent: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + rf_channel_width: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + wireless_link: Annotated['WirelessLinkFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + wireless_link_id: ID | None = strawberry_django.filter_field() + wireless_lans: Annotated['WirelessLANFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + mac_addresses: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + fhrp_group_assignments: Annotated[ + 'FHRPGroupAssignmentFilterV1', strawberry.lazy('ipam.graphql.filters_v1') + ] | None = ( + strawberry_django.filter_field() + ) + tunnel_terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + @strawberry_django.filter_field + def connected(self, queryset, value: bool, prefix: str): + if value is True: + return queryset, Q(**{f"{prefix}_path__is_active": True}) + else: + return queryset, Q(**{f"{prefix}_path__isnull": True}) | Q(**{f"{prefix}_path__is_active": False}) + + @strawberry_django.filter_field + def kind( + self, + queryset, + value: Annotated['InterfaceKindEnum', strawberry.lazy('dcim.graphql.enums')], + prefix: str + ): + if value == InterfaceKindEnum.KIND_PHYSICAL: + return queryset, ~Q(**{f"{prefix}type__in": NONCONNECTABLE_IFACE_TYPES}) + elif value == InterfaceKindEnum.KIND_VIRTUAL: + return queryset, Q(**{f"{prefix}type__in": VIRTUAL_IFACE_TYPES}) + elif value == InterfaceKindEnum.KIND_WIRELESS: + return queryset, Q(**{f"{prefix}type__in": WIRELESS_IFACE_TYPES}) + + +@strawberry_django.filter_type(models.InterfaceTemplate, lookups=True) +class InterfaceTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() + bridge: Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + bridge_id: ID | None = strawberry_django.filter_field() + poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.InventoryItem, lookups=True) +class InventoryItemFilterV1(ComponentModelFilterMixinV1): + parent: Annotated['InventoryItemFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + component_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + component_id: ID | None = strawberry_django.filter_field() + status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['InventoryItemRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + part_id: FilterLookup[str] | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + discovered: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.InventoryItemRole, lookups=True) +class InventoryItemRoleFilterV1(OrganizationalModelFilterMixinV1): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Location, lookups=True) +class LocationFilterV1( + ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, NestedGroupModelFilterMixinV1 +): + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + facility: FilterLookup[str] | None = strawberry_django.filter_field() + prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Manufacturer, lookups=True) +class ManufacturerFilterV1(ContactFilterMixinV1, OrganizationalModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.Module, lookups=True) +class ModuleFilterV1(PrimaryModelFilterMixinV1, ConfigContextFilterMixinV1): + device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_id: ID | None = strawberry_django.filter_field() + module_bay: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + module_bay_id: ID | None = strawberry_django.filter_field() + module_type: Annotated['ModuleTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + module_type_id: ID | None = strawberry_django.filter_field() + status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + console_ports: Annotated['ConsolePortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + console_server_ports: Annotated['ConsoleServerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_outlets: Annotated['PowerOutletFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_ports: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interfaces: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + front_ports: Annotated['FrontPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rear_ports: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_bays: Annotated['DeviceBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + module_bays: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + modules: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ModuleBay, lookups=True) +class ModuleBayFilterV1(ModularComponentModelFilterMixinV1): + parent: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + position: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True) +class ModuleBayTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + position: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True) +class ModuleTypeProfileFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ModuleType, lookups=True) +class ModuleTypeFilterV1(ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1): + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + profile: Annotated['ModuleTypeProfileFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + profile_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + part_number: FilterLookup[str] | None = strawberry_django.filter_field() + airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + console_port_templates: ( + Annotated['ConsolePortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + console_server_port_templates: ( + Annotated['ConsoleServerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + power_port_templates: ( + Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + power_outlet_templates: ( + Annotated['PowerOutletTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + interface_templates: ( + Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + front_port_templates: ( + Annotated['FrontPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + rear_port_templates: ( + Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + device_bay_templates: ( + Annotated['DeviceBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + module_bay_templates: ( + Annotated['ModuleBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + inventory_item_templates: ( + Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Platform, lookups=True) +class PlatformFilterV1(OrganizationalModelFilterMixinV1): + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + config_template: Annotated['ConfigTemplateFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + config_template_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.PowerFeed, lookups=True) +class PowerFeedFilterV1(CabledObjectModelFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + power_panel: Annotated['PowerPanelFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_panel_id: ID | None = strawberry_django.filter_field() + rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rack_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + amperage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + max_utilization: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + available_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.PowerOutlet, lookups=True) +class PowerOutletFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + power_port: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_port_id: ID | None = strawberry_django.filter_field() + feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True) +class PowerOutletTemplateFilterV1(ModularComponentModelFilterMixinV1): + type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + power_port: Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_port_id: ID | None = strawberry_django.filter_field() + feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.PowerPanel, lookups=True) +class PowerPanelFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1): + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.PowerPort, lookups=True) +class PowerPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + allocated_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.PowerPortTemplate, lookups=True) +class PowerPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + allocated_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.RackType, lookups=True) +class RackTypeFilterV1(RackBaseFilterMixinV1): + form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Rack, lookups=True) +class RackFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, RackBaseFilterMixinV1): + form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rack_type: Annotated['RackTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rack_type_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + facility_id: FilterLookup[str] | None = strawberry_django.filter_field() + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + role: Annotated['RackRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.RackReservation, lookups=True) +class RackReservationFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rack_id: ID | None = strawberry_django.filter_field() + units: Annotated['IntegerArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + user_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.RackRole, lookups=True) +class RackRoleFilterV1(OrganizationalModelFilterMixinV1): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.RearPort, lookups=True) +class RearPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.RearPortTemplate, lookups=True) +class RearPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Region, lookups=True) +class RegionFilterV1(ContactFilterMixinV1, NestedGroupModelFilterMixinV1): + prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Site, lookups=True) +class SiteFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + region: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + facility: FilterLookup[str] | None = strawberry_django.filter_field() + asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + time_zone: FilterLookup[str] | None = strawberry_django.filter_field() + physical_address: FilterLookup[str] | None = strawberry_django.filter_field() + shipping_address: FilterLookup[str] | None = strawberry_django.filter_field() + latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + longitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.SiteGroup, lookups=True) +class SiteGroupFilterV1(ContactFilterMixinV1, NestedGroupModelFilterMixinV1): + prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VirtualChassis, lookups=True) +class VirtualChassisFilterV1(PrimaryModelFilterMixinV1): + master: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + master_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + domain: FilterLookup[str] | None = strawberry_django.filter_field() + members: ( + Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + member_count: FilterLookup[int] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.VirtualDeviceContext, lookups=True) +class VirtualDeviceContextFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() + interfaces: ( + Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() diff --git a/netbox/dcim/graphql/gfk_mixins_v1.py b/netbox/dcim/graphql/gfk_mixins_v1.py new file mode 100644 index 000000000..378b3a916 --- /dev/null +++ b/netbox/dcim/graphql/gfk_mixins_v1.py @@ -0,0 +1,137 @@ +from strawberry.types import Info + +from circuits.graphql.types_v1 import CircuitTerminationTypeV1, ProviderNetworkTypeV1 +from circuits.models import CircuitTermination, ProviderNetwork +from dcim.graphql.types_v1 import ( + ConsolePortTemplateTypeV1, + ConsolePortTypeV1, + ConsoleServerPortTemplateTypeV1, + ConsoleServerPortTypeV1, + FrontPortTemplateTypeV1, + FrontPortTypeV1, + InterfaceTemplateTypeV1, + InterfaceTypeV1, + PowerFeedTypeV1, + PowerOutletTemplateTypeV1, + PowerOutletTypeV1, + PowerPortTemplateTypeV1, + PowerPortTypeV1, + RearPortTemplateTypeV1, + RearPortTypeV1, +) +from dcim.models import ( + ConsolePort, + ConsolePortTemplate, + ConsoleServerPort, + ConsoleServerPortTemplate, + FrontPort, + FrontPortTemplate, + Interface, + InterfaceTemplate, + PowerFeed, + PowerOutlet, + PowerOutletTemplate, + PowerPort, + PowerPortTemplate, + RearPort, + RearPortTemplate, +) + + +class InventoryItemTemplateComponentTypeV1: + class Meta: + types = ( + ConsolePortTemplateTypeV1, + ConsoleServerPortTemplateTypeV1, + FrontPortTemplateTypeV1, + InterfaceTemplateTypeV1, + PowerOutletTemplateTypeV1, + PowerPortTemplateTypeV1, + RearPortTemplateTypeV1, + ) + + @classmethod + def resolve_type(cls, instance, info: Info): + if type(instance) is ConsolePortTemplate: + return ConsolePortTemplateTypeV1 + if type(instance) is ConsoleServerPortTemplate: + return ConsoleServerPortTemplateTypeV1 + if type(instance) is FrontPortTemplate: + return FrontPortTemplateTypeV1 + if type(instance) is InterfaceTemplate: + return InterfaceTemplateTypeV1 + if type(instance) is PowerOutletTemplate: + return PowerOutletTemplateTypeV1 + if type(instance) is PowerPortTemplate: + return PowerPortTemplateTypeV1 + if type(instance) is RearPortTemplate: + return RearPortTemplateTypeV1 + + +class InventoryItemComponentTypeV1: + class Meta: + types = ( + ConsolePortTypeV1, + ConsoleServerPortTypeV1, + FrontPortTypeV1, + InterfaceTypeV1, + PowerOutletTypeV1, + PowerPortTypeV1, + RearPortTypeV1, + ) + + @classmethod + def resolve_type(cls, instance, info: Info): + if type(instance) is ConsolePort: + return ConsolePortTypeV1 + if type(instance) is ConsoleServerPort: + return ConsoleServerPortTypeV1 + if type(instance) is FrontPort: + return FrontPortTypeV1 + if type(instance) is Interface: + return InterfaceTypeV1 + if type(instance) is PowerOutlet: + return PowerOutletTypeV1 + if type(instance) is PowerPort: + return PowerPortTypeV1 + if type(instance) is RearPort: + return RearPortTypeV1 + + +class ConnectedEndpointTypeV1: + class Meta: + types = ( + CircuitTerminationTypeV1, + ConsolePortTypeV1, + ConsoleServerPortTypeV1, + FrontPortTypeV1, + InterfaceTypeV1, + PowerFeedTypeV1, + PowerOutletTypeV1, + PowerPortTypeV1, + ProviderNetworkTypeV1, + RearPortTypeV1, + ) + + @classmethod + def resolve_type(cls, instance, info: Info): + if type(instance) is CircuitTermination: + return CircuitTerminationTypeV1 + if type(instance) is ConsolePort: + return ConsolePortTypeV1 + if type(instance) is ConsoleServerPort: + return ConsoleServerPortTypeV1 + if type(instance) is FrontPort: + return FrontPortTypeV1 + if type(instance) is Interface: + return InterfaceTypeV1 + if type(instance) is PowerFeed: + return PowerFeedTypeV1 + if type(instance) is PowerOutlet: + return PowerOutletTypeV1 + if type(instance) is PowerPort: + return PowerPortTypeV1 + if type(instance) is ProviderNetwork: + return ProviderNetworkTypeV1 + if type(instance) is RearPort: + return RearPortTypeV1 diff --git a/netbox/dcim/graphql/mixins_v1.py b/netbox/dcim/graphql/mixins_v1.py new file mode 100644 index 000000000..694283e3c --- /dev/null +++ b/netbox/dcim/graphql/mixins_v1.py @@ -0,0 +1,43 @@ +from typing import Annotated, List, Union + +import strawberry + +__all__ = ( + 'CabledObjectMixinV1', + 'PathEndpointMixinV1', +) + + +@strawberry.type +class CabledObjectMixinV1: + cable: Annotated["CableTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None # noqa: F821 + + link_peers: List[Annotated[Union[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + ], strawberry.union("LinkPeerType")]] + + +@strawberry.type +class PathEndpointMixinV1: + + connected_endpoints: List[Annotated[Union[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 + Annotated["VirtualCircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 + ], strawberry.union("ConnectedEndpointTypeV1")]] diff --git a/netbox/dcim/graphql/schema_v1.py b/netbox/dcim/graphql/schema_v1.py new file mode 100644 index 000000000..29d2f09ba --- /dev/null +++ b/netbox/dcim/graphql/schema_v1.py @@ -0,0 +1,138 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class DCIMQueryV1: + cable: CableTypeV1 = strawberry_django.field() + cable_list: List[CableTypeV1] = strawberry_django.field() + + console_port: ConsolePortTypeV1 = strawberry_django.field() + console_port_list: List[ConsolePortTypeV1] = strawberry_django.field() + + console_port_template: ConsolePortTemplateTypeV1 = strawberry_django.field() + console_port_template_list: List[ConsolePortTemplateTypeV1] = strawberry_django.field() + + console_server_port: ConsoleServerPortTypeV1 = strawberry_django.field() + console_server_port_list: List[ConsoleServerPortTypeV1] = strawberry_django.field() + + console_server_port_template: ConsoleServerPortTemplateTypeV1 = strawberry_django.field() + console_server_port_template_list: List[ConsoleServerPortTemplateTypeV1] = strawberry_django.field() + + device: DeviceTypeV1 = strawberry_django.field() + device_list: List[DeviceTypeV1] = strawberry_django.field() + + device_bay: DeviceBayTypeV1 = strawberry_django.field() + device_bay_list: List[DeviceBayTypeV1] = strawberry_django.field() + + device_bay_template: DeviceBayTemplateTypeV1 = strawberry_django.field() + device_bay_template_list: List[DeviceBayTemplateTypeV1] = strawberry_django.field() + + device_role: DeviceRoleTypeV1 = strawberry_django.field() + device_role_list: List[DeviceRoleTypeV1] = strawberry_django.field() + + device_type: DeviceTypeTypeV1 = strawberry_django.field() + device_type_list: List[DeviceTypeTypeV1] = strawberry_django.field() + + front_port: FrontPortTypeV1 = strawberry_django.field() + front_port_list: List[FrontPortTypeV1] = strawberry_django.field() + + front_port_template: FrontPortTemplateTypeV1 = strawberry_django.field() + front_port_template_list: List[FrontPortTemplateTypeV1] = strawberry_django.field() + + mac_address: MACAddressTypeV1 = strawberry_django.field() + mac_address_list: List[MACAddressTypeV1] = strawberry_django.field() + + interface: InterfaceTypeV1 = strawberry_django.field() + interface_list: List[InterfaceTypeV1] = strawberry_django.field() + + interface_template: InterfaceTemplateTypeV1 = strawberry_django.field() + interface_template_list: List[InterfaceTemplateTypeV1] = strawberry_django.field() + + inventory_item: InventoryItemTypeV1 = strawberry_django.field() + inventory_item_list: List[InventoryItemTypeV1] = strawberry_django.field() + + inventory_item_role: InventoryItemRoleTypeV1 = strawberry_django.field() + inventory_item_role_list: List[InventoryItemRoleTypeV1] = strawberry_django.field() + + inventory_item_template: InventoryItemTemplateTypeV1 = strawberry_django.field() + inventory_item_template_list: List[InventoryItemTemplateTypeV1] = strawberry_django.field() + + location: LocationTypeV1 = strawberry_django.field() + location_list: List[LocationTypeV1] = strawberry_django.field() + + manufacturer: ManufacturerTypeV1 = strawberry_django.field() + manufacturer_list: List[ManufacturerTypeV1] = strawberry_django.field() + + module: ModuleTypeV1 = strawberry_django.field() + module_list: List[ModuleTypeV1] = strawberry_django.field() + + module_bay: ModuleBayTypeV1 = strawberry_django.field() + module_bay_list: List[ModuleBayTypeV1] = strawberry_django.field() + + module_bay_template: ModuleBayTemplateTypeV1 = strawberry_django.field() + module_bay_template_list: List[ModuleBayTemplateTypeV1] = strawberry_django.field() + + module_type_profile: ModuleTypeProfileTypeV1 = strawberry_django.field() + module_type_profile_list: List[ModuleTypeProfileTypeV1] = strawberry_django.field() + + module_type: ModuleTypeTypeV1 = strawberry_django.field() + module_type_list: List[ModuleTypeTypeV1] = strawberry_django.field() + + platform: PlatformTypeV1 = strawberry_django.field() + platform_list: List[PlatformTypeV1] = strawberry_django.field() + + power_feed: PowerFeedTypeV1 = strawberry_django.field() + power_feed_list: List[PowerFeedTypeV1] = strawberry_django.field() + + power_outlet: PowerOutletTypeV1 = strawberry_django.field() + power_outlet_list: List[PowerOutletTypeV1] = strawberry_django.field() + + power_outlet_template: PowerOutletTemplateTypeV1 = strawberry_django.field() + power_outlet_template_list: List[PowerOutletTemplateTypeV1] = strawberry_django.field() + + power_panel: PowerPanelTypeV1 = strawberry_django.field() + power_panel_list: List[PowerPanelTypeV1] = strawberry_django.field() + + power_port: PowerPortTypeV1 = strawberry_django.field() + power_port_list: List[PowerPortTypeV1] = strawberry_django.field() + + power_port_template: PowerPortTemplateTypeV1 = strawberry_django.field() + power_port_template_list: List[PowerPortTemplateTypeV1] = strawberry_django.field() + + rack_type: RackTypeTypeV1 = strawberry_django.field() + rack_type_list: List[RackTypeTypeV1] = strawberry_django.field() + + rack: RackTypeV1 = strawberry_django.field() + rack_list: List[RackTypeV1] = strawberry_django.field() + + rack_reservation: RackReservationTypeV1 = strawberry_django.field() + rack_reservation_list: List[RackReservationTypeV1] = strawberry_django.field() + + rack_role: RackRoleTypeV1 = strawberry_django.field() + rack_role_list: List[RackRoleTypeV1] = strawberry_django.field() + + rear_port: RearPortTypeV1 = strawberry_django.field() + rear_port_list: List[RearPortTypeV1] = strawberry_django.field() + + rear_port_template: RearPortTemplateTypeV1 = strawberry_django.field() + rear_port_template_list: List[RearPortTemplateTypeV1] = strawberry_django.field() + + region: RegionTypeV1 = strawberry_django.field() + region_list: List[RegionTypeV1] = strawberry_django.field() + + site: SiteTypeV1 = strawberry_django.field() + site_list: List[SiteTypeV1] = strawberry_django.field() + + site_group: SiteGroupTypeV1 = strawberry_django.field() + site_group_list: List[SiteGroupTypeV1] = strawberry_django.field() + + virtual_chassis: VirtualChassisTypeV1 = strawberry_django.field() + virtual_chassis_list: List[VirtualChassisTypeV1] = strawberry_django.field() + + virtual_device_context: VirtualDeviceContextTypeV1 = strawberry_django.field() + virtual_device_context_list: List[VirtualDeviceContextTypeV1] = strawberry_django.field() diff --git a/netbox/dcim/graphql/types_v1.py b/netbox/dcim/graphql/types_v1.py new file mode 100644 index 000000000..c7f069ecb --- /dev/null +++ b/netbox/dcim/graphql/types_v1.py @@ -0,0 +1,906 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from core.graphql.mixins_v1 import ChangelogMixinV1 +from dcim import models +from extras.graphql.mixins_v1 import ( + ConfigContextMixinV1, + ContactsMixinV1, + CustomFieldsMixinV1, + ImageAttachmentsMixinV1, + TagsMixinV1, +) +from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 +from netbox.graphql.scalars import BigInt +from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1 +from .filters_v1 import * +from .mixins_v1 import CabledObjectMixinV1, PathEndpointMixinV1 + +if TYPE_CHECKING: + from circuits.graphql.types_v1 import CircuitTerminationTypeV1 + from extras.graphql.types_v1 import ConfigTemplateTypeV1 + from ipam.graphql.types_v1 import ( + ASNTypeV1, + IPAddressTypeV1, + PrefixTypeV1, + ServiceTypeV1, + VLANTranslationPolicyTypeV1, + VLANTypeV1, + VRFTypeV1, + ) + from tenancy.graphql.types_v1 import TenantTypeV1 + from users.graphql.types_v1 import UserTypeV1 + from virtualization.graphql.types_v1 import ClusterTypeV1, VMInterfaceTypeV1, VirtualMachineTypeV1 + from vpn.graphql.types_v1 import L2VPNTerminationTypeV1 + from wireless.graphql.types_v1 import WirelessLANTypeV1, WirelessLinkTypeV1 + +__all__ = ( + 'CableTypeV1', + 'ComponentTypeV1', + 'ConsolePortTypeV1', + 'ConsolePortTemplateTypeV1', + 'ConsoleServerPortTypeV1', + 'ConsoleServerPortTemplateTypeV1', + 'DeviceTypeV1', + 'DeviceBayTypeV1', + 'DeviceBayTemplateTypeV1', + 'DeviceRoleTypeV1', + 'DeviceTypeTypeV1', + 'FrontPortTypeV1', + 'FrontPortTemplateTypeV1', + 'InterfaceTypeV1', + 'InterfaceTemplateTypeV1', + 'InventoryItemTypeV1', + 'InventoryItemRoleTypeV1', + 'InventoryItemTemplateTypeV1', + 'LocationTypeV1', + 'MACAddressTypeV1', + 'ManufacturerTypeV1', + 'ModularComponentTypeV1', + 'ModuleTypeV1', + 'ModuleBayTypeV1', + 'ModuleBayTemplateTypeV1', + 'ModuleTypeProfileTypeV1', + 'ModuleTypeTypeV1', + 'PlatformTypeV1', + 'PowerFeedTypeV1', + 'PowerOutletTypeV1', + 'PowerOutletTemplateTypeV1', + 'PowerPanelTypeV1', + 'PowerPortTypeV1', + 'PowerPortTemplateTypeV1', + 'RackTypeV1', + 'RackReservationTypeV1', + 'RackRoleTypeV1', + 'RackTypeTypeV1', + 'RearPortTypeV1', + 'RearPortTemplateTypeV1', + 'RegionTypeV1', + 'SiteTypeV1', + 'SiteGroupTypeV1', + 'VirtualChassisTypeV1', + 'VirtualDeviceContextTypeV1', +) + + +# +# Base types +# + + +@strawberry.type +class ComponentTypeV1( + ChangelogMixinV1, + CustomFieldsMixinV1, + TagsMixinV1, + BaseObjectTypeV1 +): + """ + Base type for device/VM components + """ + device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + +@strawberry.type +class ModularComponentTypeV1(ComponentTypeV1): + module: Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + +@strawberry.type +class ComponentTemplateTypeV1( + ChangelogMixinV1, + BaseObjectTypeV1 +): + """ + Base type for device/VM components + """ + device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + +@strawberry.type +class ModularComponentTemplateTypeV1(ComponentTemplateTypeV1): + """ + Base type for ComponentTemplateModel which supports optional assignment to a ModuleType. + """ + device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + module_type: Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + +# +# Model types +# + + +@strawberry_django.type( + models.CableTermination, + exclude=['termination_type', 'termination_id', '_device', '_rack', '_location', '_site'], + filters=CableTerminationFilterV1, + pagination=True +) +class CableTerminationTypeV1(NetBoxObjectTypeV1): + cable: Annotated["CableTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + termination: Annotated[Union[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("CableTerminationTerminationTypeV1")] | None + + +@strawberry_django.type( + models.Cable, + fields='__all__', + filters=CableFilterV1, + pagination=True +) +class CableTypeV1(NetBoxObjectTypeV1): + color: str + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + terminations: List[CableTerminationTypeV1] + + a_terminations: List[Annotated[Union[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("CableTerminationTerminationTypeV1")]] + + b_terminations: List[Annotated[Union[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("CableTerminationTerminationTypeV1")]] + + +@strawberry_django.type( + models.ConsolePort, + exclude=['_path'], + filters=ConsolePortFilterV1, + pagination=True +) +class ConsolePortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + pass + + +@strawberry_django.type( + models.ConsolePortTemplate, + fields='__all__', + filters=ConsolePortTemplateFilterV1, + pagination=True +) +class ConsolePortTemplateTypeV1(ModularComponentTemplateTypeV1): + pass + + +@strawberry_django.type( + models.ConsoleServerPort, + exclude=['_path'], + filters=ConsoleServerPortFilterV1, + pagination=True +) +class ConsoleServerPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + pass + + +@strawberry_django.type( + models.ConsoleServerPortTemplate, + fields='__all__', + filters=ConsoleServerPortTemplateFilterV1, + pagination=True +) +class ConsoleServerPortTemplateTypeV1(ModularComponentTemplateTypeV1): + pass + + +@strawberry_django.type( + models.Device, + fields='__all__', + filters=DeviceFilterV1, + pagination=True +) +class DeviceTypeV1(ConfigContextMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): + console_port_count: BigInt + console_server_port_count: BigInt + power_port_count: BigInt + power_outlet_count: BigInt + interface_count: BigInt + front_port_count: BigInt + rear_port_count: BigInt + device_bay_count: BigInt + module_bay_count: BigInt + inventory_item_count: BigInt + config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None + device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + role: Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + oob_ip: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + cluster: Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + virtual_chassis: Annotated["VirtualChassisTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + modules: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + rearports: List[Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleports: List[Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerports: List[Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleserverports: List[Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + devicebays: List[Annotated["DeviceBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + modulebays: List[Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + inventoryitems: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + vdcs: List[Annotated["VirtualDeviceContextTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def vc_master_for(self) -> Annotated["VirtualChassisTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.vc_master_for if hasattr(self, 'vc_master_for') else None + + @strawberry_django.field + def parent_bay(self) -> Annotated["DeviceBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent_bay if hasattr(self, 'parent_bay') else None + + +@strawberry_django.type( + models.DeviceBay, + fields='__all__', + filters=DeviceBayFilterV1, + pagination=True +) +class DeviceBayTypeV1(ComponentTypeV1): + installed_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + +@strawberry_django.type( + models.DeviceBayTemplate, + fields='__all__', + filters=DeviceBayTemplateFilterV1, + pagination=True +) +class DeviceBayTemplateTypeV1(ComponentTemplateTypeV1): + pass + + +@strawberry_django.type( + models.InventoryItemTemplate, + exclude=['component_type', 'component_id', 'parent'], + filters=InventoryItemTemplateFilterV1, + pagination=True +) +class InventoryItemTemplateTypeV1(ComponentTemplateTypeV1): + role: Annotated["InventoryItemRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + @strawberry_django.field + def parent(self) -> Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent + + child_items: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + component: Annotated[Union[ + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("InventoryItemTemplateComponentTypeV1")] | None + + +@strawberry_django.type( + models.DeviceRole, + fields='__all__', + filters=DeviceRoleFilterV1, + pagination=True +) +class DeviceRoleTypeV1(OrganizationalObjectTypeV1): + parent: Annotated['DeviceRoleTypeV1', strawberry.lazy('dcim.graphql.types_v1')] | None + children: List[Annotated['DeviceRoleTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + color: str + config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None + + virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.DeviceType, + fields='__all__', + filters=DeviceTypeFilterV1, + pagination=True +) +class DeviceTypeTypeV1(NetBoxObjectTypeV1): + console_port_template_count: BigInt + console_server_port_template_count: BigInt + power_port_template_count: BigInt + power_outlet_template_count: BigInt + interface_template_count: BigInt + front_port_template_count: BigInt + rear_port_template_count: BigInt + device_bay_template_count: BigInt + module_bay_template_count: BigInt + inventory_item_template_count: BigInt + front_image: strawberry_django.fields.types.DjangoImageType | None + rear_image: strawberry_django.fields.types.DjangoImageType | None + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + default_platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + frontporttemplates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + modulebaytemplates: List[Annotated["ModuleBayTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + instances: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + poweroutlettemplates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerporttemplates: List[Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + inventoryitemtemplates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + rearporttemplates: List[Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleserverporttemplates: List[ + Annotated["ConsoleServerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + ] + interfacetemplates: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + devicebaytemplates: List[Annotated["DeviceBayTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleporttemplates: List[Annotated["ConsolePortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.FrontPort, + fields='__all__', + filters=FrontPortFilterV1, + pagination=True +) +class FrontPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1): + color: str + rear_port: Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + +@strawberry_django.type( + models.FrontPortTemplate, + fields='__all__', + filters=FrontPortTemplateFilterV1, + pagination=True +) +class FrontPortTemplateTypeV1(ModularComponentTemplateTypeV1): + color: str + rear_port: Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + +@strawberry_django.type( + models.MACAddress, + exclude=['assigned_object_type', 'assigned_object_id'], + filters=MACAddressFilterV1, + pagination=True +) +class MACAddressTypeV1(NetBoxObjectTypeV1): + mac_address: str + + @strawberry_django.field + def assigned_object(self) -> Annotated[Union[ + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + ], strawberry.union("MACAddressAssignmentTypeV1")] | None: + return self.assigned_object + + +@strawberry_django.type( + models.Interface, + exclude=['_path'], + filters=InterfaceFilterV1, + pagination=True +) +class InterfaceTypeV1(IPAddressesMixinV1, ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + _name: str + wwn: str | None + parent: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + bridge: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + lag: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + wireless_link: Annotated["WirelessLinkTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None + untagged_vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + primary_mac_address: Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + qinq_svlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + vlan_translation_policy: Annotated["VLANTranslationPolicyTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + l2vpn_termination: Annotated["L2VPNTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None + + vdcs: List[Annotated["VirtualDeviceContextTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + tagged_vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + bridge_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + wireless_lans: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] + member_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + child_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + mac_addresses: List[Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.InterfaceTemplate, + fields='__all__', + filters=InterfaceTemplateFilterV1, + pagination=True +) +class InterfaceTemplateTypeV1(ModularComponentTemplateTypeV1): + _name: str + bridge: Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + bridge_interfaces: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.InventoryItem, + exclude=['component_type', 'component_id', 'parent'], + filters=InventoryItemFilterV1, + pagination=True +) +class InventoryItemTypeV1(ComponentTypeV1): + role: Annotated["InventoryItemRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + child_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def parent(self) -> Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent + + component: Annotated[Union[ + Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("InventoryItemComponentTypeV1")] | None + + +@strawberry_django.type( + models.InventoryItemRole, + fields='__all__', + filters=InventoryItemRoleFilterV1, + pagination=True +) +class InventoryItemRoleTypeV1(OrganizationalObjectTypeV1): + color: str + + inventory_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + inventory_item_templates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.Location, + # fields='__all__', + exclude=['parent'], # bug - temp + filters=LocationFilterV1, + pagination=True +) +class LocationTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): + site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + parent: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + powerpanel_set: List[Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + children: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: + return self.cluster_set.all() + + @strawberry_django.field + def circuit_terminations(self) -> List[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + ]: + return self.circuit_terminations.all() + + +@strawberry_django.type( + models.Manufacturer, + fields='__all__', + filters=ManufacturerFilterV1, + pagination=True +) +class ManufacturerTypeV1(OrganizationalObjectTypeV1, ContactsMixinV1): + + platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + device_types: List[Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + inventory_item_templates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + inventory_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + module_types: List[Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.Module, + fields='__all__', + filters=ModuleFilterV1, + pagination=True +) +class ModuleTypeV1(NetBoxObjectTypeV1): + device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + module_bay: Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + module_type: Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerports: List[Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleserverports: List[Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleports: List[Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + rearports: List[Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.ModuleBay, + # fields='__all__', + exclude=['parent'], + filters=ModuleBayFilterV1, + pagination=True +) +class ModuleBayTypeV1(ModularComponentTypeV1): + + installed_module: Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + children: List[Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def parent(self) -> Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent + + +@strawberry_django.type( + models.ModuleBayTemplate, + fields='__all__', + filters=ModuleBayTemplateFilterV1, + pagination=True +) +class ModuleBayTemplateTypeV1(ModularComponentTemplateTypeV1): + pass + + +@strawberry_django.type( + models.ModuleTypeProfile, + fields='__all__', + filters=ModuleTypeProfileFilterV1, + pagination=True +) +class ModuleTypeProfileTypeV1(NetBoxObjectTypeV1): + module_types: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.ModuleType, + fields='__all__', + filters=ModuleTypeFilterV1, + pagination=True +) +class ModuleTypeTypeV1(NetBoxObjectTypeV1): + profile: Annotated["ModuleTypeProfileTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + frontporttemplates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleserverporttemplates: List[ + Annotated["ConsoleServerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + ] + interfacetemplates: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerporttemplates: List[Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + poweroutlettemplates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + rearporttemplates: List[Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + instances: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + consoleporttemplates: List[Annotated["ConsolePortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.Platform, + fields='__all__', + filters=PlatformFilterV1, + pagination=True +) +class PlatformTypeV1(OrganizationalObjectTypeV1): + parent: Annotated['PlatformTypeV1', strawberry.lazy('dcim.graphql.types_v1')] | None + children: List[Annotated['PlatformTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None + + virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.PowerFeed, + exclude=['_path'], + filters=PowerFeedFilterV1, + pagination=True +) +class PowerFeedTypeV1(NetBoxObjectTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + power_panel: Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + +@strawberry_django.type( + models.PowerOutlet, + exclude=['_path'], + filters=PowerOutletFilterV1, + pagination=True +) +class PowerOutletTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + power_port: Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + color: str + + +@strawberry_django.type( + models.PowerOutletTemplate, + fields='__all__', + filters=PowerOutletTemplateFilterV1, + pagination=True +) +class PowerOutletTemplateTypeV1(ModularComponentTemplateTypeV1): + power_port: Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + +@strawberry_django.type( + models.PowerPanel, + fields='__all__', + filters=PowerPanelFilterV1, + pagination=True +) +class PowerPanelTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + powerfeeds: List[Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.PowerPort, + exclude=['_path'], + filters=PowerPortFilterV1, + pagination=True +) +class PowerPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): + + poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.PowerPortTemplate, + fields='__all__', + filters=PowerPortTemplateFilterV1, + pagination=True +) +class PowerPortTemplateTypeV1(ModularComponentTemplateTypeV1): + poweroutlet_templates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.RackType, + fields='__all__', + filters=RackTypeFilterV1, + pagination=True +) +class RackTypeTypeV1(NetBoxObjectTypeV1): + manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + + +@strawberry_django.type( + models.Rack, + fields='__all__', + filters=RackFilterV1, + pagination=True +) +class RackTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): + site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + role: Annotated["RackRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + rack_type: Annotated["RackTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + reservations: List[Annotated["RackReservationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerfeeds: List[Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.RackReservation, + fields='__all__', + filters=RackReservationFilterV1, + pagination=True +) +class RackReservationTypeV1(NetBoxObjectTypeV1): + units: List[int] + rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] + + +@strawberry_django.type( + models.RackRole, + fields='__all__', + filters=RackRoleFilterV1, + pagination=True +) +class RackRoleTypeV1(OrganizationalObjectTypeV1): + color: str + + racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.RearPort, + fields='__all__', + filters=RearPortFilterV1, + pagination=True +) +class RearPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1): + color: str + + frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.RearPortTemplate, + fields='__all__', + filters=RearPortTemplateFilterV1, + pagination=True +) +class RearPortTemplateTypeV1(ModularComponentTemplateTypeV1): + color: str + + frontport_templates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.Region, + exclude=['parent'], + filters=RegionFilterV1, + pagination=True +) +class RegionTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): + + sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + children: List[Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def parent(self) -> Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent + + @strawberry_django.field + def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: + return self.cluster_set.all() + + @strawberry_django.field + def circuit_terminations(self) -> List[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + ]: + return self.circuit_terminations.all() + + +@strawberry_django.type( + models.Site, + fields='__all__', + filters=SiteFilterV1, + pagination=True +) +class SiteTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): + time_zone: str | None + region: Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + group: Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + powerpanel_set: List[Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + locations: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + circuit_terminations: List[Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] + clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + @strawberry_django.field + def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: + return self.cluster_set.all() + + @strawberry_django.field + def circuit_terminations(self) -> List[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + ]: + return self.circuit_terminations.all() + + +@strawberry_django.type( + models.SiteGroup, + exclude=['parent'], # bug - temp + filters=SiteGroupFilterV1, + pagination=True +) +class SiteGroupTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): + + sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + children: List[Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def parent(self) -> Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: + return self.parent + + @strawberry_django.field + def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: + return self.cluster_set.all() + + @strawberry_django.field + def circuit_terminations(self) -> List[ + Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] + ]: + return self.circuit_terminations.all() + + +@strawberry_django.type( + models.VirtualChassis, + fields='__all__', + filters=VirtualChassisFilterV1, + pagination=True +) +class VirtualChassisTypeV1(NetBoxObjectTypeV1): + member_count: BigInt + master: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + + members: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.VirtualDeviceContext, + fields='__all__', + filters=VirtualDeviceContextFilterV1, + pagination=True +) +class VirtualDeviceContextTypeV1(NetBoxObjectTypeV1): + device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] diff --git a/netbox/extras/graphql/filter_mixins_v1.py b/netbox/extras/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..48611cc83 --- /dev/null +++ b/netbox/extras/graphql/filter_mixins_v1.py @@ -0,0 +1,52 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import JSONFilter + from .filters_v1 import * + +__all__ = ( + 'CustomFieldsFilterMixinV1', + 'JournalEntriesFilterMixinV1', + 'TagsFilterMixinV1', + 'ConfigContextFilterMixinV1', + 'TagBaseFilterMixinV1', +) + + +@dataclass +class CustomFieldsFilterMixinV1(BaseFilterMixinV1): + custom_field_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class JournalEntriesFilterMixinV1(BaseFilterMixinV1): + journal_entries: Annotated['JournalEntryFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TagsFilterMixinV1(BaseFilterMixinV1): + tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field() + + +@dataclass +class ConfigContextFilterMixinV1(BaseFilterMixinV1): + local_context_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TagBaseFilterMixinV1(BaseFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/filters_v1.py b/netbox/extras/graphql/filters_v1.py new file mode 100644 index 000000000..b903fee34 --- /dev/null +++ b/netbox/extras/graphql/filters_v1.py @@ -0,0 +1,357 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 +from extras import models +from extras.graphql.filter_mixins_v1 import TagBaseFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import PrimaryModelFilterMixinV1, SyncedDataFilterMixinV1 + +if TYPE_CHECKING: + from core.graphql.filters_v1 import ContentTypeFilterV1 + from dcim.graphql.filters_v1 import ( + DeviceRoleFilterV1, + DeviceTypeFilterV1, + LocationFilterV1, + PlatformFilterV1, + RegionFilterV1, + SiteFilterV1, + SiteGroupFilterV1, + ) + from tenancy.graphql.filters_v1 import TenantFilterV1, TenantGroupFilterV1 + from netbox.graphql.enums import ColorEnum + from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter + from virtualization.graphql.filters_v1 import ClusterFilterV1, ClusterGroupFilterV1, ClusterTypeFilterV1 + from .enums import * + +__all__ = ( + 'ConfigContextFilterV1', + 'ConfigContextProfileFilterV1', + 'ConfigTemplateFilterV1', + 'CustomFieldFilterV1', + 'CustomFieldChoiceSetFilterV1', + 'CustomLinkFilterV1', + 'EventRuleFilterV1', + 'ExportTemplateFilterV1', + 'ImageAttachmentFilterV1', + 'JournalEntryFilterV1', + 'NotificationGroupFilterV1', + 'SavedFilterFilterV1', + 'TableConfigFilterV1', + 'TagFilterV1', + 'WebhookFilterV1', +) + + +@strawberry_django.filter_type(models.ConfigContext, lookups=True) +class ConfigContextFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + description: FilterLookup[str] | None = strawberry_django.filter_field() + is_active: FilterLookup[bool] | None = strawberry_django.filter_field() + regions: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + site_groups: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + sites: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + locations: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_types: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + roles: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + platforms: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cluster_types: Annotated['ClusterTypeFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cluster_groups: Annotated['ClusterGroupFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + clusters: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tenant_groups: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tenant_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenants: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tags: Annotated['TagFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ConfigContextProfile, lookups=True) +class ConfigContextProfileFilterV1(SyncedDataFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] = strawberry_django.filter_field() + description: FilterLookup[str] = strawberry_django.filter_field() + tags: Annotated['TagFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ConfigTemplate, lookups=True) +class ConfigTemplateFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + template_code: FilterLookup[str] | None = strawberry_django.filter_field() + environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + mime_type: FilterLookup[str] | None = strawberry_django.filter_field() + file_name: FilterLookup[str] | None = strawberry_django.filter_field() + file_extension: FilterLookup[str] | None = strawberry_django.filter_field() + as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.CustomField, lookups=True) +class CustomFieldFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + object_types: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + related_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + group_name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + required: FilterLookup[bool] | None = strawberry_django.filter_field() + unique: FilterLookup[bool] | None = strawberry_django.filter_field() + search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + default: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + related_object_filter: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_minimum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_regex: FilterLookup[str] | None = strawberry_django.filter_field() + choice_set: Annotated['CustomFieldChoiceSetFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + choice_set_id: ID | None = strawberry_django.filter_field() + ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True) +class CustomFieldChoiceSetFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + order_alphabetically: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.CustomLink, lookups=True) +class CustomLinkFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + link_text: FilterLookup[str] | None = strawberry_django.filter_field() + link_url: FilterLookup[str] | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + group_name: FilterLookup[str] | None = strawberry_django.filter_field() + button_class: Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + new_window: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ExportTemplate, lookups=True) +class ExportTemplateFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + template_code: FilterLookup[str] | None = strawberry_django.filter_field() + environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + mime_type: FilterLookup[str] | None = strawberry_django.filter_field() + file_name: FilterLookup[str] | None = strawberry_django.filter_field() + file_extension: FilterLookup[str] | None = strawberry_django.filter_field() + as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ImageAttachment, lookups=True) +class ImageAttachmentFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + object_id: ID | None = strawberry_django.filter_field() + image_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + image_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.JournalEntry, lookups=True) +class JournalEntryFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_type_id: ID | None = strawberry_django.filter_field() + assigned_object_id: ID | None = strawberry_django.filter_field() + created_by: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + comments: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.NotificationGroup, lookups=True) +class NotificationGroupFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + users: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.SavedFilter, lookups=True) +class SavedFilterFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + user_id: ID | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + shared: FilterLookup[bool] | None = strawberry_django.filter_field() + parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.TableConfig, lookups=True) +class TableConfigFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + user_id: ID | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + shared: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Tag, lookups=True) +class TagFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1, TagBaseFilterMixinV1): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Webhook, lookups=True) +class WebhookFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + payload_url: FilterLookup[str] | None = strawberry_django.filter_field() + http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + http_content_type: FilterLookup[str] | None = strawberry_django.filter_field() + additional_headers: FilterLookup[str] | None = strawberry_django.filter_field() + body_template: FilterLookup[str] | None = strawberry_django.filter_field() + secret: FilterLookup[str] | None = strawberry_django.filter_field() + ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field() + ca_file_path: FilterLookup[str] | None = strawberry_django.filter_field() + events: Annotated['EventRuleFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.EventRule, lookups=True) +class EventRuleFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + action_object_type: FilterLookup[str] | None = strawberry_django.filter_field() + action_object_type_id: ID | None = strawberry_django.filter_field() + action_object_id: ID | None = strawberry_django.filter_field() + action_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + comments: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/mixins_v1.py b/netbox/extras/graphql/mixins_v1.py new file mode 100644 index 000000000..4f56549d7 --- /dev/null +++ b/netbox/extras/graphql/mixins_v1.py @@ -0,0 +1,62 @@ +from typing import TYPE_CHECKING, Annotated, List + +import strawberry +import strawberry_django +from strawberry.types import Info + +__all__ = ( + 'ConfigContextMixinV1', + 'ContactsMixinV1', + 'CustomFieldsMixinV1', + 'ImageAttachmentsMixinV1', + 'JournalEntriesMixinV1', + 'TagsMixinV1', +) + +if TYPE_CHECKING: + from .types_v1 import ImageAttachmentTypeV1, JournalEntryTypeV1, TagTypeV1 + from tenancy.graphql.types_v1 import ContactAssignmentTypeV1 + + +@strawberry.type +class ConfigContextMixinV1: + + @strawberry_django.field + def config_context(self) -> strawberry.scalars.JSON: + return self.get_config_context() + + +@strawberry.type +class CustomFieldsMixinV1: + + @strawberry_django.field + def custom_fields(self) -> strawberry.scalars.JSON: + return self.custom_field_data + + +@strawberry.type +class ImageAttachmentsMixinV1: + + @strawberry_django.field + def image_attachments(self, info: Info) -> List[Annotated['ImageAttachmentTypeV1', strawberry.lazy('.types_v1')]]: + return self.images.restrict(info.context.request.user, 'view') + + +@strawberry.type +class JournalEntriesMixinV1: + + @strawberry_django.field + def journal_entries(self, info: Info) -> List[Annotated['JournalEntryTypeV1', strawberry.lazy('.types_v1')]]: + return self.journal_entries.all() + + +@strawberry.type +class TagsMixinV1: + + tags: List[Annotated['TagTypeV1', strawberry.lazy('.types_v1')]] + + +@strawberry.type +class ContactsMixinV1: + + contacts: List[Annotated['ContactAssignmentTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] diff --git a/netbox/extras/graphql/schema_v1.py b/netbox/extras/graphql/schema_v1.py new file mode 100644 index 000000000..3a2757105 --- /dev/null +++ b/netbox/extras/graphql/schema_v1.py @@ -0,0 +1,60 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class ExtrasQueryV1: + config_context: ConfigContextTypeV1 = strawberry_django.field() + config_context_list: List[ConfigContextTypeV1] = strawberry_django.field() + + config_context_profile: ConfigContextProfileTypeV1 = strawberry_django.field() + config_context_profile_list: List[ConfigContextProfileTypeV1] = strawberry_django.field() + + config_template: ConfigTemplateTypeV1 = strawberry_django.field() + config_template_list: List[ConfigTemplateTypeV1] = strawberry_django.field() + + custom_field: CustomFieldTypeV1 = strawberry_django.field() + custom_field_list: List[CustomFieldTypeV1] = strawberry_django.field() + + custom_field_choice_set: CustomFieldChoiceSetTypeV1 = strawberry_django.field() + custom_field_choice_set_list: List[CustomFieldChoiceSetTypeV1] = strawberry_django.field() + + custom_link: CustomLinkTypeV1 = strawberry_django.field() + custom_link_list: List[CustomLinkTypeV1] = strawberry_django.field() + + export_template: ExportTemplateTypeV1 = strawberry_django.field() + export_template_list: List[ExportTemplateTypeV1] = strawberry_django.field() + + image_attachment: ImageAttachmentTypeV1 = strawberry_django.field() + image_attachment_list: List[ImageAttachmentTypeV1] = strawberry_django.field() + + saved_filter: SavedFilterTypeV1 = strawberry_django.field() + saved_filter_list: List[SavedFilterTypeV1] = strawberry_django.field() + + table_config: TableConfigTypeV1 = strawberry_django.field() + table_config_list: List[TableConfigTypeV1] = strawberry_django.field() + + journal_entry: JournalEntryTypeV1 = strawberry_django.field() + journal_entry_list: List[JournalEntryTypeV1] = strawberry_django.field() + + notification: NotificationTypeV1 = strawberry_django.field() + notification_list: List[NotificationTypeV1] = strawberry_django.field() + + notification_group: NotificationGroupTypeV1 = strawberry_django.field() + notification_group_list: List[NotificationGroupTypeV1] = strawberry_django.field() + + subscription: SubscriptionTypeV1 = strawberry_django.field() + subscription_list: List[SubscriptionTypeV1] = strawberry_django.field() + + tag: TagTypeV1 = strawberry_django.field() + tag_list: List[TagTypeV1] = strawberry_django.field() + + webhook: WebhookTypeV1 = strawberry_django.field() + webhook_list: List[WebhookTypeV1] = strawberry_django.field() + + event_rule: EventRuleTypeV1 = strawberry_django.field() + event_rule_list: List[EventRuleTypeV1] = strawberry_django.field() diff --git a/netbox/extras/graphql/types_v1.py b/netbox/extras/graphql/types_v1.py new file mode 100644 index 000000000..d51b57e88 --- /dev/null +++ b/netbox/extras/graphql/types_v1.py @@ -0,0 +1,239 @@ +from typing import Annotated, List, TYPE_CHECKING + +import strawberry +import strawberry_django + +from core.graphql.mixins_v1 import SyncedDataMixinV1 +from extras import models +from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1 +from netbox.graphql.types_v1 import ( + BaseObjectTypeV1, ContentTypeTypeV1, NetBoxObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1 +) +from .filters_v1 import * + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import ( + DeviceRoleTypeV1, + DeviceTypeV1, + DeviceTypeTypeV1, + LocationTypeV1, + PlatformTypeV1, + RegionTypeV1, + SiteGroupTypeV1, + SiteTypeV1, + ) + from tenancy.graphql.types_v1 import TenantGroupTypeV1, TenantTypeV1 + from users.graphql.types_v1 import GroupTypeV1, UserTypeV1 + from virtualization.graphql.types_v1 import ( + ClusterGroupTypeV1, ClusterTypeV1, ClusterTypeTypeV1, VirtualMachineTypeV1 + ) + +__all__ = ( + 'ConfigContextProfileTypeV1', + 'ConfigContextTypeV1', + 'ConfigTemplateTypeV1', + 'CustomFieldChoiceSetTypeV1', + 'CustomFieldTypeV1', + 'CustomLinkTypeV1', + 'EventRuleTypeV1', + 'ExportTemplateTypeV1', + 'ImageAttachmentTypeV1', + 'JournalEntryTypeV1', + 'NotificationGroupTypeV1', + 'NotificationTypeV1', + 'SavedFilterTypeV1', + 'SubscriptionTypeV1', + 'TableConfigTypeV1', + 'TagTypeV1', + 'WebhookTypeV1', +) + + +@strawberry_django.type( + models.ConfigContextProfile, + fields='__all__', + filters=ConfigContextProfileFilterV1, + pagination=True +) +class ConfigContextProfileTypeV1(SyncedDataMixinV1, NetBoxObjectTypeV1): + pass + + +@strawberry_django.type( + models.ConfigContext, + fields='__all__', + filters=ConfigContextFilterV1, + pagination=True +) +class ConfigContextTypeV1(SyncedDataMixinV1, ObjectTypeV1): + profile: ConfigContextProfileTypeV1 | None + roles: List[Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + device_types: List[Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + tags: List[Annotated["TagTypeV1", strawberry.lazy('extras.graphql.types_v1')]] + platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + regions: List[Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + cluster_groups: List[Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + tenant_groups: List[Annotated["TenantGroupTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] + cluster_types: List[Annotated["ClusterTypeTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + locations: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + tenants: List[Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] + site_groups: List[Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.ConfigTemplate, + fields='__all__', + filters=ConfigTemplateFilterV1, + pagination=True +) +class ConfigTemplateTypeV1(SyncedDataMixinV1, TagsMixinV1, ObjectTypeV1): + virtualmachines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + device_roles: List[Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.CustomField, + fields='__all__', + filters=CustomFieldFilterV1, + pagination=True +) +class CustomFieldTypeV1(ObjectTypeV1): + related_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None + choice_set: Annotated["CustomFieldChoiceSetTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None + + +@strawberry_django.type( + models.CustomFieldChoiceSet, + exclude=['extra_choices'], + filters=CustomFieldChoiceSetFilterV1, + pagination=True +) +class CustomFieldChoiceSetTypeV1(ObjectTypeV1): + + choices_for: List[Annotated["CustomFieldTypeV1", strawberry.lazy('extras.graphql.types_v1')]] + extra_choices: List[List[str]] | None + + +@strawberry_django.type( + models.CustomLink, + fields='__all__', + filters=CustomLinkFilterV1, + pagination=True +) +class CustomLinkTypeV1(ObjectTypeV1): + pass + + +@strawberry_django.type( + models.ExportTemplate, + fields='__all__', + filters=ExportTemplateFilterV1, + pagination=True +) +class ExportTemplateTypeV1(SyncedDataMixinV1, ObjectTypeV1): + pass + + +@strawberry_django.type( + models.ImageAttachment, + fields='__all__', + filters=ImageAttachmentFilterV1, + pagination=True +) +class ImageAttachmentTypeV1(BaseObjectTypeV1): + object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None + + +@strawberry_django.type( + models.JournalEntry, + fields='__all__', + filters=JournalEntryFilterV1, + pagination=True +) +class JournalEntryTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): + assigned_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None + created_by: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Notification, + # filters=NotificationFilter + pagination=True +) +class NotificationTypeV1(ObjectTypeV1): + user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None + + +@strawberry_django.type( + models.NotificationGroup, + filters=NotificationGroupFilterV1, + pagination=True +) +class NotificationGroupTypeV1(ObjectTypeV1): + users: List[Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')]] + groups: List[Annotated["GroupTypeV1", strawberry.lazy('users.graphql.types_v1')]] + + +@strawberry_django.type( + models.SavedFilter, + exclude=['content_types',], + filters=SavedFilterFilterV1, + pagination=True +) +class SavedFilterTypeV1(ObjectTypeV1): + user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Subscription, + # filters=NotificationFilter + pagination=True +) +class SubscriptionTypeV1(ObjectTypeV1): + user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None + + +@strawberry_django.type( + models.TableConfig, + fields='__all__', + filters=TableConfigFilterV1, + pagination=True +) +class TableConfigTypeV1(ObjectTypeV1): + user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Tag, + exclude=['extras_taggeditem_items', ], + filters=TagFilterV1, + pagination=True +) +class TagTypeV1(ObjectTypeV1): + color: str + + object_types: List[ContentTypeTypeV1] + + +@strawberry_django.type( + models.Webhook, + exclude=['content_types',], + filters=WebhookFilterV1, + pagination=True +) +class WebhookTypeV1(OrganizationalObjectTypeV1): + pass + + +@strawberry_django.type( + models.EventRule, + exclude=['content_types',], + filters=EventRuleFilterV1, + pagination=True +) +class EventRuleTypeV1(OrganizationalObjectTypeV1): + action_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None diff --git a/netbox/ipam/graphql/filter_mixins_v1.py b/netbox/ipam/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..0ba314d5b --- /dev/null +++ b/netbox/ipam/graphql/filter_mixins_v1.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import IntegerLookup + from .enums import * + +__all__ = ( + 'ServiceBaseFilterMixinV1', +) + + +@dataclass +class ServiceBaseFilterMixinV1(BaseFilterMixinV1): + protocol: Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ports: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/ipam/graphql/filters_v1.py b/netbox/ipam/graphql/filters_v1.py new file mode 100644 index 000000000..7e2171e76 --- /dev/null +++ b/netbox/ipam/graphql/filters_v1.py @@ -0,0 +1,392 @@ +from datetime import date +from typing import Annotated, TYPE_CHECKING + +import netaddr +import strawberry +import strawberry_django +from django.db.models import Q +from netaddr.core import AddrFormatError +from strawberry.scalars import ID +from strawberry_django import FilterLookup, DateFilterLookup + +from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 +from dcim.graphql.filter_mixins_v1 import ScopedFilterMixinV1 +from dcim.models import Device +from ipam import models +from ipam.graphql.filter_mixins_v1 import ServiceBaseFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + NetBoxModelFilterMixinV1, OrganizationalModelFilterMixinV1, PrimaryModelFilterMixinV1 +) +from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 +from virtualization.models import VMInterface + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import IntegerLookup, IntegerRangeArrayLookup + from circuits.graphql.filters_v1 import ProviderFilterV1 + from core.graphql.filters_v1 import ContentTypeFilterV1 + from dcim.graphql.filters_v1 import SiteFilterV1 + from vpn.graphql.filters_v1 import L2VPNFilterV1 + from .enums import * + +__all__ = ( + 'ASNFilterV1', + 'ASNRangeFilterV1', + 'AggregateFilterV1', + 'FHRPGroupFilterV1', + 'FHRPGroupAssignmentFilterV1', + 'IPAddressFilterV1', + 'IPRangeFilterV1', + 'PrefixFilterV1', + 'RIRFilterV1', + 'RoleFilterV1', + 'RouteTargetFilterV1', + 'ServiceFilterV1', + 'ServiceTemplateFilterV1', + 'VLANFilterV1', + 'VLANGroupFilterV1', + 'VLANTranslationPolicyFilterV1', + 'VLANTranslationRuleFilterV1', + 'VRFFilterV1', +) + + +@strawberry_django.filter_type(models.ASN, lookups=True) +class ASNFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + asn: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + sites: ( + Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + providers: ( + Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None + ) = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ASNRange, lookups=True) +class ASNRangeFilterV1(TenancyFilterMixinV1, OrganizationalModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + start: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + end: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Aggregate, lookups=True) +class AggregateFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + prefix: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + prefix_id: ID | None = strawberry_django.filter_field() + rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + date_added: DateFilterLookup[date] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.FHRPGroup, lookups=True) +class FHRPGroupFilterV1(PrimaryModelFilterMixinV1): + group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_key: FilterLookup[str] | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.FHRPGroupAssignment, lookups=True) +class FHRPGroupAssignmentFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): + interface_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interface_id: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['FHRPGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + priority: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + @strawberry_django.filter_field() + def device_id(self, queryset, value: list[str], prefix) -> Q: + return self.filter_device('id', value) + + @strawberry_django.filter_field() + def device(self, value: list[str], prefix) -> Q: + return self.filter_device('name', value) + + @strawberry_django.filter_field() + def virtual_machine_id(self, value: list[str], prefix) -> Q: + return Q(interface_id__in=VMInterface.objects.filter(virtual_machine_id__in=value)) + + @strawberry_django.filter_field() + def virtual_machine(self, value: list[str], prefix) -> Q: + return Q(interface_id__in=VMInterface.objects.filter(virtual_machine__name__in=value)) + + def filter_device(self, field, value) -> Q: + """Helper to standardize logic for device and device_id filters""" + devices = Device.objects.filter(**{f'{field}__in': value}) + interface_ids = [] + for device in devices: + interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) + return Q(interface_id__in=interface_ids) + + +@strawberry_django.filter_type(models.IPAddress, lookups=True) +class IPAddressFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + address: FilterLookup[str] | None = strawberry_django.filter_field() + vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: ID | None = strawberry_django.filter_field() + nat_inside: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + nat_inside_id: ID | None = strawberry_django.filter_field() + nat_outside: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + nat_outside_id: ID | None = strawberry_django.filter_field() + dns_name: FilterLookup[str] | None = strawberry_django.filter_field() + + @strawberry_django.filter_field() + def assigned(self, value: bool, prefix) -> Q: + return Q(assigned_object_id__isnull=(not value)) + + @strawberry_django.filter_field() + def parent(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + q |= Q(address__net_host_contained=query) + except (AddrFormatError, ValueError): + return Q() + return q + + @strawberry_django.filter_field() + def family( + self, + value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], + prefix, + ) -> Q: + return Q(**{f"{prefix}address__family": value.value}) + + +@strawberry_django.filter_type(models.IPRange, lookups=True) +class IPRangeFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + start_address: FilterLookup[str] | None = strawberry_django.filter_field() + end_address: FilterLookup[str] | None = strawberry_django.filter_field() + size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() + + @strawberry_django.filter_field() + def parent(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + q |= Q(start_address__net_host_contained=query, end_address__net_host_contained=query) + except (AddrFormatError, ValueError): + return Q() + return q + + @strawberry_django.filter_field() + def contains(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + net = netaddr.IPNetwork(subnet.strip()) + q |= Q( + start_address__host__inet__lte=str(netaddr.IPAddress(net.first)), + end_address__host__inet__gte=str(netaddr.IPAddress(net.last)), + ) + return q + + +@strawberry_django.filter_type(models.Prefix, lookups=True) +class PrefixFilterV1(ContactFilterMixinV1, ScopedFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + prefix: FilterLookup[str] | None = strawberry_django.filter_field() + vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_id: ID | None = strawberry_django.filter_field() + status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + is_pool: FilterLookup[bool] | None = strawberry_django.filter_field() + mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() + + @strawberry_django.filter_field() + def contains(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + q |= Q(prefix__net_contains=query) + return q + + +@strawberry_django.filter_type(models.RIR, lookups=True) +class RIRFilterV1(OrganizationalModelFilterMixinV1): + is_private: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Role, lookups=True) +class RoleFilterV1(OrganizationalModelFilterMixinV1): + weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.RouteTarget, lookups=True) +class RouteTargetFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + importing_vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + exporting_vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + importing_l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + exporting_l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Service, lookups=True) +class ServiceFilterV1(ContactFilterMixinV1, ServiceBaseFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_object_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.ServiceTemplate, lookups=True) +class ServiceTemplateFilterV1(ServiceBaseFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.VLAN, lookups=True) +class VLANFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + group: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field() + role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + qinq_svlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + qinq_svlan_id: ID | None = strawberry_django.filter_field() + qinq_cvlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VLANGroup, lookups=True) +class VLANGroupFilterV1(ScopedFilterMixinV1, OrganizationalModelFilterMixinV1): + vid_ranges: Annotated['IntegerRangeArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True) +class VLANTranslationPolicyFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.VLANTranslationRule, lookups=True) +class VLANTranslationRuleFilterV1(NetBoxModelFilterMixinV1): + policy: Annotated['VLANTranslationPolicyFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + policy_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + remote_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VRF, lookups=True) +class VRFFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + rd: FilterLookup[str] | None = strawberry_django.filter_field() + enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() + import_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + export_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/ipam/graphql/mixins_v1.py b/netbox/ipam/graphql/mixins_v1.py new file mode 100644 index 000000000..6d3e31197 --- /dev/null +++ b/netbox/ipam/graphql/mixins_v1.py @@ -0,0 +1,18 @@ +from typing import Annotated, List + +import strawberry + +__all__ = ( + 'IPAddressesMixinV1', + 'VLANGroupsMixinV1', +) + + +@strawberry.type +class IPAddressesMixinV1: + ip_addresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] # noqa: F821 + + +@strawberry.type +class VLANGroupsMixinV1: + vlan_groups: List[Annotated["VLANGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] # noqa: F821 diff --git a/netbox/ipam/graphql/schema_v1.py b/netbox/ipam/graphql/schema_v1.py new file mode 100644 index 000000000..3bc7fdd97 --- /dev/null +++ b/netbox/ipam/graphql/schema_v1.py @@ -0,0 +1,63 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class IPAMQueryV1: + asn: ASNTypeV1 = strawberry_django.field() + asn_list: List[ASNTypeV1] = strawberry_django.field() + + asn_range: ASNRangeTypeV1 = strawberry_django.field() + asn_range_list: List[ASNRangeTypeV1] = strawberry_django.field() + + aggregate: AggregateTypeV1 = strawberry_django.field() + aggregate_list: List[AggregateTypeV1] = strawberry_django.field() + + ip_address: IPAddressTypeV1 = strawberry_django.field() + ip_address_list: List[IPAddressTypeV1] = strawberry_django.field() + + ip_range: IPRangeTypeV1 = strawberry_django.field() + ip_range_list: List[IPRangeTypeV1] = strawberry_django.field() + + prefix: PrefixTypeV1 = strawberry_django.field() + prefix_list: List[PrefixTypeV1] = strawberry_django.field() + + rir: RIRTypeV1 = strawberry_django.field() + rir_list: List[RIRTypeV1] = strawberry_django.field() + + role: RoleTypeV1 = strawberry_django.field() + role_list: List[RoleTypeV1] = strawberry_django.field() + + route_target: RouteTargetTypeV1 = strawberry_django.field() + route_target_list: List[RouteTargetTypeV1] = strawberry_django.field() + + service: ServiceTypeV1 = strawberry_django.field() + service_list: List[ServiceTypeV1] = strawberry_django.field() + + service_template: ServiceTemplateTypeV1 = strawberry_django.field() + service_template_list: List[ServiceTemplateTypeV1] = strawberry_django.field() + + fhrp_group: FHRPGroupTypeV1 = strawberry_django.field() + fhrp_group_list: List[FHRPGroupTypeV1] = strawberry_django.field() + + fhrp_group_assignment: FHRPGroupAssignmentTypeV1 = strawberry_django.field() + fhrp_group_assignment_list: List[FHRPGroupAssignmentTypeV1] = strawberry_django.field() + + vlan: VLANTypeV1 = strawberry_django.field() + vlan_list: List[VLANTypeV1] = strawberry_django.field() + + vlan_group: VLANGroupTypeV1 = strawberry_django.field() + vlan_group_list: List[VLANGroupTypeV1] = strawberry_django.field() + + vlan_translation_policy: VLANTranslationPolicyTypeV1 = strawberry_django.field() + vlan_translation_policy_list: List[VLANTranslationPolicyTypeV1] = strawberry_django.field() + + vlan_translation_rule: VLANTranslationRuleTypeV1 = strawberry_django.field() + vlan_translation_rule_list: List[VLANTranslationRuleTypeV1] = strawberry_django.field() + + vrf: VRFTypeV1 = strawberry_django.field() + vrf_list: List[VRFTypeV1] = strawberry_django.field() diff --git a/netbox/ipam/graphql/types_v1.py b/netbox/ipam/graphql/types_v1.py new file mode 100644 index 000000000..7b5d2c652 --- /dev/null +++ b/netbox/ipam/graphql/types_v1.py @@ -0,0 +1,360 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from circuits.graphql.types_v1 import ProviderTypeV1 +from dcim.graphql.types_v1 import SiteTypeV1 +from extras.graphql.mixins_v1 import ContactsMixinV1 +from ipam import models +from netbox.graphql.scalars import BigInt +from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1 +from .filters_v1 import * +from .mixins_v1 import IPAddressesMixinV1 + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import ( + DeviceTypeV1, + InterfaceTypeV1, + LocationTypeV1, + RackTypeV1, + RegionTypeV1, + SiteGroupTypeV1, + SiteTypeV1, + ) + from tenancy.graphql.types_v1 import TenantTypeV1 + from virtualization.graphql.types_v1 import ( + ClusterGroupTypeV1, ClusterTypeV1, VMInterfaceTypeV1, VirtualMachineTypeV1 + ) + from vpn.graphql.types_v1 import L2VPNTypeV1, TunnelTerminationTypeV1 + from wireless.graphql.types_v1 import WirelessLANTypeV1 + +__all__ = ( + 'ASNTypeV1', + 'ASNRangeTypeV1', + 'AggregateTypeV1', + 'FHRPGroupTypeV1', + 'FHRPGroupAssignmentTypeV1', + 'IPAddressTypeV1', + 'IPRangeTypeV1', + 'PrefixTypeV1', + 'RIRTypeV1', + 'RoleTypeV1', + 'RouteTargetTypeV1', + 'ServiceTypeV1', + 'ServiceTemplateTypeV1', + 'VLANTypeV1', + 'VLANGroupTypeV1', + 'VLANTranslationPolicyTypeV1', + 'VLANTranslationRuleTypeV1', + 'VRFTypeV1', +) + + +@strawberry.type +class IPAddressFamilyTypeV1: + value: int + label: str + + +@strawberry.type +class BaseIPAddressFamilyTypeV1: + """ + Base type for models that need to expose their IPAddress family type. + """ + + @strawberry.field + def family(self) -> IPAddressFamilyTypeV1: + # Note that self, is an instance of models.IPAddress + # thus resolves to the address family value. + return IPAddressFamilyTypeV1(value=self.family, label=f'IPv{self.family}') + + +@strawberry_django.type( + models.ASN, + fields='__all__', + filters=ASNFilterV1, + pagination=True +) +class ASNTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + asn: BigInt + rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + sites: List[SiteTypeV1] + providers: List[ProviderTypeV1] + + +@strawberry_django.type( + models.ASNRange, + fields='__all__', + filters=ASNRangeFilterV1, + pagination=True +) +class ASNRangeTypeV1(NetBoxObjectTypeV1): + start: BigInt + end: BigInt + rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Aggregate, + fields='__all__', + filters=AggregateFilterV1, + pagination=True +) +class AggregateTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): + prefix: str + rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + +@strawberry_django.type( + models.FHRPGroup, + fields='__all__', + filters=FHRPGroupFilterV1, + pagination=True +) +class FHRPGroupTypeV1(NetBoxObjectTypeV1, IPAddressesMixinV1): + + fhrpgroupassignment_set: List[Annotated["FHRPGroupAssignmentTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.FHRPGroupAssignment, + exclude=['interface_type', 'interface_id'], + filters=FHRPGroupAssignmentFilterV1, + pagination=True +) +class FHRPGroupAssignmentTypeV1(BaseObjectTypeV1): + group: Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')] + + @strawberry_django.field + def interface(self) -> Annotated[Union[ + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + ], strawberry.union("FHRPGroupInterfaceTypeV1")]: + return self.interface + + +@strawberry_django.type( + models.IPAddress, + exclude=['assigned_object_type', 'assigned_object_id', 'address'], + filters=IPAddressFilterV1, + pagination=True +) +class IPAddressTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): + address: str + vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + nat_inside: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + nat_outside: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + tunnel_terminations: List[Annotated["TunnelTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + @strawberry_django.field + def assigned_object(self) -> Annotated[Union[ + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')], + Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + ], strawberry.union("IPAddressAssignmentTypeV1")] | None: + return self.assigned_object + + +@strawberry_django.type( + models.IPRange, + fields='__all__', + filters=IPRangeFilterV1, + pagination=True +) +class IPRangeTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + start_address: str + end_address: str + vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Prefix, + exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], + filters=PrefixFilterV1, + pagination=True +) +class PrefixTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): + prefix: str + vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + @strawberry_django.field + def scope(self) -> Annotated[Union[ + Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("PrefixScopeTypeV1")] | None: + return self.scope + + +@strawberry_django.type( + models.RIR, + fields='__all__', + filters=RIRFilterV1, + pagination=True +) +class RIRTypeV1(OrganizationalObjectTypeV1): + + asn_ranges: List[Annotated["ASNRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + aggregates: List[Annotated["AggregateTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.Role, + fields='__all__', + filters=RoleFilterV1, + pagination=True +) +class RoleTypeV1(OrganizationalObjectTypeV1): + + prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + ip_ranges: List[Annotated["IPRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.RouteTarget, + fields='__all__', + filters=RouteTargetFilterV1, + pagination=True +) +class RouteTargetTypeV1(NetBoxObjectTypeV1): + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + importing_l2vpns: List[Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + exporting_l2vpns: List[Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + importing_vrfs: List[Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + exporting_vrfs: List[Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.Service, + exclude=('parent_object_type', 'parent_object_id'), + filters=ServiceFilterV1, + pagination=True +) +class ServiceTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): + ports: List[int] + ipaddresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + @strawberry_django.field + def parent(self) -> Annotated[Union[ + Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')], + ], strawberry.union("ServiceParentTypeV1")] | None: + return self.parent + + +@strawberry_django.type( + models.ServiceTemplate, + fields='__all__', + filters=ServiceTemplateFilterV1, + pagination=True +) +class ServiceTemplateTypeV1(NetBoxObjectTypeV1): + ports: List[int] + + +@strawberry_django.type( + models.VLAN, + exclude=['qinq_svlan'], + filters=VLANFilterV1, + pagination=True +) +class VLANTypeV1(NetBoxObjectTypeV1): + site: Annotated["SiteTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + group: Annotated["VLANGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + interfaces_as_untagged: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + vminterfaces_as_untagged: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + wirelesslan_set: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] + prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + interfaces_as_tagged: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + vminterfaces_as_tagged: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + + @strawberry_django.field + def qinq_svlan(self) -> Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None: + return self.qinq_svlan + + +@strawberry_django.type( + models.VLANGroup, + exclude=['scope_type', 'scope_id'], + filters=VLANGroupFilterV1, + pagination=True +) +class VLANGroupTypeV1(OrganizationalObjectTypeV1): + + vlans: List[VLANTypeV1] + vid_ranges: List[str] + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + @strawberry_django.field + def scope(self) -> Annotated[Union[ + Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("VLANGroupScopeTypeV1")] | None: + return self.scope + + +@strawberry_django.type( + models.VLANTranslationPolicy, + fields='__all__', + filters=VLANTranslationPolicyFilterV1, + pagination=True +) +class VLANTranslationPolicyTypeV1(NetBoxObjectTypeV1): + rules: List[Annotated["VLANTranslationRuleTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.VLANTranslationRule, + fields='__all__', + filters=VLANTranslationRuleFilterV1, + pagination=True +) +class VLANTranslationRuleTypeV1(NetBoxObjectTypeV1): + policy: Annotated[ + "VLANTranslationPolicyTypeV1", + strawberry.lazy('ipam.graphql.types_v1') + ] = strawberry_django.field(select_related=["policy"]) + + +@strawberry_django.type( + models.VRF, + fields='__all__', + filters=VRFFilterV1, + pagination=True +) +class VRFTypeV1(NetBoxObjectTypeV1): + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + ip_addresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + vminterfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + ip_ranges: List[Annotated["IPRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + export_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + import_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] diff --git a/netbox/netbox/graphql/filter_mixins_v1.py b/netbox/netbox/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..2d34ff12d --- /dev/null +++ b/netbox/netbox/graphql/filter_mixins_v1.py @@ -0,0 +1,104 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import TypeVar, TYPE_CHECKING, Annotated + +import strawberry +import strawberry_django +from strawberry import ID +from strawberry_django import FilterLookup, DatetimeFilterLookup + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1, BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 +from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, JournalEntriesFilterMixinV1, TagsFilterMixinV1 + +__all__ = ( + 'DistanceFilterMixinV1', + 'ImageAttachmentFilterMixinV1', + 'NestedGroupModelFilterMixinV1', + 'NetBoxModelFilterMixinV1', + 'OrganizationalModelFilterMixinV1', + 'PrimaryModelFilterMixinV1', + 'SyncedDataFilterMixinV1', + 'WeightFilterMixinV1', +) + +T = TypeVar('T') + + +if TYPE_CHECKING: + from .enums import * + from core.graphql.filters_v1 import * + from extras.graphql.filters_v1 import * + + +class NetBoxModelFilterMixinV1( + ChangeLogFilterMixinV1, + CustomFieldsFilterMixinV1, + JournalEntriesFilterMixinV1, + TagsFilterMixinV1, + BaseObjectTypeFilterMixinV1, +): + pass + + +@dataclass +class NestedGroupModelFilterMixinV1(NetBoxModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + parent_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class OrganizationalModelFilterMixinV1( + ChangeLogFilterMixinV1, + CustomFieldsFilterMixinV1, + TagsFilterMixinV1, + BaseObjectTypeFilterMixinV1, +): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class PrimaryModelFilterMixinV1(NetBoxModelFilterMixinV1): + description: FilterLookup[str] | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ImageAttachmentFilterMixinV1(BaseFilterMixinV1): + images: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class WeightFilterMixinV1(BaseFilterMixinV1): + weight: FilterLookup[float] | None = strawberry_django.filter_field() + weight_unit: Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class SyncedDataFilterMixinV1(BaseFilterMixinV1): + data_source: Annotated['DataSourceFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + data_source_id: FilterLookup[int] | None = strawberry_django.filter_field() + data_file: Annotated['DataFileFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + data_file_id: FilterLookup[int] | None = strawberry_django.filter_field() + data_path: FilterLookup[str] | None = strawberry_django.filter_field() + auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + + +@dataclass +class DistanceFilterMixinV1(BaseFilterMixinV1): + distance: FilterLookup[float] | None = strawberry_django.filter_field() + distance_unit: Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py index 70a6ec7bf..54fe61712 100644 --- a/netbox/netbox/graphql/schema.py +++ b/netbox/netbox/graphql/schema.py @@ -4,16 +4,26 @@ from strawberry_django.optimizer import DjangoOptimizerExtension from strawberry.extensions import MaxAliasesLimiter from strawberry.schema.config import StrawberryConfig +from circuits.graphql.schema_v1 import CircuitsQueryV1 from circuits.graphql.schema import CircuitsQuery +from core.graphql.schema_v1 import CoreQueryV1 from core.graphql.schema import CoreQuery +from dcim.graphql.schema_v1 import DCIMQueryV1 from dcim.graphql.schema import DCIMQuery +from extras.graphql.schema_v1 import ExtrasQueryV1 from extras.graphql.schema import ExtrasQuery +from ipam.graphql.schema_v1 import IPAMQueryV1 from ipam.graphql.schema import IPAMQuery from netbox.registry import registry +from tenancy.graphql.schema_v1 import TenancyQueryV1 from tenancy.graphql.schema import TenancyQuery +from users.graphql.schema_v1 import UsersQueryV1 from users.graphql.schema import UsersQuery +from virtualization.graphql.schema_v1 import VirtualizationQueryV1 from virtualization.graphql.schema import VirtualizationQuery +from vpn.graphql.schema_v1 import VPNQueryV1 from vpn.graphql.schema import VPNQuery +from wireless.graphql.schema_v1 import WirelessQueryV1 from wireless.graphql.schema import WirelessQuery __all__ = ( @@ -27,16 +37,16 @@ __all__ = ( @strawberry.type class QueryV1( - UsersQuery, - CircuitsQuery, - CoreQuery, - DCIMQuery, - ExtrasQuery, - IPAMQuery, - TenancyQuery, - VirtualizationQuery, - VPNQuery, - WirelessQuery, + UsersQueryV1, + CircuitsQueryV1, + CoreQueryV1, + DCIMQueryV1, + ExtrasQueryV1, + IPAMQueryV1, + TenancyQueryV1, + VirtualizationQueryV1, + VPNQueryV1, + WirelessQueryV1, *registry['plugins']['graphql_schemas'], # Append plugin schemas ): """Query class for GraphQL API v1""" diff --git a/netbox/netbox/graphql/types_v1.py b/netbox/netbox/graphql/types_v1.py new file mode 100644 index 000000000..c6b3ad2ec --- /dev/null +++ b/netbox/netbox/graphql/types_v1.py @@ -0,0 +1,100 @@ +import strawberry +import strawberry_django +from strawberry.types import Info +from django.contrib.contenttypes.models import ContentType + +from core.graphql.mixins import ChangelogMixin +from core.models import ObjectType as ObjectType_ +from extras.graphql.mixins import CustomFieldsMixin, JournalEntriesMixin, TagsMixin + +__all__ = ( + 'BaseObjectTypeV1', + 'ContentTypeTypeV1', + 'ObjectTypeV1', + 'OrganizationalObjectTypeV1', + 'NetBoxObjectTypeV1', +) + + +# +# Base types +# + +@strawberry.type +class BaseObjectTypeV1: + """ + Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions. + """ + + @classmethod + def get_queryset(cls, queryset, info: Info, **kwargs): + # Enforce object permissions on the queryset + if hasattr(queryset, 'restrict'): + return queryset.restrict(info.context.request.user, 'view') + else: + return queryset + + @strawberry_django.field + def display(self) -> str: + return str(self) + + @strawberry_django.field + def class_type(self) -> str: + return self.__class__.__name__ + + +class ObjectTypeV1( + ChangelogMixin, + BaseObjectTypeV1 +): + """ + Base GraphQL object type for unclassified models which support change logging + """ + pass + + +class OrganizationalObjectTypeV1( + ChangelogMixin, + CustomFieldsMixin, + TagsMixin, + BaseObjectTypeV1 +): + """ + Base type for organizational models + """ + pass + + +class NetBoxObjectTypeV1( + ChangelogMixin, + CustomFieldsMixin, + JournalEntriesMixin, + TagsMixin, + BaseObjectTypeV1 +): + """ + GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags. + """ + pass + + +# +# Miscellaneous types +# + +@strawberry_django.type( + ContentType, + fields=['id', 'app_label', 'model'], + pagination=True +) +class ContentTypeTypeV1: + pass + + +@strawberry_django.type( + ObjectType_, + fields=['id', 'app_label', 'model'], + pagination=True +) +class ObjectTypeTypeV1: + pass diff --git a/netbox/tenancy/graphql/filter_mixins_v1.py b/netbox/tenancy/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..c5ffd914f --- /dev/null +++ b/netbox/tenancy/graphql/filter_mixins_v1.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry import ID + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 + +if TYPE_CHECKING: + from netbox.graphql.filter_lookups import TreeNodeFilter + from .filters_v1 import ContactAssignmentFilterV1, TenantFilterV1, TenantGroupFilterV1 + +__all__ = ( + 'ContactFilterMixinV1', + 'TenancyFilterMixinV1', +) + + +@dataclass +class ContactFilterMixinV1(BaseFilterMixinV1): + contacts: Annotated['ContactAssignmentFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TenancyFilterMixinV1(BaseFilterMixinV1): + tenant: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + tenant_group: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tenant_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/tenancy/graphql/filters_v1.py b/netbox/tenancy/graphql/filters_v1.py new file mode 100644 index 000000000..fb101858b --- /dev/null +++ b/netbox/tenancy/graphql/filters_v1.py @@ -0,0 +1,210 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import ChangeLogFilterMixinV1 +from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + NestedGroupModelFilterMixinV1, + OrganizationalModelFilterMixinV1, + PrimaryModelFilterMixinV1, +) +from tenancy import models +from .filter_mixins_v1 import ContactFilterMixinV1 + +if TYPE_CHECKING: + from core.graphql.filters_v1 import ContentTypeFilterV1 + from circuits.graphql.filters_v1 import CircuitFilterV1, CircuitGroupFilterV1, VirtualCircuitFilterV1 + from dcim.graphql.filters_v1 import ( + CableFilterV1, + DeviceFilterV1, + LocationFilterV1, + PowerFeedFilterV1, + RackFilterV1, + RackReservationFilterV1, + SiteFilterV1, + VirtualDeviceContextFilterV1, + ) + from ipam.graphql.filters_v1 import ( + AggregateFilterV1, + ASNFilterV1, + ASNRangeFilterV1, + IPAddressFilterV1, + IPRangeFilterV1, + PrefixFilterV1, + RouteTargetFilterV1, + VLANFilterV1, + VLANGroupFilterV1, + VRFFilterV1, + ) + from netbox.graphql.filter_lookups import TreeNodeFilter + from wireless.graphql.filters_v1 import WirelessLANFilterV1, WirelessLinkFilterV1 + from virtualization.graphql.filters_v1 import ClusterFilterV1, VirtualMachineFilterV1 + from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelFilterV1 + from .enums import * + +__all__ = ( + 'TenantFilterV1', + 'TenantGroupFilterV1', + 'ContactFilterV1', + 'ContactRoleFilterV1', + 'ContactGroupFilterV1', + 'ContactAssignmentFilterV1', +) + + +@strawberry_django.filter_type(models.Tenant, lookups=True) +class TenantFilterV1(PrimaryModelFilterMixinV1, ContactFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + # Reverse relations + aggregates: Annotated['AggregateFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + asn_ranges: Annotated['ASNRangeFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cables: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + circuit_groups: Annotated['CircuitGroupFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + circuits: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + clusters: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + devices: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + ip_ranges: Annotated['IPRangeFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + locations: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + power_feeds: Annotated['PowerFeedFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + racks: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + rackreservations: Annotated['RackReservationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + route_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + sites: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tunnels: Annotated['TunnelFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vdcs: Annotated['VirtualDeviceContextFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + virtual_machines: Annotated[ + 'VirtualMachineFilterV1', strawberry.lazy('virtualization.graphql.filters_v1') + ] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + virtual_circuits: Annotated['VirtualCircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + wireless_lans: Annotated['WirelessLANFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + wireless_links: Annotated['WirelessLinkFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.TenantGroup, lookups=True) +class TenantGroupFilterV1(OrganizationalModelFilterMixinV1): + parent: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry.UNSET + tenants: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + children: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1'), True] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.Contact, lookups=True) +class ContactFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + title: FilterLookup[str] | None = strawberry_django.filter_field() + phone: FilterLookup[str] | None = strawberry_django.filter_field() + email: FilterLookup[str] | None = strawberry_django.filter_field() + address: FilterLookup[str] | None = strawberry_django.filter_field() + link: FilterLookup[str] | None = strawberry_django.filter_field() + groups: Annotated['ContactGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + assignments: Annotated['ContactAssignmentFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ContactRole, lookups=True) +class ContactRoleFilterV1(OrganizationalModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.ContactGroup, lookups=True) +class ContactGroupFilterV1(NestedGroupModelFilterMixinV1): + parent: Annotated['ContactGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ContactAssignment, lookups=True) +class ContactAssignmentFilterV1(CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1): + object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + object_id: ID | None = strawberry_django.filter_field() + contact: Annotated['ContactFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + contact_id: ID | None = strawberry_django.filter_field() + role: Annotated['ContactRoleFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + priority: Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/tenancy/graphql/mixins_v1.py b/netbox/tenancy/graphql/mixins_v1.py new file mode 100644 index 000000000..a6c31b68a --- /dev/null +++ b/netbox/tenancy/graphql/mixins_v1.py @@ -0,0 +1,12 @@ +from typing import Annotated, List + +import strawberry + +__all__ = ( + 'ContactAssignmentsMixinV1', +) + + +@strawberry.type +class ContactAssignmentsMixinV1: + assignments: List[Annotated["ContactAssignmentTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] # noqa: F821 diff --git a/netbox/tenancy/graphql/schema_v1.py b/netbox/tenancy/graphql/schema_v1.py new file mode 100644 index 000000000..135f1573f --- /dev/null +++ b/netbox/tenancy/graphql/schema_v1.py @@ -0,0 +1,27 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class TenancyQueryV1: + tenant: TenantTypeV1 = strawberry_django.field() + tenant_list: List[TenantTypeV1] = strawberry_django.field() + + tenant_group: TenantGroupTypeV1 = strawberry_django.field() + tenant_group_list: List[TenantGroupTypeV1] = strawberry_django.field() + + contact: ContactTypeV1 = strawberry_django.field() + contact_list: List[ContactTypeV1] = strawberry_django.field() + + contact_role: ContactRoleTypeV1 = strawberry_django.field() + contact_role_list: List[ContactRoleTypeV1] = strawberry_django.field() + + contact_group: ContactGroupTypeV1 = strawberry_django.field() + contact_group_list: List[ContactGroupTypeV1] = strawberry_django.field() + + contact_assignment: ContactAssignmentTypeV1 = strawberry_django.field() + contact_assignment_list: List[ContactAssignmentTypeV1] = strawberry_django.field() diff --git a/netbox/tenancy/graphql/types_v1.py b/netbox/tenancy/graphql/types_v1.py new file mode 100644 index 000000000..cc4b774bc --- /dev/null +++ b/netbox/tenancy/graphql/types_v1.py @@ -0,0 +1,147 @@ +from typing import Annotated, List, TYPE_CHECKING + +import strawberry +import strawberry_django + +from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1, ContactsMixinV1 +from netbox.graphql.types_v1 import BaseObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from tenancy import models +from .filters_v1 import * +from .mixins_v1 import ContactAssignmentsMixinV1 + +if TYPE_CHECKING: + from circuits.graphql.types_v1 import CircuitTypeV1 + from dcim.graphql.types_v1 import ( + CableTypeV1, + DeviceTypeV1, + LocationTypeV1, + PowerFeedTypeV1, + RackTypeV1, + RackReservationTypeV1, + SiteTypeV1, + VirtualDeviceContextTypeV1, + ) + from ipam.graphql.types_v1 import ( + AggregateTypeV1, + ASNTypeV1, + ASNRangeTypeV1, + IPAddressTypeV1, + IPRangeTypeV1, + PrefixTypeV1, + RouteTargetTypeV1, + VLANTypeV1, + VRFTypeV1, + ) + from netbox.graphql.types_v1 import ContentTypeTypeV1 + from wireless.graphql.types_v1 import WirelessLANTypeV1, WirelessLinkTypeV1 + from virtualization.graphql.types_v1 import ClusterTypeV1, VirtualMachineTypeV1 + from vpn.graphql.types_v1 import L2VPNTypeV1, TunnelTypeV1 + +__all__ = ( + 'ContactAssignmentTypeV1', + 'ContactGroupTypeV1', + 'ContactRoleTypeV1', + 'ContactTypeV1', + 'TenantTypeV1', + 'TenantGroupTypeV1', +) + + +# +# Tenants +# + +@strawberry_django.type( + models.Tenant, + fields='__all__', + filters=TenantFilterV1, + pagination=True +) +class TenantTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): + group: Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None + asns: List[Annotated['ASNTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + circuits: List[Annotated['CircuitTypeV1', strawberry.lazy('circuits.graphql.types_v1')]] + sites: List[Annotated['SiteTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + vlans: List[Annotated['VLANTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + wireless_lans: List[Annotated['WirelessLANTypeV1', strawberry.lazy('wireless.graphql.types_v1')]] + route_targets: List[Annotated['RouteTargetTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + locations: List[Annotated['LocationTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + ip_ranges: List[Annotated['IPRangeTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + rackreservations: List[Annotated['RackReservationTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + racks: List[Annotated['RackTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + vdcs: List[Annotated['VirtualDeviceContextTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + prefixes: List[Annotated['PrefixTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + cables: List[Annotated['CableTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + virtual_machines: List[Annotated['VirtualMachineTypeV1', strawberry.lazy('virtualization.graphql.types_v1')]] + vrfs: List[Annotated['VRFTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + asn_ranges: List[Annotated['ASNRangeTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + wireless_links: List[Annotated['WirelessLinkTypeV1', strawberry.lazy('wireless.graphql.types_v1')]] + aggregates: List[Annotated['AggregateTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + power_feeds: List[Annotated['PowerFeedTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + devices: List[Annotated['DeviceTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] + tunnels: List[Annotated['TunnelTypeV1', strawberry.lazy('vpn.graphql.types_v1')]] + ip_addresses: List[Annotated['IPAddressTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] + clusters: List[Annotated['ClusterTypeV1', strawberry.lazy('virtualization.graphql.types_v1')]] + l2vpns: List[Annotated['L2VPNTypeV1', strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.TenantGroup, + fields='__all__', + filters=TenantGroupFilterV1, + pagination=True +) +class TenantGroupTypeV1(OrganizationalObjectTypeV1): + parent: Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None + + tenants: List[TenantTypeV1] + children: List[Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] + + +# +# Contacts +# + +@strawberry_django.type( + models.Contact, + fields='__all__', + filters=ContactFilterV1, + pagination=True +) +class ContactTypeV1(ContactAssignmentsMixinV1, NetBoxObjectTypeV1): + groups: List[Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] + + +@strawberry_django.type( + models.ContactRole, + fields='__all__', + filters=ContactRoleFilterV1, + pagination=True +) +class ContactRoleTypeV1(ContactAssignmentsMixinV1, OrganizationalObjectTypeV1): + pass + + +@strawberry_django.type( + models.ContactGroup, + fields='__all__', + filters=ContactGroupFilterV1, + pagination=True +) +class ContactGroupTypeV1(OrganizationalObjectTypeV1): + parent: Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None + + contacts: List[ContactTypeV1] + children: List[Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] + + +@strawberry_django.type( + models.ContactAssignment, + fields='__all__', + filters=ContactAssignmentFilterV1, + pagination=True +) +class ContactAssignmentTypeV1(CustomFieldsMixinV1, TagsMixinV1, BaseObjectTypeV1): + object_type: Annotated['ContentTypeTypeV1', strawberry.lazy('netbox.graphql.types_v1')] | None + contact: Annotated['ContactTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None + role: Annotated['ContactRoleTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None diff --git a/netbox/users/graphql/filters_v1.py b/netbox/users/graphql/filters_v1.py new file mode 100644 index 000000000..1b08ed6fc --- /dev/null +++ b/netbox/users/graphql/filters_v1.py @@ -0,0 +1,34 @@ +from datetime import datetime +from typing import Annotated + +import strawberry +import strawberry_django +from strawberry_django import DatetimeFilterLookup, FilterLookup + +from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1 +from users import models + +__all__ = ( + 'GroupFilterV1', + 'UserFilterV1', +) + + +@strawberry_django.filter_type(models.Group, lookups=True) +class GroupFilterV1(BaseObjectTypeFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.User, lookups=True) +class UserFilterV1(BaseObjectTypeFilterMixinV1): + username: FilterLookup[str] | None = strawberry_django.filter_field() + first_name: FilterLookup[str] | None = strawberry_django.filter_field() + last_name: FilterLookup[str] | None = strawberry_django.filter_field() + email: FilterLookup[str] | None = strawberry_django.filter_field() + is_superuser: FilterLookup[bool] | None = strawberry_django.filter_field() + is_active: FilterLookup[bool] | None = strawberry_django.filter_field() + date_joined: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_login: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field()) diff --git a/netbox/users/graphql/schema_v1.py b/netbox/users/graphql/schema_v1.py new file mode 100644 index 000000000..69c3b9d57 --- /dev/null +++ b/netbox/users/graphql/schema_v1.py @@ -0,0 +1,15 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class UsersQueryV1: + group: GroupTypeV1 = strawberry_django.field() + group_list: List[GroupTypeV1] = strawberry_django.field() + + user: UserTypeV1 = strawberry_django.field() + user_list: List[UserTypeV1] = strawberry_django.field() diff --git a/netbox/users/graphql/types_v1.py b/netbox/users/graphql/types_v1.py new file mode 100644 index 000000000..3b48431d0 --- /dev/null +++ b/netbox/users/graphql/types_v1.py @@ -0,0 +1,34 @@ +from typing import List + +import strawberry_django + +from netbox.graphql.types_v1 import BaseObjectTypeV1 +from users.models import Group, User +from .filters_v1 import * + +__all__ = ( + 'GroupTypeV1', + 'UserTypeV1', +) + + +@strawberry_django.type( + Group, + fields=['id', 'name'], + filters=GroupFilterV1, + pagination=True +) +class GroupTypeV1(BaseObjectTypeV1): + pass + + +@strawberry_django.type( + User, + fields=[ + 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'date_joined', 'groups', + ], + filters=UserFilterV1, + pagination=True +) +class UserTypeV1(BaseObjectTypeV1): + groups: List[GroupTypeV1] diff --git a/netbox/virtualization/graphql/filter_mixins_v1.py b/netbox/virtualization/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..7de4192c8 --- /dev/null +++ b/netbox/virtualization/graphql/filter_mixins_v1.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry import ID +from strawberry_django import FilterLookup + +from netbox.graphql.filter_mixins_v1 import NetBoxModelFilterMixinV1 + +if TYPE_CHECKING: + from .filters_v1 import VirtualMachineFilterV1 + +__all__ = ( + 'VMComponentFilterMixinV1', +) + + +@dataclass +class VMComponentFilterMixinV1(NetBoxModelFilterMixinV1): + virtual_machine: Annotated[ + 'VirtualMachineFilterV1', strawberry.lazy('virtualization.graphql.filters_v1') + ] | None = ( + strawberry_django.filter_field() + ) + virtual_machine_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/filters_v1.py b/netbox/virtualization/graphql/filters_v1.py new file mode 100644 index 000000000..d1ba0cff4 --- /dev/null +++ b/netbox/virtualization/graphql/filters_v1.py @@ -0,0 +1,170 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from dcim.graphql.filter_mixins_v1 import InterfaceBaseFilterMixinV1, RenderConfigFilterMixinV1, ScopedFilterMixinV1 +from extras.graphql.filter_mixins_v1 import ConfigContextFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + ImageAttachmentFilterMixinV1, + OrganizationalModelFilterMixinV1, + PrimaryModelFilterMixinV1, +) +from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 +from virtualization import models +from virtualization.graphql.filter_mixins_v1 import VMComponentFilterMixinV1 + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup + from dcim.graphql.filters_v1 import ( + DeviceFilterV1, DeviceRoleFilterV1, MACAddressFilterV1, PlatformFilterV1, SiteFilterV1 + ) + from ipam.graphql.filters_v1 import ( + FHRPGroupAssignmentFilterV1, + IPAddressFilterV1, + ServiceFilterV1, + VLANGroupFilterV1, + VRFFilterV1, + ) + from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelTerminationFilterV1 + +__all__ = ( + 'ClusterFilterV1', + 'ClusterGroupFilterV1', + 'ClusterTypeFilterV1', + 'VirtualMachineFilterV1', + 'VMInterfaceFilterV1', + 'VirtualDiskFilterV1', +) + + +@strawberry_django.filter_type(models.Cluster, lookups=True) +class ClusterFilterV1(ContactFilterMixinV1, ScopedFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + type: Annotated['ClusterTypeFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + group: Annotated['ClusterGroupFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + status: Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ClusterGroup, lookups=True) +class ClusterGroupFilterV1(ContactFilterMixinV1, OrganizationalModelFilterMixinV1): + vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.ClusterType, lookups=True) +class ClusterTypeFilterV1(OrganizationalModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.VirtualMachine, lookups=True) +class VirtualMachineFilterV1( + ContactFilterMixinV1, + ImageAttachmentFilterMixinV1, + RenderConfigFilterMixinV1, + ConfigContextFilterMixinV1, + TenancyFilterMixinV1, + PrimaryModelFilterMixinV1, +): + name: FilterLookup[str] | None = strawberry_django.filter_field() + site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + site_id: ID | None = strawberry_django.filter_field() + cluster: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + cluster_id: ID | None = strawberry_django.filter_field() + device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + device_id: ID | None = strawberry_django.filter_field() + platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + platform_id: ID | None = strawberry_django.filter_field() + status: Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + vcpus: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + memory: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + disk: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + serial: FilterLookup[str] | None = strawberry_django.filter_field() + interface_count: FilterLookup[int] | None = strawberry_django.filter_field() + virtual_disk_count: FilterLookup[int] | None = strawberry_django.filter_field() + interfaces: Annotated['VMInterfaceFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + services: Annotated['ServiceFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + virtual_disks: Annotated['VirtualDiskFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VMInterface, lookups=True) +class VMInterfaceFilterV1(VMComponentFilterMixinV1, InterfaceBaseFilterMixinV1): + ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + parent: Annotated['VMInterfaceFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + fhrp_group_assignments: Annotated[ + 'FHRPGroupAssignmentFilterV1', strawberry.lazy('ipam.graphql.filters_v1') + ] | None = ( + strawberry_django.filter_field() + ) + tunnel_terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + mac_addresses: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.VirtualDisk, lookups=True) +class VirtualDiskFilterV1(VMComponentFilterMixinV1): + size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/virtualization/graphql/schema_v1.py b/netbox/virtualization/graphql/schema_v1.py new file mode 100644 index 000000000..85994411f --- /dev/null +++ b/netbox/virtualization/graphql/schema_v1.py @@ -0,0 +1,27 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class VirtualizationQueryV1: + cluster: ClusterTypeV1 = strawberry_django.field() + cluster_list: List[ClusterTypeV1] = strawberry_django.field() + + cluster_group: ClusterGroupTypeV1 = strawberry_django.field() + cluster_group_list: List[ClusterGroupTypeV1] = strawberry_django.field() + + cluster_type: ClusterTypeTypeV1 = strawberry_django.field() + cluster_type_list: List[ClusterTypeTypeV1] = strawberry_django.field() + + virtual_machine: VirtualMachineTypeV1 = strawberry_django.field() + virtual_machine_list: List[VirtualMachineTypeV1] = strawberry_django.field() + + vm_interface: VMInterfaceTypeV1 = strawberry_django.field() + vm_interface_list: List[VMInterfaceTypeV1] = strawberry_django.field() + + virtual_disk: VirtualDiskTypeV1 = strawberry_django.field() + virtual_disk_list: List[VirtualDiskTypeV1] = strawberry_django.field() diff --git a/netbox/virtualization/graphql/types_v1.py b/netbox/virtualization/graphql/types_v1.py new file mode 100644 index 000000000..079fa7b29 --- /dev/null +++ b/netbox/virtualization/graphql/types_v1.py @@ -0,0 +1,146 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from extras.graphql.mixins_v1 import ConfigContextMixinV1, ContactsMixinV1 +from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 +from netbox.graphql.scalars import BigInt +from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from virtualization import models +from .filters_v1 import * + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import ( + DeviceRoleTypeV1, + DeviceTypeV1, + LocationTypeV1, + MACAddressTypeV1, + PlatformTypeV1, + RegionTypeV1, + SiteGroupTypeV1, + SiteTypeV1, + ) + from extras.graphql.types_v1 import ConfigTemplateTypeV1 + from ipam.graphql.types_v1 import IPAddressTypeV1, ServiceTypeV1, VLANTranslationPolicyTypeV1, VLANTypeV1, VRFTypeV1 + from tenancy.graphql.types_v1 import TenantTypeV1 + +__all__ = ( + 'ClusterTypeV1', + 'ClusterGroupTypeV1', + 'ClusterTypeTypeV1', + 'VirtualDiskTypeV1', + 'VirtualMachineTypeV1', + 'VMInterfaceTypeV1', +) + + +@strawberry.type +class ComponentTypeV1(NetBoxObjectTypeV1): + """ + Base type for device/VM components + """ + virtual_machine: Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] + + +@strawberry_django.type( + models.Cluster, + exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], + filters=ClusterFilterV1, + pagination=True +) +class ClusterTypeV1(ContactsMixinV1, VLANGroupsMixinV1, NetBoxObjectTypeV1): + type: Annotated["ClusterTypeTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + group: Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def scope(self) -> Annotated[Union[ + Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("ClusterScopeTypeV1")] | None: + return self.scope + + +@strawberry_django.type( + models.ClusterGroup, + fields='__all__', + filters=ClusterGroupFilterV1, + pagination=True +) +class ClusterGroupTypeV1(ContactsMixinV1, VLANGroupsMixinV1, OrganizationalObjectTypeV1): + + clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + + +@strawberry_django.type( + models.ClusterType, + fields='__all__', + filters=ClusterTypeFilterV1, + pagination=True +) +class ClusterTypeTypeV1(OrganizationalObjectTypeV1): + + clusters: List[ClusterTypeV1] + + +@strawberry_django.type( + models.VirtualMachine, + fields='__all__', + filters=VirtualMachineFilterV1, + pagination=True +) +class VirtualMachineTypeV1(ConfigContextMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): + interface_count: BigInt + virtual_disk_count: BigInt + interface_count: BigInt + config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None + site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + cluster: Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + role: Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + virtualdisks: List[Annotated["VirtualDiskTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + + +@strawberry_django.type( + models.VMInterface, + fields='__all__', + filters=VMInterfaceFilterV1, + pagination=True +) +class VMInterfaceTypeV1(IPAddressesMixinV1, ComponentTypeV1): + _name: str + mac_address: str | None + parent: Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + bridge: Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None + untagged_vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + primary_mac_address: Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + qinq_svlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + vlan_translation_policy: Annotated["VLANTranslationPolicyTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + tagged_vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + bridge_interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + child_interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] + mac_addresses: List[Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + +@strawberry_django.type( + models.VirtualDisk, + fields='__all__', + filters=VirtualDiskFilterV1, + pagination=True +) +class VirtualDiskTypeV1(ComponentTypeV1): + pass diff --git a/netbox/vpn/graphql/filters_v1.py b/netbox/vpn/graphql/filters_v1.py new file mode 100644 index 000000000..9b70b9a9e --- /dev/null +++ b/netbox/vpn/graphql/filters_v1.py @@ -0,0 +1,192 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 +from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + NetBoxModelFilterMixinV1, OrganizationalModelFilterMixinV1, PrimaryModelFilterMixinV1 +) +from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 +from vpn import models + +if TYPE_CHECKING: + from core.graphql.filters_v1 import ContentTypeFilterV1 + from ipam.graphql.filters_v1 import IPAddressFilterV1, RouteTargetFilterV1 + from netbox.graphql.filter_lookups import IntegerLookup + from .enums import * + +__all__ = ( + 'TunnelGroupFilterV1', + 'TunnelTerminationFilterV1', + 'TunnelFilterV1', + 'IKEProposalFilterV1', + 'IKEPolicyFilterV1', + 'IPSecProposalFilterV1', + 'IPSecPolicyFilterV1', + 'IPSecProfileFilterV1', + 'L2VPNFilterV1', + 'L2VPNTerminationFilterV1', +) + + +@strawberry_django.filter_type(models.TunnelGroup, lookups=True) +class TunnelGroupFilterV1(OrganizationalModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.TunnelTermination, lookups=True) +class TunnelTerminationFilterV1( + BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 +): + tunnel: Annotated['TunnelFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tunnel_id: ID | None = strawberry_django.filter_field() + role: Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type_id: ID | None = strawberry_django.filter_field() + termination_id: ID | None = strawberry_django.filter_field() + outside_ip: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + outside_ip_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.Tunnel, lookups=True) +class TunnelFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['TunnelGroupFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + encapsulation: Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ipsec_profile: Annotated['IPSecProfileFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + tunnel_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.IKEProposal, lookups=True) +class IKEProposalFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + authentication_method: Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + ike_policies: Annotated['IKEPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.IKEPolicy, lookups=True) +class IKEPolicyFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + version: Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + mode: Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + proposals: Annotated['IKEProposalFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + preshared_key: FilterLookup[str] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.IPSecProposal, lookups=True) +class IPSecProposalFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + sa_lifetime_data: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + ipsec_policies: Annotated['IPSecPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.IPSecPolicy, lookups=True) +class IPSecPolicyFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + proposals: Annotated['IPSecProposalFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + pfs_group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.IPSecProfile, lookups=True) +class IPSecProfileFilterV1(PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + mode: Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + ike_policy: Annotated['IKEPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + ike_policy_id: ID | None = strawberry_django.filter_field() + ipsec_policy: Annotated['IPSecPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + ipsec_policy_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.L2VPN, lookups=True) +class L2VPNFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + type: Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + import_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + export_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + terminations: Annotated['L2VPNTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.L2VPNTermination, lookups=True) +class L2VPNTerminationFilterV1(NetBoxModelFilterMixinV1): + l2vpn: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_id: ID | None = strawberry_django.filter_field() + assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/vpn/graphql/schema_v1.py b/netbox/vpn/graphql/schema_v1.py new file mode 100644 index 000000000..7ed22333c --- /dev/null +++ b/netbox/vpn/graphql/schema_v1.py @@ -0,0 +1,39 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class VPNQueryV1: + ike_policy: IKEPolicyTypeV1 = strawberry_django.field() + ike_policy_list: List[IKEPolicyTypeV1] = strawberry_django.field() + + ike_proposal: IKEProposalTypeV1 = strawberry_django.field() + ike_proposal_list: List[IKEProposalTypeV1] = strawberry_django.field() + + ipsec_policy: IPSecPolicyTypeV1 = strawberry_django.field() + ipsec_policy_list: List[IPSecPolicyTypeV1] = strawberry_django.field() + + ipsec_profile: IPSecProfileTypeV1 = strawberry_django.field() + ipsec_profile_list: List[IPSecProfileTypeV1] = strawberry_django.field() + + ipsec_proposal: IPSecProposalTypeV1 = strawberry_django.field() + ipsec_proposal_list: List[IPSecProposalTypeV1] = strawberry_django.field() + + l2vpn: L2VPNTypeV1 = strawberry_django.field() + l2vpn_list: List[L2VPNTypeV1] = strawberry_django.field() + + l2vpn_termination: L2VPNTerminationTypeV1 = strawberry_django.field() + l2vpn_termination_list: List[L2VPNTerminationTypeV1] = strawberry_django.field() + + tunnel: TunnelTypeV1 = strawberry_django.field() + tunnel_list: List[TunnelTypeV1] = strawberry_django.field() + + tunnel_group: TunnelGroupTypeV1 = strawberry_django.field() + tunnel_group_list: List[TunnelGroupTypeV1] = strawberry_django.field() + + tunnel_termination: TunnelTerminationTypeV1 = strawberry_django.field() + tunnel_termination_list: List[TunnelTerminationTypeV1] = strawberry_django.field() diff --git a/netbox/vpn/graphql/types_v1.py b/netbox/vpn/graphql/types_v1.py new file mode 100644 index 000000000..7812f88bb --- /dev/null +++ b/netbox/vpn/graphql/types_v1.py @@ -0,0 +1,157 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 +from netbox.graphql.types_v1 import ObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from vpn import models +from .filters_v1 import * + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import InterfaceTypeV1 + from ipam.graphql.types_v1 import IPAddressTypeV1, RouteTargetTypeV1, VLANTypeV1 + from netbox.graphql.types_v1 import ContentTypeTypeV1 + from tenancy.graphql.types_v1 import TenantTypeV1 + from virtualization.graphql.types_v1 import VMInterfaceTypeV1 + +__all__ = ( + 'IKEPolicyTypeV1', + 'IKEProposalTypeV1', + 'IPSecPolicyTypeV1', + 'IPSecProfileTypeV1', + 'IPSecProposalTypeV1', + 'L2VPNTypeV1', + 'L2VPNTerminationTypeV1', + 'TunnelGroupTypeV1', + 'TunnelTerminationTypeV1', + 'TunnelTypeV1', +) + + +@strawberry_django.type( + models.TunnelGroup, + fields='__all__', + filters=TunnelGroupFilterV1, + pagination=True +) +class TunnelGroupTypeV1(ContactsMixinV1, OrganizationalObjectTypeV1): + + tunnels: List[Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.TunnelTermination, + fields='__all__', + filters=TunnelTerminationFilterV1, + pagination=True +) +class TunnelTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): + tunnel: Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')] + termination_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None + outside_ip: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + + +@strawberry_django.type( + models.Tunnel, + fields='__all__', + filters=TunnelFilterV1, + pagination=True +) +class TunnelTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): + group: Annotated["TunnelGroupTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None + ipsec_profile: Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + terminations: List[Annotated["TunnelTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.IKEProposal, + fields='__all__', + filters=IKEProposalFilterV1, + pagination=True +) +class IKEProposalTypeV1(OrganizationalObjectTypeV1): + + ike_policies: List[Annotated["IKEPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.IKEPolicy, + fields='__all__', + filters=IKEPolicyFilterV1, + pagination=True +) +class IKEPolicyTypeV1(OrganizationalObjectTypeV1): + + proposals: List[Annotated["IKEProposalTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + ipsec_profiles: List[Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.IPSecProposal, + fields='__all__', + filters=IPSecProposalFilterV1, + pagination=True +) +class IPSecProposalTypeV1(OrganizationalObjectTypeV1): + + ipsec_policies: List[Annotated["IPSecPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.IPSecPolicy, + fields='__all__', + filters=IPSecPolicyFilterV1, + pagination=True +) +class IPSecPolicyTypeV1(OrganizationalObjectTypeV1): + + proposals: List[Annotated["IPSecProposalTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + ipsec_profiles: List[Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.IPSecProfile, + fields='__all__', + filters=IPSecProfileFilterV1, + pagination=True +) +class IPSecProfileTypeV1(OrganizationalObjectTypeV1): + ike_policy: Annotated["IKEPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')] + ipsec_policy: Annotated["IPSecPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')] + + tunnels: List[Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + + +@strawberry_django.type( + models.L2VPN, + fields='__all__', + filters=L2VPNFilterV1, + pagination=True +) +class L2VPNTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + export_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + terminations: List[Annotated["L2VPNTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] + import_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] + + +@strawberry_django.type( + models.L2VPNTermination, + exclude=['assigned_object_type', 'assigned_object_id'], + filters=L2VPNTerminationFilterV1, + pagination=True +) +class L2VPNTerminationTypeV1(NetBoxObjectTypeV1): + l2vpn: Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')] + + @strawberry_django.field + def assigned_object(self) -> Annotated[Union[ + Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')], + Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], + ], strawberry.union("L2VPNAssignmentTypeV1")]: + return self.assigned_object diff --git a/netbox/wireless/graphql/filter_mixins_v1.py b/netbox/wireless/graphql/filter_mixins_v1.py new file mode 100644 index 000000000..2e32f2cde --- /dev/null +++ b/netbox/wireless/graphql/filter_mixins_v1.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 + +if TYPE_CHECKING: + from .enums import * + +__all__ = ( + 'WirelessAuthenticationBaseFilterMixinV1', +) + + +@dataclass +class WirelessAuthenticationBaseFilterMixinV1(BaseFilterMixinV1): + auth_type: Annotated['WirelessAuthTypeEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_cipher: Annotated['WirelessAuthCipherEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_psk: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/wireless/graphql/filters_v1.py b/netbox/wireless/graphql/filters_v1.py new file mode 100644 index 000000000..166679843 --- /dev/null +++ b/netbox/wireless/graphql/filters_v1.py @@ -0,0 +1,72 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry +import strawberry_django +from strawberry.scalars import ID +from strawberry_django import FilterLookup + +from dcim.graphql.filter_mixins_v1 import ScopedFilterMixinV1 +from netbox.graphql.filter_mixins_v1 import ( + DistanceFilterMixinV1, PrimaryModelFilterMixinV1, NestedGroupModelFilterMixinV1 +) +from tenancy.graphql.filter_mixins_v1 import TenancyFilterMixinV1 +from wireless import models +from .filter_mixins_v1 import WirelessAuthenticationBaseFilterMixinV1 + +if TYPE_CHECKING: + from dcim.graphql.filters_v1 import InterfaceFilterV1 + from ipam.graphql.filters_v1 import VLANFilterV1 + from .enums import * + +__all__ = ( + 'WirelessLANGroupFilterV1', + 'WirelessLANFilterV1', + 'WirelessLinkFilterV1', +) + + +@strawberry_django.filter_type(models.WirelessLANGroup, lookups=True) +class WirelessLANGroupFilterV1(NestedGroupModelFilterMixinV1): + pass + + +@strawberry_django.filter_type(models.WirelessLAN, lookups=True) +class WirelessLANFilterV1( + WirelessAuthenticationBaseFilterMixinV1, + ScopedFilterMixinV1, + TenancyFilterMixinV1, + PrimaryModelFilterMixinV1 +): + ssid: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['WirelessLANGroupFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + vlan_id: ID | None = strawberry_django.filter_field() + + +@strawberry_django.filter_type(models.WirelessLink, lookups=True) +class WirelessLinkFilterV1( + WirelessAuthenticationBaseFilterMixinV1, + DistanceFilterMixinV1, + TenancyFilterMixinV1, + PrimaryModelFilterMixinV1 +): + interface_a: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interface_a_id: ID | None = strawberry_django.filter_field() + interface_b: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + interface_b_id: ID | None = strawberry_django.filter_field() + ssid: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/wireless/graphql/schema_v1.py b/netbox/wireless/graphql/schema_v1.py new file mode 100644 index 000000000..fafe28c08 --- /dev/null +++ b/netbox/wireless/graphql/schema_v1.py @@ -0,0 +1,18 @@ +from typing import List + +import strawberry +import strawberry_django + +from .types_v1 import * + + +@strawberry.type(name="Query") +class WirelessQueryV1: + wireless_lan: WirelessLANTypeV1 = strawberry_django.field() + wireless_lan_list: List[WirelessLANTypeV1] = strawberry_django.field() + + wireless_lan_group: WirelessLANGroupTypeV1 = strawberry_django.field() + wireless_lan_group_list: List[WirelessLANGroupTypeV1] = strawberry_django.field() + + wireless_link: WirelessLinkTypeV1 = strawberry_django.field() + wireless_link_list: List[WirelessLinkTypeV1] = strawberry_django.field() diff --git a/netbox/wireless/graphql/types_v1.py b/netbox/wireless/graphql/types_v1.py new file mode 100644 index 000000000..b774f8f28 --- /dev/null +++ b/netbox/wireless/graphql/types_v1.py @@ -0,0 +1,71 @@ +from typing import Annotated, List, TYPE_CHECKING, Union + +import strawberry +import strawberry_django + +from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from wireless import models +from .filters_v1 import * + +if TYPE_CHECKING: + from dcim.graphql.types_v1 import ( + DeviceTypeV1, InterfaceTypeV1, LocationTypeV1, RegionTypeV1, SiteGroupTypeV1, SiteTypeV1 + ) + from ipam.graphql.types_v1 import VLANTypeV1 + from tenancy.graphql.types_v1 import TenantTypeV1 + +__all__ = ( + 'WirelessLANTypeV1', + 'WirelessLANGroupTypeV1', + 'WirelessLinkTypeV1', +) + + +@strawberry_django.type( + models.WirelessLANGroup, + fields='__all__', + filters=WirelessLANGroupFilterV1, + pagination=True +) +class WirelessLANGroupTypeV1(OrganizationalObjectTypeV1): + parent: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None + + wireless_lans: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] + children: List[Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] + + +@strawberry_django.type( + models.WirelessLAN, + exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], + filters=WirelessLANFilterV1, + pagination=True +) +class WirelessLANTypeV1(NetBoxObjectTypeV1): + group: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None + vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + + interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] + + @strawberry_django.field + def scope(self) -> Annotated[Union[ + Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], + ], strawberry.union("WirelessLANScopeTypeV1")] | None: + return self.scope + + +@strawberry_django.type( + models.WirelessLink, + fields='__all__', + filters=WirelessLinkFilterV1, + pagination=True +) +class WirelessLinkTypeV1(NetBoxObjectTypeV1): + interface_a: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + interface_b: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] + tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None + _interface_a_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + _interface_b_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None From a718cb1173c5da5425f294c5d1a20882b0e1950c Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 14:33:34 -0400 Subject: [PATCH 02/15] Convert all id fields and enum fields to FilterLookups (with in_list and exact support) --- netbox/circuits/graphql/filters.py | 14 ++- netbox/core/graphql/filter_mixins.py | 6 +- netbox/dcim/graphql/filters.py | 152 ++++++++++++++--------- netbox/extras/graphql/filters.py | 30 +++-- netbox/ipam/graphql/filters.py | 20 +-- netbox/tenancy/graphql/filters.py | 2 +- netbox/virtualization/graphql/filters.py | 6 +- netbox/vpn/graphql/filters.py | 54 +++++--- netbox/wireless/graphql/filters.py | 4 +- 9 files changed, 183 insertions(+), 105 deletions(-) diff --git a/netbox/circuits/graphql/filters.py b/netbox/circuits/graphql/filters.py index d6ef2976d..545e76cb8 100644 --- a/netbox/circuits/graphql/filters.py +++ b/netbox/circuits/graphql/filters.py @@ -52,7 +52,9 @@ class CircuitTerminationFilter( circuit: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( strawberry_django.filter_field() ) - term_side: Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + term_side: ( + FilterLookup[Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) termination_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -108,7 +110,7 @@ class CircuitFilter( strawberry_django.filter_field() ) type_id: ID | None = strawberry_django.filter_field() - status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + status: FilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() @@ -143,7 +145,7 @@ class CircuitGroupAssignmentFilter( strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - priority: Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + priority: FilterLookup[Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -198,7 +200,7 @@ class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilterMixin): strawberry_django.filter_field() ) type_id: ID | None = strawberry_django.filter_field() - status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + status: FilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group_assignments: Annotated['CircuitGroupAssignmentFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( @@ -214,7 +216,9 @@ class VirtualCircuitTerminationFilter( strawberry_django.filter_field() ) virtual_circuit_id: ID | None = strawberry_django.filter_field() - role: Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + role: ( + FilterLookup[Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) interface: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( diff --git a/netbox/core/graphql/filter_mixins.py b/netbox/core/graphql/filter_mixins.py index 670ec2ebb..27d950d73 100644 --- a/netbox/core/graphql/filter_mixins.py +++ b/netbox/core/graphql/filter_mixins.py @@ -5,7 +5,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry import ID -from strawberry_django import DatetimeFilterLookup +from strawberry_django import FilterLookup, DatetimeFilterLookup if TYPE_CHECKING: from .filters import * @@ -23,12 +23,12 @@ class BaseFilterMixin: ... @dataclass class BaseObjectTypeFilterMixin(BaseFilterMixin): - id: ID | None = strawberry.UNSET + id: FilterLookup[ID] | None = strawberry_django.filter_field() @dataclass class ChangeLogFilterMixin(BaseFilterMixin): - id: ID | None = strawberry.UNSET + id: FilterLookup[ID] | None = strawberry_django.filter_field() changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 111902dd9..4ccb85580 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -97,14 +97,20 @@ __all__ = ( @strawberry_django.filter_type(models.Cable, lookups=True) class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin): - type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + status: FilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) label: FilterLookup[str] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + length_unit: FilterLookup[Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) terminations: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -116,7 +122,7 @@ class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin): class CableTerminationFilter(ChangeLogFilterMixin): cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() cable_id: ID | None = strawberry_django.filter_field() - cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + cable_end: FilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) termination_type: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -127,34 +133,34 @@ class CableTerminationFilter(ChangeLogFilterMixin): @strawberry_django.filter_type(models.ConsolePort, lookups=True) class ConsolePortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + speed: FilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True) class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsoleServerPort, lookups=True) class ConsoleServerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + speed: FilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True) class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -195,11 +201,13 @@ class DeviceFilter( position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + face: FilterLookup[Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + airflow: FilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -311,7 +319,9 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin): @strawberry_django.filter_type(models.DeviceRole, lookups=True) class DeviceRoleFilter(OrganizationalModelFilterMixin, RenderConfigFilterMixin): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) vm_role: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -336,10 +346,10 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig ) exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field() is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field() - subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + subdevice_role: FilterLookup[Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + airflow: FilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) front_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = ( @@ -393,8 +403,12 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig @strawberry_django.filter_type(models.FrontPort, lookups=True) class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -406,8 +420,12 @@ class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM @strawberry_django.filter_type(models.FrontPortTemplate, lookups=True) class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) rear_port: Annotated['RearPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -451,14 +469,14 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin ) lag: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() lag_id: ID | None = strawberry_django.filter_field() - type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + duplex: FilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) wwn: FilterLookup[str] | None = strawberry_django.filter_field() @@ -466,10 +484,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin strawberry_django.filter_field() ) parent_id: ID | None = strawberry_django.filter_field() - rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + rf_role: FilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + rf_channel: FilterLookup[Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -481,10 +499,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + poe_mode: FilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + poe_type: FilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) wireless_link: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( @@ -536,7 +554,7 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin @strawberry_django.filter_type(models.InterfaceTemplate, lookups=True) class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) enabled: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -545,13 +563,13 @@ class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): strawberry_django.filter_field() ) bridge_id: ID | None = strawberry_django.filter_field() - poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + poe_mode: FilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + poe_type: FilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + rf_role: FilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -566,7 +584,7 @@ class InventoryItemFilter(ComponentModelFilterMixin): strawberry_django.filter_field() ) component_id: ID | None = strawberry_django.filter_field() - status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -585,14 +603,16 @@ class InventoryItemFilter(ComponentModelFilterMixin): @strawberry_django.filter_type(models.InventoryItemRole, lookups=True) class InventoryItemRoleFilter(OrganizationalModelFilterMixin): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.Location, lookups=True) class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, NestedGroupModelFilterMixin): site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() site_id: ID | None = strawberry_django.filter_field() - status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) facility: FilterLookup[str] | None = strawberry_django.filter_field() @@ -621,7 +641,7 @@ class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin): strawberry_django.filter_field() ) module_type_id: ID | None = strawberry_django.filter_field() - status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) serial: FilterLookup[str] | None = strawberry_django.filter_field() @@ -692,7 +712,7 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) - airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + airflow: FilterLookup[Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) console_port_templates: ( @@ -749,16 +769,16 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() rack_id: ID | None = strawberry_django.filter_field() name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + supply: FilterLookup[Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + phase: FilterLookup[Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -777,29 +797,31 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM @strawberry_django.filter_type(models.PowerOutlet, lookups=True) class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) power_port: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + feed_leg: FilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True) class PowerOutletTemplateFilter(ModularComponentModelFilterMixin): - type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) power_port: Annotated['PowerPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + feed_leg: FilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -819,7 +841,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo @strawberry_django.filter_type(models.PowerPort, lookups=True) class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -832,7 +854,7 @@ class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM @strawberry_django.filter_type(models.PowerPortTemplate, lookups=True) class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + type: FilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -845,7 +867,7 @@ class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin): @strawberry_django.filter_type(models.RackType, lookups=True) class RackTypeFilter(RackBaseFilterMixin): - form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + form_factor: FilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -860,7 +882,7 @@ class RackTypeFilter(RackBaseFilterMixin): @strawberry_django.filter_type(models.Rack, lookups=True) class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, RackBaseFilterMixin): - form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + form_factor: FilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rack_type: Annotated['RackTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -877,12 +899,14 @@ class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: FilterLookup[Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() role_id: ID | None = strawberry_django.filter_field() serial: FilterLookup[str] | None = strawberry_django.filter_field() asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + airflow: FilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -904,13 +928,19 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.RackRole, lookups=True) class RackRoleFilter(OrganizationalModelFilterMixin): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.RearPort, lookups=True) class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -918,8 +948,12 @@ class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMi @strawberry_django.filter_type(models.RearPortTemplate, lookups=True) class RearPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -939,7 +973,9 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin): class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() slug: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: FilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() @@ -996,7 +1032,7 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilterMixin): device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() device_id: ID | None = strawberry_django.filter_field() name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + status: FilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index 13221982c..75aaac955 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -121,7 +121,7 @@ class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Cha @strawberry_django.filter_type(models.CustomField, lookups=True) class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): - type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + type: FilterLookup[Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) object_types: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -139,7 +139,9 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + filter_logic: ( + FilterLookup[Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) default: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -162,10 +164,12 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): strawberry_django.filter_field() ) choice_set_id: ID | None = strawberry_django.filter_field() - ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + ui_visible: FilterLookup[Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + ui_editable: ( + FilterLookup[Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -176,7 +180,9 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() description: FilterLookup[str] | None = strawberry_django.filter_field() - base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + base_choices: ( + FilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -195,7 +201,9 @@ class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): strawberry_django.filter_field() ) group_name: FilterLookup[str] | None = strawberry_django.filter_field() - button_class: Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + button_class: ( + FilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) new_window: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -240,7 +248,7 @@ class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, Tag created_by: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = ( strawberry_django.filter_field() ) - kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + kind: FilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) comments: FilterLookup[str] | None = strawberry_django.filter_field() @@ -286,7 +294,9 @@ class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): @strawberry_django.filter_type(models.Tag, lookups=True) class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) description: FilterLookup[str] | None = strawberry_django.filter_field() @@ -295,7 +305,7 @@ class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilt name: FilterLookup[str] | None = strawberry_django.filter_field() description: FilterLookup[str] | None = strawberry_django.filter_field() payload_url: FilterLookup[str] | None = strawberry_django.filter_field() - http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + http_method: FilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) http_content_type: FilterLookup[str] | None = strawberry_django.filter_field() @@ -320,7 +330,7 @@ class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFi conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + action_type: FilterLookup[Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) action_object_type: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py index c0a01d72b..ce22c16ed 100644 --- a/netbox/ipam/graphql/filters.py +++ b/netbox/ipam/graphql/filters.py @@ -116,10 +116,10 @@ class FHRPGroupFilter(PrimaryModelFilterMixin): strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + protocol: FilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + auth_type: FilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) auth_key: FilterLookup[str] | None = strawberry_django.filter_field() @@ -172,10 +172,10 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter address: FilterLookup[str] | None = strawberry_django.filter_field() vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: FilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + role: FilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -212,7 +212,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter @strawberry_django.filter_field() def family( self, - value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], + value: FilterLookup[Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')]], prefix, ) -> Q: return Q(**{f"{prefix}address__family": value.value}) @@ -227,7 +227,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi ) vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: FilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -279,7 +279,7 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr vrf_id: ID | None = strawberry_django.filter_field() vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vlan_id: ID | None = strawberry_django.filter_field() - status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: FilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -367,7 +367,9 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field() + status: FilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() role_id: ID | None = strawberry_django.filter_field() qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -377,7 +379,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): qinq_cvlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) - qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + qinq_role: FilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( diff --git a/netbox/tenancy/graphql/filters.py b/netbox/tenancy/graphql/filters.py index fb37359ef..08682c442 100644 --- a/netbox/tenancy/graphql/filters.py +++ b/netbox/tenancy/graphql/filters.py @@ -191,6 +191,6 @@ class ContactAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLo strawberry_django.filter_field() ) role_id: ID | None = strawberry_django.filter_field() - priority: Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')] | None = ( + priority: FilterLookup[Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')]] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/virtualization/graphql/filters.py b/netbox/virtualization/graphql/filters.py index a10ade5a2..7ee8d9053 100644 --- a/netbox/virtualization/graphql/filters.py +++ b/netbox/virtualization/graphql/filters.py @@ -50,7 +50,7 @@ class ClusterFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, P strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - status: Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + status: FilterLookup[Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None = ( strawberry_django.filter_field() ) vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -92,7 +92,9 @@ class VirtualMachineFilter( strawberry_django.filter_field() ) platform_id: ID | None = strawberry_django.filter_field() - status: Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + status: ( + FilterLookup[Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) role: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( diff --git a/netbox/vpn/graphql/filters.py b/netbox/vpn/graphql/filters.py index 21adcd100..d7bd423ef 100644 --- a/netbox/vpn/graphql/filters.py +++ b/netbox/vpn/graphql/filters.py @@ -42,10 +42,12 @@ class TunnelTerminationFilter( ): tunnel: Annotated['TunnelFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() tunnel_id: ID | None = strawberry_django.filter_field() - role: Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + role: FilterLookup[Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - termination_type: Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + termination_type: ( + FilterLookup[Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) termination_type_id: ID | None = strawberry_django.filter_field() @@ -59,14 +61,14 @@ class TunnelTerminationFilter( @strawberry_django.filter_type(models.Tunnel, lookups=True) class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + status: FilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group: Annotated['TunnelGroupFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - encapsulation: Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + encapsulation: FilterLookup[Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( @@ -83,16 +85,24 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.IKEProposal, lookups=True) class IKEProposalFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - authentication_method: Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + authentication_method: ( + FilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) - encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + encryption_algorithm: ( + FilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) - authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + authentication_algorithm: ( + FilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( + strawberry_django.filter_field() + ) + group: FilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -104,8 +114,12 @@ class IKEProposalFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.IKEPolicy, lookups=True) class IKEPolicyFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - version: Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - mode: Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + version: FilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) + mode: FilterLookup[Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -115,10 +129,14 @@ class IKEPolicyFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.IPSecProposal, lookups=True) class IPSecProposalFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + encryption_algorithm: ( + FilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) - authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + authentication_algorithm: FilterLookup[ + Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] + ] | None = ( strawberry_django.filter_field() ) sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -138,13 +156,17 @@ class IPSecPolicyFilter(PrimaryModelFilterMixin): proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) - pfs_group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + pfs_group: FilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.IPSecProfile, lookups=True) class IPSecProfileFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - mode: Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + mode: FilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) ike_policy: Annotated['IKEPolicyFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -159,7 +181,9 @@ class IPSecProfileFilter(PrimaryModelFilterMixin): class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() slug: FilterLookup[str] | None = strawberry_django.filter_field() - type: Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + type: FilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/wireless/graphql/filters.py b/netbox/wireless/graphql/filters.py index 5bd22afab..bcf10a7b4 100644 --- a/netbox/wireless/graphql/filters.py +++ b/netbox/wireless/graphql/filters.py @@ -36,7 +36,7 @@ class WirelessLANFilter( PrimaryModelFilterMixin ): ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + status: FilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group: Annotated['WirelessLANGroupFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( @@ -63,6 +63,6 @@ class WirelessLinkFilter( ) interface_b_id: ID | None = strawberry_django.filter_field() ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + status: FilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) From c7d94bd5291aba68299d191243bde3e77c1c28cc Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 17:47:34 -0400 Subject: [PATCH 03/15] Change usages of FilterLookup to BaseFilterLookup --- netbox/circuits/graphql/filters.py | 14 +-- netbox/dcim/graphql/filters.py | 120 ++++++++++++----------- netbox/extras/graphql/filters.py | 26 ++--- netbox/ipam/graphql/filters.py | 20 ++-- netbox/tenancy/graphql/filters.py | 4 +- netbox/virtualization/graphql/filters.py | 6 +- netbox/vpn/graphql/filters.py | 40 ++++---- netbox/wireless/graphql/filters.py | 6 +- 8 files changed, 124 insertions(+), 112 deletions(-) diff --git a/netbox/circuits/graphql/filters.py b/netbox/circuits/graphql/filters.py index 545e76cb8..25cb8092d 100644 --- a/netbox/circuits/graphql/filters.py +++ b/netbox/circuits/graphql/filters.py @@ -4,7 +4,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup, DateFilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup from circuits import models from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin @@ -53,7 +53,7 @@ class CircuitTerminationFilter( strawberry_django.filter_field() ) term_side: ( - FilterLookup[Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')]] | None + BaseFilterLookup[Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -110,7 +110,7 @@ class CircuitFilter( strawberry_django.filter_field() ) type_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() @@ -145,7 +145,7 @@ class CircuitGroupAssignmentFilter( strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - priority: FilterLookup[Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( + priority: BaseFilterLookup[Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -200,7 +200,7 @@ class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilterMixin): strawberry_django.filter_field() ) type_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group_assignments: Annotated['CircuitGroupAssignmentFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( @@ -217,7 +217,9 @@ class VirtualCircuitTerminationFilter( ) virtual_circuit_id: ID | None = strawberry_django.filter_field() role: ( - FilterLookup[Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')]] | None + BaseFilterLookup[ + Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] + ] | None ) = ( strawberry_django.filter_field() ) diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 4ccb85580..de156b55b 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -4,7 +4,7 @@ from django.db.models import Q import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import ComparisonFilterLookup, FilterLookup +from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, FilterLookup from core.graphql.filter_mixins import ChangeLogFilterMixin from dcim import models @@ -97,20 +97,20 @@ __all__ = ( @strawberry_django.filter_type(models.Cable, lookups=True) class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin): - type: FilterLookup[Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - status: FilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) label: FilterLookup[str] | None = strawberry_django.filter_field() - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - length_unit: FilterLookup[Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + length_unit: BaseFilterLookup[Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) terminations: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -122,7 +122,7 @@ class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin): class CableTerminationFilter(ChangeLogFilterMixin): cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() cable_id: ID | None = strawberry_django.filter_field() - cable_end: FilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + cable_end: BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) termination_type: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -133,34 +133,34 @@ class CableTerminationFilter(ChangeLogFilterMixin): @strawberry_django.filter_type(models.ConsolePort, lookups=True) class ConsolePortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - speed: FilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True) class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsoleServerPort, lookups=True) class ConsoleServerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - speed: FilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True) class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -201,13 +201,13 @@ class DeviceFilter( position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - face: FilterLookup[Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + face: BaseFilterLookup[Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - status: FilterLookup[Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - airflow: FilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -319,7 +319,7 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin): @strawberry_django.filter_type(models.DeviceRole, lookups=True) class DeviceRoleFilter(OrganizationalModelFilterMixin, RenderConfigFilterMixin): - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) vm_role: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -346,10 +346,10 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig ) exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field() is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field() - subdevice_role: FilterLookup[Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + subdevice_role: BaseFilterLookup[Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - airflow: FilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) front_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = ( @@ -403,10 +403,10 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig @strawberry_django.filter_type(models.FrontPort, lookups=True) class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -420,10 +420,10 @@ class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM @strawberry_django.filter_type(models.FrontPortTemplate, lookups=True) class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rear_port: Annotated['RearPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -469,14 +469,14 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin ) lag: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() lag_id: ID | None = strawberry_django.filter_field() - type: FilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - duplex: FilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) wwn: FilterLookup[str] | None = strawberry_django.filter_field() @@ -484,10 +484,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin strawberry_django.filter_field() ) parent_id: ID | None = strawberry_django.filter_field() - rf_role: FilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( + rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - rf_channel: FilterLookup[Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( + rf_channel: BaseFilterLookup[Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -499,10 +499,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - poe_mode: FilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - poe_type: FilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) wireless_link: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( @@ -554,7 +554,7 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin @strawberry_django.filter_type(models.InterfaceTemplate, lookups=True) class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) enabled: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -563,13 +563,13 @@ class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): strawberry_django.filter_field() ) bridge_id: ID | None = strawberry_django.filter_field() - poe_mode: FilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - poe_type: FilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - rf_role: FilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( + rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -584,7 +584,7 @@ class InventoryItemFilter(ComponentModelFilterMixin): strawberry_django.filter_field() ) component_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -603,7 +603,7 @@ class InventoryItemFilter(ComponentModelFilterMixin): @strawberry_django.filter_type(models.InventoryItemRole, lookups=True) class InventoryItemRoleFilter(OrganizationalModelFilterMixin): - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -612,7 +612,7 @@ class InventoryItemRoleFilter(OrganizationalModelFilterMixin): class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, NestedGroupModelFilterMixin): site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() site_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) facility: FilterLookup[str] | None = strawberry_django.filter_field() @@ -641,7 +641,7 @@ class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin): strawberry_django.filter_field() ) module_type_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) serial: FilterLookup[str] | None = strawberry_django.filter_field() @@ -712,7 +712,7 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) - airflow: FilterLookup[Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + airflow: BaseFilterLookup[Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) console_port_templates: ( @@ -769,16 +769,16 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() rack_id: ID | None = strawberry_django.filter_field() name: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - type: FilterLookup[Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - supply: FilterLookup[Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + supply: BaseFilterLookup[Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - phase: FilterLookup[Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + phase: BaseFilterLookup[Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -797,31 +797,31 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM @strawberry_django.filter_type(models.PowerOutlet, lookups=True) class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) power_port: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: FilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True) class PowerOutletTemplateFilter(ModularComponentModelFilterMixin): - type: FilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) power_port: Annotated['PowerPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: FilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -841,7 +841,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo @strawberry_django.filter_type(models.PowerPort, lookups=True) class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -854,7 +854,7 @@ class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM @strawberry_django.filter_type(models.PowerPortTemplate, lookups=True) class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -867,7 +867,7 @@ class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin): @strawberry_django.filter_type(models.RackType, lookups=True) class RackTypeFilter(RackBaseFilterMixin): - form_factor: FilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -882,7 +882,7 @@ class RackTypeFilter(RackBaseFilterMixin): @strawberry_django.filter_type(models.Rack, lookups=True) class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, RackBaseFilterMixin): - form_factor: FilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) rack_type: Annotated['RackTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( @@ -899,14 +899,14 @@ class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - status: FilterLookup[Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() role_id: ID | None = strawberry_django.filter_field() serial: FilterLookup[str] | None = strawberry_django.filter_field() asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - airflow: FilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -928,17 +928,17 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.RackRole, lookups=True) class RackRoleFilter(OrganizationalModelFilterMixin): - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.RearPort, lookups=True) class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): - type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -948,10 +948,10 @@ class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMi @strawberry_django.filter_type(models.RearPortTemplate, lookups=True) class RearPortTemplateFilter(ModularComponentTemplateFilterMixin): - type: FilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -973,7 +973,7 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin): class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() slug: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() @@ -1032,7 +1032,9 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilterMixin): device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() device_id: ID | None = strawberry_django.filter_field() name: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + status: ( + BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index 75aaac955..724c53421 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin from extras import models @@ -121,7 +121,7 @@ class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Cha @strawberry_django.filter_type(models.CustomField, lookups=True) class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): - type: FilterLookup[Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) object_types: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -140,7 +140,7 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): strawberry_django.filter_field() ) filter_logic: ( - FilterLookup[Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')]] | None + BaseFilterLookup[Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -164,11 +164,13 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): strawberry_django.filter_field() ) choice_set_id: ID | None = strawberry_django.filter_field() - ui_visible: FilterLookup[Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( + ui_visible: ( + BaseFilterLookup[Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) ui_editable: ( - FilterLookup[Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')]] | None + BaseFilterLookup[Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -181,7 +183,7 @@ class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin name: FilterLookup[str] | None = strawberry_django.filter_field() description: FilterLookup[str] | None = strawberry_django.filter_field() base_choices: ( - FilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None + BaseFilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -202,7 +204,7 @@ class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): ) group_name: FilterLookup[str] | None = strawberry_django.filter_field() button_class: ( - FilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None + BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -248,7 +250,7 @@ class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, Tag created_by: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = ( strawberry_django.filter_field() ) - kind: FilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( + kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) comments: FilterLookup[str] | None = strawberry_django.filter_field() @@ -294,7 +296,7 @@ class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): @strawberry_django.filter_type(models.Tag, lookups=True) class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin): - color: FilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( + color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) description: FilterLookup[str] | None = strawberry_django.filter_field() @@ -305,7 +307,9 @@ class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilt name: FilterLookup[str] | None = strawberry_django.filter_field() description: FilterLookup[str] | None = strawberry_django.filter_field() payload_url: FilterLookup[str] | None = strawberry_django.filter_field() - http_method: FilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( + http_method: ( + BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) http_content_type: FilterLookup[str] | None = strawberry_django.filter_field() @@ -330,7 +334,7 @@ class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFi conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - action_type: FilterLookup[Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( + action_type: BaseFilterLookup[Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')]] | None = ( strawberry_django.filter_field() ) action_object_type: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py index ce22c16ed..67d7a723d 100644 --- a/netbox/ipam/graphql/filters.py +++ b/netbox/ipam/graphql/filters.py @@ -7,7 +7,7 @@ import strawberry_django from django.db.models import Q from netaddr.core import AddrFormatError from strawberry.scalars import ID -from strawberry_django import FilterLookup, DateFilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin from dcim.graphql.filter_mixins import ScopedFilterMixin @@ -116,10 +116,10 @@ class FHRPGroupFilter(PrimaryModelFilterMixin): strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - protocol: FilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - auth_type: FilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) auth_key: FilterLookup[str] | None = strawberry_django.filter_field() @@ -172,10 +172,10 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter address: FilterLookup[str] | None = strawberry_django.filter_field() vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - role: FilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + role: BaseFilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -212,7 +212,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter @strawberry_django.filter_field() def family( self, - value: FilterLookup[Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')]], + value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], prefix, ) -> Q: return Q(**{f"{prefix}address__family": value.value}) @@ -227,7 +227,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi ) vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -279,7 +279,7 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr vrf_id: ID | None = strawberry_django.filter_field() vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vlan_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -367,7 +367,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -379,7 +379,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): qinq_cvlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) - qinq_role: FilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + qinq_role: BaseFilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( diff --git a/netbox/tenancy/graphql/filters.py b/netbox/tenancy/graphql/filters.py index 08682c442..dbdbfd750 100644 --- a/netbox/tenancy/graphql/filters.py +++ b/netbox/tenancy/graphql/filters.py @@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from core.graphql.filter_mixins import ChangeLogFilterMixin from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin @@ -191,6 +191,6 @@ class ContactAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLo strawberry_django.filter_field() ) role_id: ID | None = strawberry_django.filter_field() - priority: FilterLookup[Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')]] | None = ( + priority: BaseFilterLookup[Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')]] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/virtualization/graphql/filters.py b/netbox/virtualization/graphql/filters.py index 7ee8d9053..5b29e362a 100644 --- a/netbox/virtualization/graphql/filters.py +++ b/netbox/virtualization/graphql/filters.py @@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from dcim.graphql.filter_mixins import InterfaceBaseFilterMixin, RenderConfigFilterMixin, ScopedFilterMixin from extras.graphql.filter_mixins import ConfigContextFilterMixin @@ -50,7 +50,7 @@ class ClusterFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, P strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None = ( strawberry_django.filter_field() ) vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -93,7 +93,7 @@ class VirtualMachineFilter( ) platform_id: ID | None = strawberry_django.filter_field() status: ( - FilterLookup[Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None + BaseFilterLookup[Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) diff --git a/netbox/vpn/graphql/filters.py b/netbox/vpn/graphql/filters.py index d7bd423ef..7955550c9 100644 --- a/netbox/vpn/graphql/filters.py +++ b/netbox/vpn/graphql/filters.py @@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin @@ -42,11 +42,11 @@ class TunnelTerminationFilter( ): tunnel: Annotated['TunnelFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() tunnel_id: ID | None = strawberry_django.filter_field() - role: FilterLookup[Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + role: BaseFilterLookup[Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) termination_type: ( - FilterLookup[Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None + BaseFilterLookup[Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) @@ -61,14 +61,16 @@ class TunnelTerminationFilter( @strawberry_django.filter_type(models.Tunnel, lookups=True) class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group: Annotated['TunnelGroupFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) group_id: ID | None = strawberry_django.filter_field() - encapsulation: FilterLookup[Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + encapsulation: ( + BaseFilterLookup[Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')]] | None + ) = ( strawberry_django.filter_field() ) ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( @@ -86,21 +88,21 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin): class IKEProposalFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() authentication_method: ( - FilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None + BaseFilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) encryption_algorithm: ( - FilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) authentication_algorithm: ( - FilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + BaseFilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) - group: FilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + group: BaseFilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -114,10 +116,10 @@ class IKEProposalFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.IKEPolicy, lookups=True) class IKEPolicyFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - version: FilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + version: BaseFilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - mode: FilterLookup[Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + mode: BaseFilterLookup[Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( @@ -130,13 +132,15 @@ class IKEPolicyFilter(PrimaryModelFilterMixin): class IPSecProposalFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() encryption_algorithm: ( - FilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None + BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None ) = ( strawberry_django.filter_field() ) - authentication_algorithm: FilterLookup[ - Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] - ] | None = ( + authentication_algorithm: ( + BaseFilterLookup[ + BaseFilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] + ] | None + ) = ( strawberry_django.filter_field() ) sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -156,7 +160,7 @@ class IPSecPolicyFilter(PrimaryModelFilterMixin): proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) - pfs_group: FilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + pfs_group: BaseFilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -164,7 +168,7 @@ class IPSecPolicyFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.IPSecProfile, lookups=True) class IPSecProfileFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() - mode: FilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + mode: BaseFilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) ike_policy: Annotated['IKEPolicyFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( @@ -181,7 +185,7 @@ class IPSecProfileFilter(PrimaryModelFilterMixin): class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() slug: FilterLookup[str] | None = strawberry_django.filter_field() - type: FilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( strawberry_django.filter_field() ) identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( diff --git a/netbox/wireless/graphql/filters.py b/netbox/wireless/graphql/filters.py index bcf10a7b4..342e078ed 100644 --- a/netbox/wireless/graphql/filters.py +++ b/netbox/wireless/graphql/filters.py @@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry.scalars import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from dcim.graphql.filter_mixins import ScopedFilterMixin from netbox.graphql.filter_mixins import DistanceFilterMixin, PrimaryModelFilterMixin, NestedGroupModelFilterMixin @@ -36,7 +36,7 @@ class WirelessLANFilter( PrimaryModelFilterMixin ): ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) group: Annotated['WirelessLANGroupFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( @@ -63,6 +63,6 @@ class WirelessLinkFilter( ) interface_b_id: ID | None = strawberry_django.filter_field() ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( + status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = ( strawberry_django.filter_field() ) From d192c1e352deb9d28d053885bf0e4b2d2836b2aa Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 18:34:07 -0400 Subject: [PATCH 04/15] Merge feature --- netbox/dcim/graphql/types_v1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/graphql/types_v1.py b/netbox/dcim/graphql/types_v1.py index c7f069ecb..53f89087a 100644 --- a/netbox/dcim/graphql/types_v1.py +++ b/netbox/dcim/graphql/types_v1.py @@ -677,6 +677,7 @@ class PowerOutletTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpoin ) class PowerOutletTemplateTypeV1(ModularComponentTemplateTypeV1): power_port: Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None + color: str @strawberry_django.type( From 3e1ccc80e988b399a96aa9af5c48f8f15cf5cc85 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 20:55:47 -0400 Subject: [PATCH 05/15] Set GRAPHQL_DEFAULT_VERSION = 2 in testing environment --- netbox/netbox/configuration_testing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 3e552e944..69ced660d 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -51,3 +51,5 @@ LOGGING = { 'version': 1, 'disable_existing_loggers': True } + +GRAPHQL_DEFAULT_VERSION = 2 From ebeceaaa217dcd27031c91c51a902dbe36826e27 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 21:18:51 -0400 Subject: [PATCH 06/15] Integrate Owner and JournalEntries fields --- netbox/netbox/configuration_testing.py | 3 ++- netbox/netbox/graphql/types_v1.py | 35 +++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 69ced660d..6256af00a 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -52,4 +52,5 @@ LOGGING = { 'disable_existing_loggers': True } -GRAPHQL_DEFAULT_VERSION = 2 +# TODO: Switch to 2 +GRAPHQL_DEFAULT_VERSION = 1 diff --git a/netbox/netbox/graphql/types_v1.py b/netbox/netbox/graphql/types_v1.py index c6b3ad2ec..66a3ac905 100644 --- a/netbox/netbox/graphql/types_v1.py +++ b/netbox/netbox/graphql/types_v1.py @@ -6,13 +6,16 @@ from django.contrib.contenttypes.models import ContentType from core.graphql.mixins import ChangelogMixin from core.models import ObjectType as ObjectType_ from extras.graphql.mixins import CustomFieldsMixin, JournalEntriesMixin, TagsMixin +from users.graphql.mixins import OwnerMixin __all__ = ( 'BaseObjectTypeV1', 'ContentTypeTypeV1', + 'NestedGroupObjectTypeV1', + 'NetBoxObjectTypeV1', 'ObjectTypeV1', 'OrganizationalObjectTypeV1', - 'NetBoxObjectTypeV1', + 'PrimaryObjectTypeV1', ) @@ -53,10 +56,26 @@ class ObjectTypeV1( pass +class PrimaryObjectTypeV1( + ChangelogMixin, + CustomFieldsMixin, + JournalEntriesMixin, + TagsMixin, + OwnerMixin, + BaseObjectTypeV1 +): + """ + Base GraphQL type for models which inherit from PrimaryModel. + """ + pass + + class OrganizationalObjectTypeV1( ChangelogMixin, CustomFieldsMixin, + JournalEntriesMixin, TagsMixin, + OwnerMixin, BaseObjectTypeV1 ): """ @@ -65,6 +84,20 @@ class OrganizationalObjectTypeV1( pass +class NestedGroupObjectTypeV1( + ChangelogMixin, + CustomFieldsMixin, + JournalEntriesMixin, + TagsMixin, + OwnerMixin, + BaseObjectTypeV1 +): + """ + Base GraphQL type for models which inherit from NestedGroupModel. + """ + pass + + class NetBoxObjectTypeV1( ChangelogMixin, CustomFieldsMixin, From db3a4bc731e2dd1721c388be390ef8670c436fd8 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 24 Oct 2025 23:11:35 -0400 Subject: [PATCH 07/15] Incorporate Owner fields/types into V1 classes --- netbox/circuits/graphql/types_v1.py | 14 ++++--- netbox/core/graphql/types_v1.py | 5 +-- netbox/dcim/graphql/types_v1.py | 44 ++++++++++------------ netbox/extras/graphql/types_v1.py | 21 ++++++----- netbox/ipam/graphql/types_v1.py | 31 +++++++-------- netbox/netbox/graphql/types_v1.py | 46 +++++++++++------------ netbox/tenancy/graphql/types_v1.py | 8 ++-- netbox/users/graphql/filters_v1.py | 23 ++++++++++++ netbox/users/graphql/mixins_v1.py | 15 ++++++++ netbox/users/graphql/schema_v1.py | 6 +++ netbox/users/graphql/types_v1.py | 24 +++++++++++- netbox/virtualization/graphql/types_v1.py | 9 +++-- netbox/vpn/graphql/types_v1.py | 11 +++--- netbox/wireless/graphql/types_v1.py | 8 ++-- 14 files changed, 166 insertions(+), 99 deletions(-) create mode 100644 netbox/users/graphql/mixins_v1.py diff --git a/netbox/circuits/graphql/types_v1.py b/netbox/circuits/graphql/types_v1.py index 3e7669df6..c9f9234dc 100644 --- a/netbox/circuits/graphql/types_v1.py +++ b/netbox/circuits/graphql/types_v1.py @@ -6,7 +6,9 @@ import strawberry_django from circuits import models from dcim.graphql.mixins_v1 import CabledObjectMixinV1 from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 -from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1 +from netbox.graphql.types_v1 import ( + BaseObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 +) from tenancy.graphql.types_v1 import TenantTypeV1 from .filters_v1 import * @@ -35,7 +37,7 @@ __all__ = ( filters=ProviderFilterV1, pagination=True ) -class ProviderTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class ProviderTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): networks: List[Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] @@ -49,7 +51,7 @@ class ProviderTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): filters=ProviderAccountFilterV1, pagination=True ) -class ProviderAccountTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): +class ProviderAccountTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] @@ -61,7 +63,7 @@ class ProviderAccountTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): filters=ProviderNetworkFilterV1, pagination=True ) -class ProviderNetworkTypeV1(NetBoxObjectTypeV1): +class ProviderNetworkTypeV1(PrimaryObjectTypeV1): provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] circuit_terminations: List[Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] @@ -105,7 +107,7 @@ class CircuitTypeTypeV1(OrganizationalObjectTypeV1): filters=CircuitFilterV1, pagination=True ) -class CircuitTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class CircuitTypeV1(PrimaryObjectTypeV1, ContactsMixinV1): provider: ProviderTypeV1 provider_account: ProviderAccountTypeV1 | None termination_a: CircuitTerminationTypeV1 | None @@ -178,7 +180,7 @@ class VirtualCircuitTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTy filters=VirtualCircuitFilterV1, pagination=True ) -class VirtualCircuitTypeV1(NetBoxObjectTypeV1): +class VirtualCircuitTypeV1(PrimaryObjectTypeV1): provider_network: ProviderNetworkTypeV1 = strawberry_django.field(select_related=["provider_network"]) provider_account: ProviderAccountTypeV1 | None type: Annotated["VirtualCircuitTypeTypeV1", strawberry.lazy('circuits.graphql.types_v1')] = strawberry_django.field( diff --git a/netbox/core/graphql/types_v1.py b/netbox/core/graphql/types_v1.py index 67effeae4..7dc8c0e3b 100644 --- a/netbox/core/graphql/types_v1.py +++ b/netbox/core/graphql/types_v1.py @@ -5,7 +5,7 @@ import strawberry_django from django.contrib.contenttypes.models import ContentType as DjangoContentType from core import models -from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1 +from netbox.graphql.types_v1 import BaseObjectTypeV1, PrimaryObjectTypeV1 from .filters_v1 import * __all__ = ( @@ -32,8 +32,7 @@ class DataFileTypeV1(BaseObjectTypeV1): filters=DataSourceFilterV1, pagination=True ) -class DataSourceTypeV1(NetBoxObjectTypeV1): - +class DataSourceTypeV1(PrimaryObjectTypeV1): datafiles: List[Annotated["DataFileTypeV1", strawberry.lazy('core.graphql.types_v1')]] diff --git a/netbox/dcim/graphql/types_v1.py b/netbox/dcim/graphql/types_v1.py index 53f89087a..ab0ba5f07 100644 --- a/netbox/dcim/graphql/types_v1.py +++ b/netbox/dcim/graphql/types_v1.py @@ -8,13 +8,14 @@ from dcim import models from extras.graphql.mixins_v1 import ( ConfigContextMixinV1, ContactsMixinV1, - CustomFieldsMixinV1, ImageAttachmentsMixinV1, - TagsMixinV1, ) from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1 +from netbox.graphql.types_v1 import ( + BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 +) +from users.graphql.mixins_v1 import OwnerMixinV1 from .filters_v1 import * from .mixins_v1 import CabledObjectMixinV1, PathEndpointMixinV1 @@ -91,12 +92,7 @@ __all__ = ( @strawberry.type -class ComponentTypeV1( - ChangelogMixinV1, - CustomFieldsMixinV1, - TagsMixinV1, - BaseObjectTypeV1 -): +class ComponentTypeV1(OwnerMixinV1, NetBoxObjectTypeV1): """ Base type for device/VM components """ @@ -159,7 +155,7 @@ class CableTerminationTypeV1(NetBoxObjectTypeV1): filters=CableFilterV1, pagination=True ) -class CableTypeV1(NetBoxObjectTypeV1): +class CableTypeV1(PrimaryObjectTypeV1): color: str tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -236,7 +232,7 @@ class ConsoleServerPortTemplateTypeV1(ModularComponentTemplateTypeV1): filters=DeviceFilterV1, pagination=True ) -class DeviceTypeV1(ConfigContextMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): +class DeviceTypeV1(ConfigContextMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): console_port_count: BigInt console_server_port_count: BigInt power_port_count: BigInt @@ -355,7 +351,7 @@ class DeviceRoleTypeV1(OrganizationalObjectTypeV1): filters=DeviceTypeFilterV1, pagination=True ) -class DeviceTypeTypeV1(NetBoxObjectTypeV1): +class DeviceTypeTypeV1(PrimaryObjectTypeV1): console_port_template_count: BigInt console_server_port_template_count: BigInt power_port_template_count: BigInt @@ -414,7 +410,7 @@ class FrontPortTemplateTypeV1(ModularComponentTemplateTypeV1): filters=MACAddressFilterV1, pagination=True ) -class MACAddressTypeV1(NetBoxObjectTypeV1): +class MACAddressTypeV1(PrimaryObjectTypeV1): mac_address: str @strawberry_django.field @@ -557,7 +553,7 @@ class ManufacturerTypeV1(OrganizationalObjectTypeV1, ContactsMixinV1): filters=ModuleFilterV1, pagination=True ) -class ModuleTypeV1(NetBoxObjectTypeV1): +class ModuleTypeV1(PrimaryObjectTypeV1): device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] module_bay: Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] module_type: Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] @@ -604,7 +600,7 @@ class ModuleBayTemplateTypeV1(ModularComponentTemplateTypeV1): filters=ModuleTypeProfileFilterV1, pagination=True ) -class ModuleTypeProfileTypeV1(NetBoxObjectTypeV1): +class ModuleTypeProfileTypeV1(PrimaryObjectTypeV1): module_types: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] @@ -614,7 +610,7 @@ class ModuleTypeProfileTypeV1(NetBoxObjectTypeV1): filters=ModuleTypeFilterV1, pagination=True ) -class ModuleTypeTypeV1(NetBoxObjectTypeV1): +class ModuleTypeTypeV1(PrimaryObjectTypeV1): profile: Annotated["ModuleTypeProfileTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] @@ -652,7 +648,7 @@ class PlatformTypeV1(OrganizationalObjectTypeV1): filters=PowerFeedFilterV1, pagination=True ) -class PowerFeedTypeV1(NetBoxObjectTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): +class PowerFeedTypeV1(CabledObjectMixinV1, PathEndpointMixinV1, PrimaryObjectTypeV1): power_panel: Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')] rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -686,7 +682,7 @@ class PowerOutletTemplateTypeV1(ModularComponentTemplateTypeV1): filters=PowerPanelFilterV1, pagination=True ) -class PowerPanelTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class PowerPanelTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None @@ -720,7 +716,7 @@ class PowerPortTemplateTypeV1(ModularComponentTemplateTypeV1): filters=RackTypeFilterV1, pagination=True ) -class RackTypeTypeV1(NetBoxObjectTypeV1): +class RackTypeTypeV1(PrimaryObjectTypeV1): manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] @@ -730,7 +726,7 @@ class RackTypeTypeV1(NetBoxObjectTypeV1): filters=RackFilterV1, pagination=True ) -class RackTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): +class RackTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -749,7 +745,7 @@ class RackTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, Ne filters=RackReservationFilterV1, pagination=True ) -class RackReservationTypeV1(NetBoxObjectTypeV1): +class RackReservationTypeV1(PrimaryObjectTypeV1): units: List[int] rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -824,7 +820,7 @@ class RegionTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV filters=SiteFilterV1, pagination=True ) -class SiteTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): +class SiteTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): time_zone: str | None region: Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None group: Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None @@ -885,7 +881,7 @@ class SiteGroupTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTy filters=VirtualChassisFilterV1, pagination=True ) -class VirtualChassisTypeV1(NetBoxObjectTypeV1): +class VirtualChassisTypeV1(PrimaryObjectTypeV1): member_count: BigInt master: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None @@ -898,7 +894,7 @@ class VirtualChassisTypeV1(NetBoxObjectTypeV1): filters=VirtualDeviceContextFilterV1, pagination=True ) -class VirtualDeviceContextTypeV1(NetBoxObjectTypeV1): +class VirtualDeviceContextTypeV1(PrimaryObjectTypeV1): device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None diff --git a/netbox/extras/graphql/types_v1.py b/netbox/extras/graphql/types_v1.py index d51b57e88..1693fe1da 100644 --- a/netbox/extras/graphql/types_v1.py +++ b/netbox/extras/graphql/types_v1.py @@ -7,8 +7,9 @@ from core.graphql.mixins_v1 import SyncedDataMixinV1 from extras import models from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1 from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, ContentTypeTypeV1, NetBoxObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1 + BaseObjectTypeV1, ContentTypeTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 ) +from users.graphql.mixins_v1 import OwnerMixinV1 from .filters_v1 import * if TYPE_CHECKING: @@ -55,7 +56,7 @@ __all__ = ( filters=ConfigContextProfileFilterV1, pagination=True ) -class ConfigContextProfileTypeV1(SyncedDataMixinV1, NetBoxObjectTypeV1): +class ConfigContextProfileTypeV1(SyncedDataMixinV1, PrimaryObjectTypeV1): pass @@ -65,7 +66,7 @@ class ConfigContextProfileTypeV1(SyncedDataMixinV1, NetBoxObjectTypeV1): filters=ConfigContextFilterV1, pagination=True ) -class ConfigContextTypeV1(SyncedDataMixinV1, ObjectTypeV1): +class ConfigContextTypeV1(SyncedDataMixinV1, OwnerMixinV1, ObjectTypeV1): profile: ConfigContextProfileTypeV1 | None roles: List[Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] device_types: List[Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] @@ -88,7 +89,7 @@ class ConfigContextTypeV1(SyncedDataMixinV1, ObjectTypeV1): filters=ConfigTemplateFilterV1, pagination=True ) -class ConfigTemplateTypeV1(SyncedDataMixinV1, TagsMixinV1, ObjectTypeV1): +class ConfigTemplateTypeV1(SyncedDataMixinV1, OwnerMixinV1, TagsMixinV1, ObjectTypeV1): virtualmachines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] @@ -101,7 +102,7 @@ class ConfigTemplateTypeV1(SyncedDataMixinV1, TagsMixinV1, ObjectTypeV1): filters=CustomFieldFilterV1, pagination=True ) -class CustomFieldTypeV1(ObjectTypeV1): +class CustomFieldTypeV1(OwnerMixinV1, ObjectTypeV1): related_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None choice_set: Annotated["CustomFieldChoiceSetTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None @@ -112,7 +113,7 @@ class CustomFieldTypeV1(ObjectTypeV1): filters=CustomFieldChoiceSetFilterV1, pagination=True ) -class CustomFieldChoiceSetTypeV1(ObjectTypeV1): +class CustomFieldChoiceSetTypeV1(OwnerMixinV1, ObjectTypeV1): choices_for: List[Annotated["CustomFieldTypeV1", strawberry.lazy('extras.graphql.types_v1')]] extra_choices: List[List[str]] | None @@ -124,7 +125,7 @@ class CustomFieldChoiceSetTypeV1(ObjectTypeV1): filters=CustomLinkFilterV1, pagination=True ) -class CustomLinkTypeV1(ObjectTypeV1): +class CustomLinkTypeV1(OwnerMixinV1, ObjectTypeV1): pass @@ -134,7 +135,7 @@ class CustomLinkTypeV1(ObjectTypeV1): filters=ExportTemplateFilterV1, pagination=True ) -class ExportTemplateTypeV1(SyncedDataMixinV1, ObjectTypeV1): +class ExportTemplateTypeV1(SyncedDataMixinV1, OwnerMixinV1, ObjectTypeV1): pass @@ -184,7 +185,7 @@ class NotificationGroupTypeV1(ObjectTypeV1): filters=SavedFilterFilterV1, pagination=True ) -class SavedFilterTypeV1(ObjectTypeV1): +class SavedFilterTypeV1(OwnerMixinV1, ObjectTypeV1): user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None @@ -213,7 +214,7 @@ class TableConfigTypeV1(ObjectTypeV1): filters=TagFilterV1, pagination=True ) -class TagTypeV1(ObjectTypeV1): +class TagTypeV1(OwnerMixinV1, ObjectTypeV1): color: str object_types: List[ContentTypeTypeV1] diff --git a/netbox/ipam/graphql/types_v1.py b/netbox/ipam/graphql/types_v1.py index 7b5d2c652..091a2f215 100644 --- a/netbox/ipam/graphql/types_v1.py +++ b/netbox/ipam/graphql/types_v1.py @@ -8,7 +8,9 @@ from dcim.graphql.types_v1 import SiteTypeV1 from extras.graphql.mixins_v1 import ContactsMixinV1 from ipam import models from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1 +from netbox.graphql.types_v1 import ( + BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 +) from .filters_v1 import * from .mixins_v1 import IPAddressesMixinV1 @@ -76,7 +78,7 @@ class BaseIPAddressFamilyTypeV1: filters=ASNFilterV1, pagination=True ) -class ASNTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class ASNTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): asn: BigInt rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -91,7 +93,7 @@ class ASNTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): filters=ASNRangeFilterV1, pagination=True ) -class ASNRangeTypeV1(NetBoxObjectTypeV1): +class ASNRangeTypeV1(OrganizationalObjectTypeV1): start: BigInt end: BigInt rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None @@ -104,7 +106,7 @@ class ASNRangeTypeV1(NetBoxObjectTypeV1): filters=AggregateFilterV1, pagination=True ) -class AggregateTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): +class AggregateTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): prefix: str rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -116,8 +118,7 @@ class AggregateTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTy filters=FHRPGroupFilterV1, pagination=True ) -class FHRPGroupTypeV1(NetBoxObjectTypeV1, IPAddressesMixinV1): - +class FHRPGroupTypeV1(IPAddressesMixinV1, PrimaryObjectTypeV1): fhrpgroupassignment_set: List[Annotated["FHRPGroupAssignmentTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] @@ -144,7 +145,7 @@ class FHRPGroupAssignmentTypeV1(BaseObjectTypeV1): filters=IPAddressFilterV1, pagination=True ) -class IPAddressTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): +class IPAddressTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): address: str vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -169,7 +170,7 @@ class IPAddressTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTy filters=IPRangeFilterV1, pagination=True ) -class IPRangeTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class IPRangeTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): start_address: str end_address: str vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None @@ -183,7 +184,7 @@ class IPRangeTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): filters=PrefixFilterV1, pagination=True ) -class PrefixTypeV1(NetBoxObjectTypeV1, ContactsMixinV1, BaseIPAddressFamilyTypeV1): +class PrefixTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): prefix: str vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -232,7 +233,7 @@ class RoleTypeV1(OrganizationalObjectTypeV1): filters=RouteTargetFilterV1, pagination=True ) -class RouteTargetTypeV1(NetBoxObjectTypeV1): +class RouteTargetTypeV1(PrimaryObjectTypeV1): tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None importing_l2vpns: List[Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] @@ -247,7 +248,7 @@ class RouteTargetTypeV1(NetBoxObjectTypeV1): filters=ServiceFilterV1, pagination=True ) -class ServiceTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): +class ServiceTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): ports: List[int] ipaddresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] @@ -266,7 +267,7 @@ class ServiceTypeV1(NetBoxObjectTypeV1, ContactsMixinV1): filters=ServiceTemplateFilterV1, pagination=True ) -class ServiceTemplateTypeV1(NetBoxObjectTypeV1): +class ServiceTemplateTypeV1(PrimaryObjectTypeV1): ports: List[int] @@ -276,7 +277,7 @@ class ServiceTemplateTypeV1(NetBoxObjectTypeV1): filters=VLANFilterV1, pagination=True ) -class VLANTypeV1(NetBoxObjectTypeV1): +class VLANTypeV1(PrimaryObjectTypeV1): site: Annotated["SiteTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None group: Annotated["VLANGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -325,7 +326,7 @@ class VLANGroupTypeV1(OrganizationalObjectTypeV1): filters=VLANTranslationPolicyFilterV1, pagination=True ) -class VLANTranslationPolicyTypeV1(NetBoxObjectTypeV1): +class VLANTranslationPolicyTypeV1(PrimaryObjectTypeV1): rules: List[Annotated["VLANTranslationRuleTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] @@ -348,7 +349,7 @@ class VLANTranslationRuleTypeV1(NetBoxObjectTypeV1): filters=VRFFilterV1, pagination=True ) -class VRFTypeV1(NetBoxObjectTypeV1): +class VRFTypeV1(PrimaryObjectTypeV1): tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] diff --git a/netbox/netbox/graphql/types_v1.py b/netbox/netbox/graphql/types_v1.py index 66a3ac905..5fc776d8d 100644 --- a/netbox/netbox/graphql/types_v1.py +++ b/netbox/netbox/graphql/types_v1.py @@ -3,10 +3,10 @@ import strawberry_django from strawberry.types import Info from django.contrib.contenttypes.models import ContentType -from core.graphql.mixins import ChangelogMixin +from core.graphql.mixins_v1 import ChangelogMixinV1 from core.models import ObjectType as ObjectType_ -from extras.graphql.mixins import CustomFieldsMixin, JournalEntriesMixin, TagsMixin -from users.graphql.mixins import OwnerMixin +from extras.graphql.mixins_v1 import CustomFieldsMixinV1, JournalEntriesMixinV1, TagsMixinV1 +from users.graphql.mixins_v1 import OwnerMixinV1 __all__ = ( 'BaseObjectTypeV1', @@ -47,7 +47,7 @@ class BaseObjectTypeV1: class ObjectTypeV1( - ChangelogMixin, + ChangelogMixinV1, BaseObjectTypeV1 ): """ @@ -57,11 +57,11 @@ class ObjectTypeV1( class PrimaryObjectTypeV1( - ChangelogMixin, - CustomFieldsMixin, - JournalEntriesMixin, - TagsMixin, - OwnerMixin, + ChangelogMixinV1, + CustomFieldsMixinV1, + JournalEntriesMixinV1, + TagsMixinV1, + OwnerMixinV1, BaseObjectTypeV1 ): """ @@ -71,11 +71,11 @@ class PrimaryObjectTypeV1( class OrganizationalObjectTypeV1( - ChangelogMixin, - CustomFieldsMixin, - JournalEntriesMixin, - TagsMixin, - OwnerMixin, + ChangelogMixinV1, + CustomFieldsMixinV1, + JournalEntriesMixinV1, + TagsMixinV1, + OwnerMixinV1, BaseObjectTypeV1 ): """ @@ -85,11 +85,11 @@ class OrganizationalObjectTypeV1( class NestedGroupObjectTypeV1( - ChangelogMixin, - CustomFieldsMixin, - JournalEntriesMixin, - TagsMixin, - OwnerMixin, + ChangelogMixinV1, + CustomFieldsMixinV1, + JournalEntriesMixinV1, + TagsMixinV1, + OwnerMixinV1, BaseObjectTypeV1 ): """ @@ -99,10 +99,10 @@ class NestedGroupObjectTypeV1( class NetBoxObjectTypeV1( - ChangelogMixin, - CustomFieldsMixin, - JournalEntriesMixin, - TagsMixin, + ChangelogMixinV1, + CustomFieldsMixinV1, + JournalEntriesMixinV1, + TagsMixinV1, BaseObjectTypeV1 ): """ diff --git a/netbox/tenancy/graphql/types_v1.py b/netbox/tenancy/graphql/types_v1.py index cc4b774bc..82e7d7610 100644 --- a/netbox/tenancy/graphql/types_v1.py +++ b/netbox/tenancy/graphql/types_v1.py @@ -4,7 +4,9 @@ import strawberry import strawberry_django from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1, ContactsMixinV1 -from netbox.graphql.types_v1 import BaseObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from netbox.graphql.types_v1 import ( + BaseObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 +) from tenancy import models from .filters_v1 import * from .mixins_v1 import ContactAssignmentsMixinV1 @@ -57,7 +59,7 @@ __all__ = ( filters=TenantFilterV1, pagination=True ) -class TenantTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): +class TenantTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): group: Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None asns: List[Annotated['ASNTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] circuits: List[Annotated['CircuitTypeV1', strawberry.lazy('circuits.graphql.types_v1')]] @@ -108,7 +110,7 @@ class TenantGroupTypeV1(OrganizationalObjectTypeV1): filters=ContactFilterV1, pagination=True ) -class ContactTypeV1(ContactAssignmentsMixinV1, NetBoxObjectTypeV1): +class ContactTypeV1(ContactAssignmentsMixinV1, PrimaryObjectTypeV1): groups: List[Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] diff --git a/netbox/users/graphql/filters_v1.py b/netbox/users/graphql/filters_v1.py index 1b08ed6fc..951c666ae 100644 --- a/netbox/users/graphql/filters_v1.py +++ b/netbox/users/graphql/filters_v1.py @@ -10,6 +10,8 @@ from users import models __all__ = ( 'GroupFilterV1', + 'OwnerFilterV1', + 'OwnerGroupFilterV1', 'UserFilterV1', ) @@ -32,3 +34,24 @@ class UserFilterV1(BaseObjectTypeFilterMixinV1): last_login: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( strawberry_django.filter_field()) + + +@strawberry_django.filter_type(models.Owner, lookups=True) +class OwnerFilterV1(BaseObjectTypeFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['OwnerGroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + user_groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + users: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter_type(models.OwnerGroup, lookups=True) +class OwnerGroupFilterV1(BaseObjectTypeFilterMixinV1): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/users/graphql/mixins_v1.py b/netbox/users/graphql/mixins_v1.py new file mode 100644 index 000000000..b2fb22e6d --- /dev/null +++ b/netbox/users/graphql/mixins_v1.py @@ -0,0 +1,15 @@ +from typing import Annotated, TYPE_CHECKING + +import strawberry + +if TYPE_CHECKING: + from users.graphql.types_v1 import OwnerTypeV1 + +__all__ = ( + 'OwnerMixinV1', +) + + +@strawberry.type +class OwnerMixinV1: + owner: Annotated['OwnerTypeV1', strawberry.lazy('users.graphql.types_v1')] | None diff --git a/netbox/users/graphql/schema_v1.py b/netbox/users/graphql/schema_v1.py index 69c3b9d57..b5389f35e 100644 --- a/netbox/users/graphql/schema_v1.py +++ b/netbox/users/graphql/schema_v1.py @@ -13,3 +13,9 @@ class UsersQueryV1: user: UserTypeV1 = strawberry_django.field() user_list: List[UserTypeV1] = strawberry_django.field() + + owner_group: OwnerGroupTypeV1 = strawberry_django.field() + owner_group_list: List[OwnerGroupTypeV1] = strawberry_django.field() + + owner: OwnerTypeV1 = strawberry_django.field() + owner_list: List[OwnerTypeV1] = strawberry_django.field() diff --git a/netbox/users/graphql/types_v1.py b/netbox/users/graphql/types_v1.py index 3b48431d0..16b0aecde 100644 --- a/netbox/users/graphql/types_v1.py +++ b/netbox/users/graphql/types_v1.py @@ -3,11 +3,13 @@ from typing import List import strawberry_django from netbox.graphql.types_v1 import BaseObjectTypeV1 -from users.models import Group, User +from users.models import Group, Owner, OwnerGroup, User from .filters_v1 import * __all__ = ( 'GroupTypeV1', + 'OwnerGroupTypeV1', + 'OwnerTypeV1', 'UserTypeV1', ) @@ -32,3 +34,23 @@ class GroupTypeV1(BaseObjectTypeV1): ) class UserTypeV1(BaseObjectTypeV1): groups: List[GroupTypeV1] + + +@strawberry_django.type( + OwnerGroup, + fields=['id', 'name', 'description'], + filters=OwnerGroupFilterV1, + pagination=True +) +class OwnerGroupTypeV1(BaseObjectTypeV1): + pass + + +@strawberry_django.type( + Owner, + fields=['id', 'group', 'name', 'description', 'user_groups', 'users'], + filters=OwnerFilterV1, + pagination=True +) +class OwnerTypeV1(BaseObjectTypeV1): + group: OwnerGroupTypeV1 | None diff --git a/netbox/virtualization/graphql/types_v1.py b/netbox/virtualization/graphql/types_v1.py index 079fa7b29..ba6add3cb 100644 --- a/netbox/virtualization/graphql/types_v1.py +++ b/netbox/virtualization/graphql/types_v1.py @@ -6,7 +6,8 @@ import strawberry_django from extras.graphql.mixins_v1 import ConfigContextMixinV1, ContactsMixinV1 from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1, PrimaryObjectTypeV1 +from users.graphql.mixins_v1 import OwnerMixinV1 from virtualization import models from .filters_v1 import * @@ -36,7 +37,7 @@ __all__ = ( @strawberry.type -class ComponentTypeV1(NetBoxObjectTypeV1): +class ComponentTypeV1(OwnerMixinV1, NetBoxObjectTypeV1): """ Base type for device/VM components """ @@ -49,7 +50,7 @@ class ComponentTypeV1(NetBoxObjectTypeV1): filters=ClusterFilterV1, pagination=True ) -class ClusterTypeV1(ContactsMixinV1, VLANGroupsMixinV1, NetBoxObjectTypeV1): +class ClusterTypeV1(ContactsMixinV1, VLANGroupsMixinV1, PrimaryObjectTypeV1): type: Annotated["ClusterTypeTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None group: Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -94,7 +95,7 @@ class ClusterTypeTypeV1(OrganizationalObjectTypeV1): filters=VirtualMachineFilterV1, pagination=True ) -class VirtualMachineTypeV1(ConfigContextMixinV1, ContactsMixinV1, NetBoxObjectTypeV1): +class VirtualMachineTypeV1(ConfigContextMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): interface_count: BigInt virtual_disk_count: BigInt interface_count: BigInt diff --git a/netbox/vpn/graphql/types_v1.py b/netbox/vpn/graphql/types_v1.py index 7812f88bb..6872cf6e2 100644 --- a/netbox/vpn/graphql/types_v1.py +++ b/netbox/vpn/graphql/types_v1.py @@ -4,7 +4,7 @@ import strawberry import strawberry_django from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 -from netbox.graphql.types_v1 import ObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from netbox.graphql.types_v1 import ObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1, PrimaryObjectTypeV1 from vpn import models from .filters_v1 import * @@ -58,7 +58,7 @@ class TunnelTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): filters=TunnelFilterV1, pagination=True ) -class TunnelTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): +class TunnelTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): group: Annotated["TunnelGroupTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None ipsec_profile: Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -72,8 +72,7 @@ class TunnelTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): filters=IKEProposalFilterV1, pagination=True ) -class IKEProposalTypeV1(OrganizationalObjectTypeV1): - +class IKEProposalTypeV1(PrimaryObjectTypeV1): ike_policies: List[Annotated["IKEPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] @@ -95,7 +94,7 @@ class IKEPolicyTypeV1(OrganizationalObjectTypeV1): filters=IPSecProposalFilterV1, pagination=True ) -class IPSecProposalTypeV1(OrganizationalObjectTypeV1): +class IPSecProposalTypeV1(PrimaryObjectTypeV1): ipsec_policies: List[Annotated["IPSecPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] @@ -131,7 +130,7 @@ class IPSecProfileTypeV1(OrganizationalObjectTypeV1): filters=L2VPNFilterV1, pagination=True ) -class L2VPNTypeV1(ContactsMixinV1, NetBoxObjectTypeV1): +class L2VPNTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None export_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] diff --git a/netbox/wireless/graphql/types_v1.py b/netbox/wireless/graphql/types_v1.py index b774f8f28..2c0fbe12f 100644 --- a/netbox/wireless/graphql/types_v1.py +++ b/netbox/wireless/graphql/types_v1.py @@ -3,7 +3,7 @@ from typing import Annotated, List, TYPE_CHECKING, Union import strawberry import strawberry_django -from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1 +from netbox.graphql.types_v1 import PrimaryObjectTypeV1, NestedGroupObjectTypeV1 from wireless import models from .filters_v1 import * @@ -27,7 +27,7 @@ __all__ = ( filters=WirelessLANGroupFilterV1, pagination=True ) -class WirelessLANGroupTypeV1(OrganizationalObjectTypeV1): +class WirelessLANGroupTypeV1(NestedGroupObjectTypeV1): parent: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None wireless_lans: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] @@ -40,7 +40,7 @@ class WirelessLANGroupTypeV1(OrganizationalObjectTypeV1): filters=WirelessLANFilterV1, pagination=True ) -class WirelessLANTypeV1(NetBoxObjectTypeV1): +class WirelessLANTypeV1(PrimaryObjectTypeV1): group: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None @@ -63,7 +63,7 @@ class WirelessLANTypeV1(NetBoxObjectTypeV1): filters=WirelessLinkFilterV1, pagination=True ) -class WirelessLinkTypeV1(NetBoxObjectTypeV1): +class WirelessLinkTypeV1(PrimaryObjectTypeV1): interface_a: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] interface_b: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None From 47ac506d5cb9db3605e814f4534e27ace58ab6ee Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 27 Oct 2025 16:32:09 -0400 Subject: [PATCH 08/15] Add a test to validate versioned GraphQL types --- netbox/netbox/tests/test_graphql.py | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index ca231526f..f5d69b03e 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -1,12 +1,15 @@ import json +import strawberry from django.test import override_settings from django.urls import reverse from rest_framework import status +from strawberry.types.lazy_type import LazyType from core.models import ObjectType from dcim.choices import LocationStatusChoices from dcim.models import Site, Location +from netbox.graphql.schema import QueryV1, QueryV2 from users.models import ObjectPermission from utilities.testing import disable_warnings, APITestCase, TestCase @@ -45,6 +48,53 @@ class GraphQLTestCase(TestCase): class GraphQLAPITestCase(APITestCase): + def test_versioned_types(self): + """ + Check that the GraphQL types defined for each version of the schema (V1 and V2) are correct. + """ + schemas = ( + (1, QueryV1), + (2, QueryV2), + ) + + def _get_class_name(field): + try: + if type(field.type) is strawberry.types.base.StrawberryList: + # Skip scalars + if field.type.of_type in (str, int): + return + if type(field.type.of_type) is LazyType: + return field.type.of_type.type_name + return field.type.of_type.__name__ + if hasattr(field.type, 'name'): + return field.type.__name__ + except AttributeError: + # Unknown field type + return + + def _check_version(class_name, version): + if version == 1: + self.assertTrue(class_name.endswith('V1'), f"{class_name} (v1) is not a V1 type") + elif version == 2: + self.assertFalse(class_name.endswith('V1'), f"{class_name} (v2) is a V1 type") + + for version, query in schemas: + schema = strawberry.Schema(query=query) + query_type = schema.get_type_by_name(query.__name__) + + # Iterate through root fields + for field in query_type.fields: + # Check for V1 suffix on class names + if type_class := _get_class_name(field): + _check_version(type_class, version) + + # Iterate through nested fields + subquery_type = schema.get_type_by_name(type_class) + for subfield in subquery_type.fields: + # Check for V1 suffix on class names + if type_class := _get_class_name(subfield): + _check_version(type_class, version) + @override_settings(LOGIN_REQUIRED=True) def test_graphql_filter_objects(self): """ From 5585b410f810a1cd5553a094901569a82f9cbd25 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 18 Nov 2025 20:32:45 -0500 Subject: [PATCH 09/15] Remove all V1 files --- netbox/circuits/graphql/filter_mixins_v1.py | 19 - netbox/circuits/graphql/filters_v1.py | 228 ---- netbox/circuits/graphql/schema_v1.py | 42 - netbox/circuits/graphql/types_v1.py | 191 --- netbox/core/graphql/filter_mixins_v1.py | 36 - netbox/core/graphql/filters_v1.py | 89 -- netbox/core/graphql/mixins_v1.py | 35 - netbox/core/graphql/schema_v1.py | 15 - netbox/core/graphql/types_v1.py | 55 - netbox/dcim/graphql/filter_mixins_v1.py | 155 --- netbox/dcim/graphql/filters_v1.py | 1020 ----------------- netbox/dcim/graphql/gfk_mixins_v1.py | 137 --- netbox/dcim/graphql/mixins_v1.py | 43 - netbox/dcim/graphql/schema_v1.py | 138 --- netbox/dcim/graphql/types_v1.py | 903 --------------- netbox/extras/graphql/filter_mixins_v1.py | 52 - netbox/extras/graphql/filters_v1.py | 357 ------ netbox/extras/graphql/mixins_v1.py | 62 - netbox/extras/graphql/schema_v1.py | 60 - netbox/extras/graphql/types_v1.py | 240 ---- netbox/ipam/graphql/filter_mixins_v1.py | 25 - netbox/ipam/graphql/filters_v1.py | 392 ------- netbox/ipam/graphql/mixins_v1.py | 18 - netbox/ipam/graphql/schema_v1.py | 63 - netbox/ipam/graphql/types_v1.py | 361 ------ netbox/netbox/configuration_testing.py | 3 - netbox/netbox/graphql/filter_mixins_v1.py | 104 -- netbox/netbox/graphql/schema.py | 57 +- netbox/netbox/graphql/types_v1.py | 133 --- netbox/netbox/graphql/utils.py | 16 - netbox/netbox/urls.py | 9 +- netbox/tenancy/graphql/filter_mixins_v1.py | 38 - netbox/tenancy/graphql/filters_v1.py | 210 ---- netbox/tenancy/graphql/mixins_v1.py | 12 - netbox/tenancy/graphql/schema_v1.py | 27 - netbox/tenancy/graphql/types_v1.py | 149 --- netbox/users/graphql/filters_v1.py | 57 - netbox/users/graphql/mixins_v1.py | 15 - netbox/users/graphql/schema_v1.py | 21 - netbox/users/graphql/types_v1.py | 56 - .../graphql/filter_mixins_v1.py | 28 - netbox/virtualization/graphql/filters_v1.py | 170 --- netbox/virtualization/graphql/schema_v1.py | 27 - netbox/virtualization/graphql/types_v1.py | 147 --- netbox/vpn/graphql/filters_v1.py | 192 ---- netbox/vpn/graphql/schema_v1.py | 39 - netbox/vpn/graphql/types_v1.py | 156 --- netbox/wireless/graphql/filter_mixins_v1.py | 26 - netbox/wireless/graphql/filters_v1.py | 72 -- netbox/wireless/graphql/schema_v1.py | 18 - netbox/wireless/graphql/types_v1.py | 71 -- 51 files changed, 6 insertions(+), 6583 deletions(-) delete mode 100644 netbox/circuits/graphql/filter_mixins_v1.py delete mode 100644 netbox/circuits/graphql/filters_v1.py delete mode 100644 netbox/circuits/graphql/schema_v1.py delete mode 100644 netbox/circuits/graphql/types_v1.py delete mode 100644 netbox/core/graphql/filter_mixins_v1.py delete mode 100644 netbox/core/graphql/filters_v1.py delete mode 100644 netbox/core/graphql/mixins_v1.py delete mode 100644 netbox/core/graphql/schema_v1.py delete mode 100644 netbox/core/graphql/types_v1.py delete mode 100644 netbox/dcim/graphql/filter_mixins_v1.py delete mode 100644 netbox/dcim/graphql/filters_v1.py delete mode 100644 netbox/dcim/graphql/gfk_mixins_v1.py delete mode 100644 netbox/dcim/graphql/mixins_v1.py delete mode 100644 netbox/dcim/graphql/schema_v1.py delete mode 100644 netbox/dcim/graphql/types_v1.py delete mode 100644 netbox/extras/graphql/filter_mixins_v1.py delete mode 100644 netbox/extras/graphql/filters_v1.py delete mode 100644 netbox/extras/graphql/mixins_v1.py delete mode 100644 netbox/extras/graphql/schema_v1.py delete mode 100644 netbox/extras/graphql/types_v1.py delete mode 100644 netbox/ipam/graphql/filter_mixins_v1.py delete mode 100644 netbox/ipam/graphql/filters_v1.py delete mode 100644 netbox/ipam/graphql/mixins_v1.py delete mode 100644 netbox/ipam/graphql/schema_v1.py delete mode 100644 netbox/ipam/graphql/types_v1.py delete mode 100644 netbox/netbox/graphql/filter_mixins_v1.py delete mode 100644 netbox/netbox/graphql/types_v1.py delete mode 100644 netbox/netbox/graphql/utils.py delete mode 100644 netbox/tenancy/graphql/filter_mixins_v1.py delete mode 100644 netbox/tenancy/graphql/filters_v1.py delete mode 100644 netbox/tenancy/graphql/mixins_v1.py delete mode 100644 netbox/tenancy/graphql/schema_v1.py delete mode 100644 netbox/tenancy/graphql/types_v1.py delete mode 100644 netbox/users/graphql/filters_v1.py delete mode 100644 netbox/users/graphql/mixins_v1.py delete mode 100644 netbox/users/graphql/schema_v1.py delete mode 100644 netbox/users/graphql/types_v1.py delete mode 100644 netbox/virtualization/graphql/filter_mixins_v1.py delete mode 100644 netbox/virtualization/graphql/filters_v1.py delete mode 100644 netbox/virtualization/graphql/schema_v1.py delete mode 100644 netbox/virtualization/graphql/types_v1.py delete mode 100644 netbox/vpn/graphql/filters_v1.py delete mode 100644 netbox/vpn/graphql/schema_v1.py delete mode 100644 netbox/vpn/graphql/types_v1.py delete mode 100644 netbox/wireless/graphql/filter_mixins_v1.py delete mode 100644 netbox/wireless/graphql/filters_v1.py delete mode 100644 netbox/wireless/graphql/schema_v1.py delete mode 100644 netbox/wireless/graphql/types_v1.py diff --git a/netbox/circuits/graphql/filter_mixins_v1.py b/netbox/circuits/graphql/filter_mixins_v1.py deleted file mode 100644 index 15ebce2d4..000000000 --- a/netbox/circuits/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,19 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django - -from netbox.graphql.filter_mixins_v1 import OrganizationalModelFilterMixinV1 - -if TYPE_CHECKING: - from netbox.graphql.enums import ColorEnum - -__all__ = ( - 'BaseCircuitTypeFilterMixinV1', -) - - -@dataclass -class BaseCircuitTypeFilterMixinV1(OrganizationalModelFilterMixinV1): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() diff --git a/netbox/circuits/graphql/filters_v1.py b/netbox/circuits/graphql/filters_v1.py deleted file mode 100644 index caf4b7c62..000000000 --- a/netbox/circuits/graphql/filters_v1.py +++ /dev/null @@ -1,228 +0,0 @@ -from datetime import date -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup, DateFilterLookup - -from circuits import models -from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 -from dcim.graphql.filter_mixins_v1 import CabledObjectModelFilterMixinV1 -from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - DistanceFilterMixinV1, - ImageAttachmentFilterMixinV1, - OrganizationalModelFilterMixinV1, - PrimaryModelFilterMixinV1, -) -from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 -from .filter_mixins_v1 import BaseCircuitTypeFilterMixinV1 - -if TYPE_CHECKING: - from core.graphql.filters_v1 import ContentTypeFilterV1 - from dcim.graphql.filters_v1 import ( - InterfaceFilterV1, LocationFilterV1, RegionFilterV1, SiteFilterV1, SiteGroupFilterV1 - ) - from ipam.graphql.filters_v1 import ASNFilterV1 - from netbox.graphql.filter_lookups import IntegerLookup - from .enums import * - -__all__ = ( - 'CircuitFilterV1', - 'CircuitGroupAssignmentFilterV1', - 'CircuitGroupFilterV1', - 'CircuitTerminationFilterV1', - 'CircuitTypeFilterV1', - 'ProviderFilterV1', - 'ProviderAccountFilterV1', - 'ProviderNetworkFilterV1', - 'VirtualCircuitFilterV1', - 'VirtualCircuitTerminationFilterV1', - 'VirtualCircuitTypeFilterV1', -) - - -@strawberry_django.filter_type(models.CircuitTermination, lookups=True) -class CircuitTerminationFilterV1( - BaseObjectTypeFilterMixinV1, - CustomFieldsFilterMixinV1, - TagsFilterMixinV1, - ChangeLogFilterMixinV1, - CabledObjectModelFilterMixinV1, -): - circuit: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - term_side: Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - termination_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - termination_id: ID | None = strawberry_django.filter_field() - port_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - upstream_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - xconnect_id: FilterLookup[str] | None = strawberry_django.filter_field() - pp_info: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - # Cached relations - _provider_network: Annotated['ProviderNetworkFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field(name='provider_network') - ) - _location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field(name='location') - ) - _region: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field(name='region') - ) - _site_group: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field(name='site_group') - ) - _site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field(name='site') - ) - - -@strawberry_django.filter_type(models.Circuit, lookups=True) -class CircuitFilterV1( - ContactFilterMixinV1, - ImageAttachmentFilterMixinV1, - DistanceFilterMixinV1, - TenancyFilterMixinV1, - PrimaryModelFilterMixinV1 -): - cid: FilterLookup[str] | None = strawberry_django.filter_field() - provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_id: ID | None = strawberry_django.filter_field() - provider_account: Annotated['ProviderAccountFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_account_id: ID | None = strawberry_django.filter_field() - type: Annotated['CircuitTypeFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - type_id: ID | None = strawberry_django.filter_field() - status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() - termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field() - commit_rate: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - terminations: Annotated['CircuitTerminationFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.CircuitType, lookups=True) -class CircuitTypeFilterV1(BaseCircuitTypeFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.CircuitGroup, lookups=True) -class CircuitGroupFilterV1(TenancyFilterMixinV1, OrganizationalModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.CircuitGroupAssignment, lookups=True) -class CircuitGroupAssignmentFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - member_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - member_id: ID | None = strawberry_django.filter_field() - group: Annotated['CircuitGroupFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - priority: Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Provider, lookups=True) -class ProviderFilterV1(ContactFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - circuits: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ProviderAccount, lookups=True) -class ProviderAccountFilterV1(ContactFilterMixinV1, PrimaryModelFilterMixinV1): - provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_id: ID | None = strawberry_django.filter_field() - account: FilterLookup[str] | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ProviderNetwork, lookups=True) -class ProviderNetworkFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - provider: Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_id: ID | None = strawberry_django.filter_field() - service_id: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.VirtualCircuitType, lookups=True) -class VirtualCircuitTypeFilterV1(BaseCircuitTypeFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.VirtualCircuit, lookups=True) -class VirtualCircuitFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - cid: FilterLookup[str] | None = strawberry_django.filter_field() - provider_network: Annotated['ProviderNetworkFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_network_id: ID | None = strawberry_django.filter_field() - provider_account: Annotated['ProviderAccountFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - provider_account_id: ID | None = strawberry_django.filter_field() - type: Annotated['VirtualCircuitTypeFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - type_id: ID | None = strawberry_django.filter_field() - status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - group_assignments: Annotated[ - 'CircuitGroupAssignmentFilterV1', strawberry.lazy('circuits.graphql.filters_v1') - ] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VirtualCircuitTermination, lookups=True) -class VirtualCircuitTerminationFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - virtual_circuit: Annotated['VirtualCircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - virtual_circuit_id: ID | None = strawberry_django.filter_field() - role: Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - interface: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interface_id: ID | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/circuits/graphql/schema_v1.py b/netbox/circuits/graphql/schema_v1.py deleted file mode 100644 index 1134978f9..000000000 --- a/netbox/circuits/graphql/schema_v1.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class CircuitsQueryV1: - circuit: CircuitTypeV1 = strawberry_django.field() - circuit_list: List[CircuitTypeV1] = strawberry_django.field() - - circuit_termination: CircuitTerminationTypeV1 = strawberry_django.field() - circuit_termination_list: List[CircuitTerminationTypeV1] = strawberry_django.field() - - circuit_type: CircuitTypeTypeV1 = strawberry_django.field() - circuit_type_list: List[CircuitTypeTypeV1] = strawberry_django.field() - - circuit_group: CircuitGroupTypeV1 = strawberry_django.field() - circuit_group_list: List[CircuitGroupTypeV1] = strawberry_django.field() - - circuit_group_assignment: CircuitGroupAssignmentTypeV1 = strawberry_django.field() - circuit_group_assignment_list: List[CircuitGroupAssignmentTypeV1] = strawberry_django.field() - - provider: ProviderTypeV1 = strawberry_django.field() - provider_list: List[ProviderTypeV1] = strawberry_django.field() - - provider_account: ProviderAccountTypeV1 = strawberry_django.field() - provider_account_list: List[ProviderAccountTypeV1] = strawberry_django.field() - - provider_network: ProviderNetworkTypeV1 = strawberry_django.field() - provider_network_list: List[ProviderNetworkTypeV1] = strawberry_django.field() - - virtual_circuit: VirtualCircuitTypeV1 = strawberry_django.field() - virtual_circuit_list: List[VirtualCircuitTypeV1] = strawberry_django.field() - - virtual_circuit_termination: VirtualCircuitTerminationTypeV1 = strawberry_django.field() - virtual_circuit_termination_list: List[VirtualCircuitTerminationTypeV1] = strawberry_django.field() - - virtual_circuit_type: VirtualCircuitTypeTypeV1 = strawberry_django.field() - virtual_circuit_type_list: List[VirtualCircuitTypeTypeV1] = strawberry_django.field() diff --git a/netbox/circuits/graphql/types_v1.py b/netbox/circuits/graphql/types_v1.py deleted file mode 100644 index c9f9234dc..000000000 --- a/netbox/circuits/graphql/types_v1.py +++ /dev/null @@ -1,191 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from circuits import models -from dcim.graphql.mixins_v1 import CabledObjectMixinV1 -from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 -from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 -) -from tenancy.graphql.types_v1 import TenantTypeV1 -from .filters_v1 import * - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import InterfaceTypeV1, LocationTypeV1, RegionTypeV1, SiteGroupTypeV1, SiteTypeV1 - from ipam.graphql.types_v1 import ASNTypeV1 - -__all__ = ( - 'CircuitGroupAssignmentTypeV1', - 'CircuitGroupTypeV1', - 'CircuitTerminationTypeV1', - 'CircuitTypeV1', - 'CircuitTypeTypeV1', - 'ProviderTypeV1', - 'ProviderAccountTypeV1', - 'ProviderNetworkTypeV1', - 'VirtualCircuitTerminationTypeV1', - 'VirtualCircuitTypeV1', - 'VirtualCircuitTypeTypeV1', -) - - -@strawberry_django.type( - models.Provider, - fields='__all__', - filters=ProviderFilterV1, - pagination=True -) -class ProviderTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - - networks: List[Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - accounts: List[Annotated["ProviderAccountTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - - -@strawberry_django.type( - models.ProviderAccount, - fields='__all__', - filters=ProviderAccountFilterV1, - pagination=True -) -class ProviderAccountTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - - circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - - -@strawberry_django.type( - models.ProviderNetwork, - fields='__all__', - filters=ProviderNetworkFilterV1, - pagination=True -) -class ProviderNetworkTypeV1(PrimaryObjectTypeV1): - provider: Annotated["ProviderTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - - circuit_terminations: List[Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - - -@strawberry_django.type( - models.CircuitTermination, - exclude=['termination_type', 'termination_id', '_location', '_region', '_site', '_site_group', '_provider_network'], - filters=CircuitTerminationFilterV1, - pagination=True -) -class CircuitTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, CabledObjectMixinV1, ObjectTypeV1): - circuit: Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - - @strawberry_django.field - def termination(self) -> Annotated[Union[ - Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - ], strawberry.union("CircuitTerminationTerminationTypeV1")] | None: - return self.termination - - -@strawberry_django.type( - models.CircuitType, - fields='__all__', - filters=CircuitTypeFilterV1, - pagination=True -) -class CircuitTypeTypeV1(OrganizationalObjectTypeV1): - color: str - - circuits: List[Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - - -@strawberry_django.type( - models.Circuit, - fields='__all__', - filters=CircuitFilterV1, - pagination=True -) -class CircuitTypeV1(PrimaryObjectTypeV1, ContactsMixinV1): - provider: ProviderTypeV1 - provider_account: ProviderAccountTypeV1 | None - termination_a: CircuitTerminationTypeV1 | None - termination_z: CircuitTerminationTypeV1 | None - type: CircuitTypeTypeV1 - tenant: TenantTypeV1 | None - - terminations: List[CircuitTerminationTypeV1] - - -@strawberry_django.type( - models.CircuitGroup, - fields='__all__', - filters=CircuitGroupFilterV1, - pagination=True -) -class CircuitGroupTypeV1(OrganizationalObjectTypeV1): - tenant: TenantTypeV1 | None - - -@strawberry_django.type( - models.CircuitGroupAssignment, - exclude=['member_type', 'member_id'], - filters=CircuitGroupAssignmentFilterV1, - pagination=True -) -class CircuitGroupAssignmentTypeV1(TagsMixinV1, BaseObjectTypeV1): - group: Annotated["CircuitGroupTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - - @strawberry_django.field - def member(self) -> Annotated[Union[ - Annotated["CircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - Annotated["VirtualCircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - ], strawberry.union("CircuitGroupAssignmentMemberTypeV1")] | None: - return self.member - - -@strawberry_django.type( - models.VirtualCircuitType, - fields='__all__', - filters=VirtualCircuitTypeFilterV1, - pagination=True -) -class VirtualCircuitTypeTypeV1(OrganizationalObjectTypeV1): - color: str - - virtual_circuits: List[Annotated["VirtualCircuitTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - - -@strawberry_django.type( - models.VirtualCircuitTermination, - fields='__all__', - filters=VirtualCircuitTerminationFilterV1, - pagination=True -) -class VirtualCircuitTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): - virtual_circuit: Annotated[ - "VirtualCircuitTypeV1", - strawberry.lazy('circuits.graphql.types_v1') - ] = strawberry_django.field(select_related=["virtual_circuit"]) - interface: Annotated[ - "InterfaceTypeV1", - strawberry.lazy('dcim.graphql.types_v1') - ] = strawberry_django.field(select_related=["interface"]) - - -@strawberry_django.type( - models.VirtualCircuit, - fields='__all__', - filters=VirtualCircuitFilterV1, - pagination=True -) -class VirtualCircuitTypeV1(PrimaryObjectTypeV1): - provider_network: ProviderNetworkTypeV1 = strawberry_django.field(select_related=["provider_network"]) - provider_account: ProviderAccountTypeV1 | None - type: Annotated["VirtualCircuitTypeTypeV1", strawberry.lazy('circuits.graphql.types_v1')] = strawberry_django.field( - select_related=["type"] - ) - tenant: TenantTypeV1 | None - - terminations: List[VirtualCircuitTerminationTypeV1] diff --git a/netbox/core/graphql/filter_mixins_v1.py b/netbox/core/graphql/filter_mixins_v1.py deleted file mode 100644 index 58c39705f..000000000 --- a/netbox/core/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,36 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry import ID -from strawberry_django import DatetimeFilterLookup - -if TYPE_CHECKING: - from .filters_v1 import * - -__all__ = ( - 'BaseFilterMixinV1', - 'BaseObjectTypeFilterMixinV1', - 'ChangeLogFilterMixinV1', -) - - -# @strawberry.input -class BaseFilterMixinV1: ... - - -@dataclass -class BaseObjectTypeFilterMixinV1(BaseFilterMixinV1): - id: ID | None = strawberry.UNSET - - -@dataclass -class ChangeLogFilterMixinV1(BaseFilterMixinV1): - id: ID | None = strawberry.UNSET - changelog: Annotated['ObjectChangeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filters_v1.py b/netbox/core/graphql/filters_v1.py deleted file mode 100644 index c75de0c75..000000000 --- a/netbox/core/graphql/filters_v1.py +++ /dev/null @@ -1,89 +0,0 @@ -from datetime import datetime -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from django.contrib.contenttypes.models import ContentType as DjangoContentType -from strawberry.scalars import ID -from strawberry_django import DatetimeFilterLookup, FilterLookup - -from core import models -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 -from netbox.graphql.filter_mixins import PrimaryModelFilterMixin - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import IntegerLookup, JSONFilter - from users.graphql.filters import UserFilter - -__all__ = ( - 'DataFileFilterV1', - 'DataSourceFilterV1', - 'ObjectChangeFilterV1', - 'ContentTypeFilterV1', -) - - -@strawberry_django.filter_type(models.DataFile, lookups=True) -class DataFileFilterV1(BaseFilterMixinV1): - id: ID | None = strawberry_django.filter_field() - created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - source: Annotated['DataSourceFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - source_id: ID | None = strawberry_django.filter_field() - path: FilterLookup[str] | None = strawberry_django.filter_field() - size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - hash: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.DataSource, lookups=True) -class DataSourceFilterV1(PrimaryModelFilterMixin): - name: FilterLookup[str] | None = strawberry_django.filter_field() - type: FilterLookup[str] | None = strawberry_django.filter_field() - source_url: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[str] | None = strawberry_django.filter_field() - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - ignore_rules: FilterLookup[str] | None = strawberry_django.filter_field() - parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - datafiles: Annotated['DataFileFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ObjectChange, lookups=True) -class ObjectChangeFilterV1(BaseFilterMixinV1): - id: ID | None = strawberry_django.filter_field() - time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() - user_name: FilterLookup[str] | None = strawberry_django.filter_field() - request_id: FilterLookup[str] | None = strawberry_django.filter_field() - action: FilterLookup[str] | None = strawberry_django.filter_field() - changed_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - changed_object_type_id: ID | None = strawberry_django.filter_field() - changed_object_id: ID | None = strawberry_django.filter_field() - related_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - related_object_id: ID | None = strawberry_django.filter_field() - object_repr: FilterLookup[str] | None = strawberry_django.filter_field() - prechange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - postchange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(DjangoContentType, lookups=True) -class ContentTypeFilterV1(BaseFilterMixinV1): - id: ID | None = strawberry_django.filter_field() - app_label: FilterLookup[str] | None = strawberry_django.filter_field() - model: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/mixins_v1.py b/netbox/core/graphql/mixins_v1.py deleted file mode 100644 index 722068cdd..000000000 --- a/netbox/core/graphql/mixins_v1.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING - -import strawberry -import strawberry_django -from django.contrib.contenttypes.models import ContentType -from strawberry.types import Info - -from core.models import ObjectChange - -if TYPE_CHECKING: - from core.graphql.types_v1 import DataFileTypeV1, DataSourceTypeV1, ObjectChangeTypeV1 - -__all__ = ( - 'ChangelogMixinV1', - 'SyncedDataMixinV1', -) - - -@strawberry.type -class ChangelogMixinV1: - - @strawberry_django.field - def changelog(self, info: Info) -> List[Annotated['ObjectChangeTypeV1', strawberry.lazy('.types_v1')]]: # noqa: F821 - content_type = ContentType.objects.get_for_model(self) - object_changes = ObjectChange.objects.filter( - changed_object_type=content_type, - changed_object_id=self.pk - ) - return object_changes.restrict(info.context.request.user, 'view') - - -@strawberry.type -class SyncedDataMixinV1: - data_source: Annotated['DataSourceTypeV1', strawberry.lazy('core.graphql.types_v1')] | None - data_file: Annotated['DataFileTypeV1', strawberry.lazy('core.graphql.types_v1')] | None diff --git a/netbox/core/graphql/schema_v1.py b/netbox/core/graphql/schema_v1.py deleted file mode 100644 index b94d14a70..000000000 --- a/netbox/core/graphql/schema_v1.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class CoreQueryV1: - data_file: DataFileTypeV1 = strawberry_django.field() - data_file_list: List[DataFileTypeV1] = strawberry_django.field() - - data_source: DataSourceTypeV1 = strawberry_django.field() - data_source_list: List[DataSourceTypeV1] = strawberry_django.field() diff --git a/netbox/core/graphql/types_v1.py b/netbox/core/graphql/types_v1.py deleted file mode 100644 index 7dc8c0e3b..000000000 --- a/netbox/core/graphql/types_v1.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Annotated, List - -import strawberry -import strawberry_django -from django.contrib.contenttypes.models import ContentType as DjangoContentType - -from core import models -from netbox.graphql.types_v1 import BaseObjectTypeV1, PrimaryObjectTypeV1 -from .filters_v1 import * - -__all__ = ( - 'ContentTypeV1', - 'DataFileTypeV1', - 'DataSourceTypeV1', - 'ObjectChangeTypeV1', -) - - -@strawberry_django.type( - models.DataFile, - exclude=['data',], - filters=DataFileFilterV1, - pagination=True -) -class DataFileTypeV1(BaseObjectTypeV1): - source: Annotated["DataSourceTypeV1", strawberry.lazy('core.graphql.types_v1')] - - -@strawberry_django.type( - models.DataSource, - fields='__all__', - filters=DataSourceFilterV1, - pagination=True -) -class DataSourceTypeV1(PrimaryObjectTypeV1): - datafiles: List[Annotated["DataFileTypeV1", strawberry.lazy('core.graphql.types_v1')]] - - -@strawberry_django.type( - models.ObjectChange, - fields='__all__', - filters=ObjectChangeFilterV1, - pagination=True -) -class ObjectChangeTypeV1(BaseObjectTypeV1): - pass - - -@strawberry_django.type( - DjangoContentType, - fields='__all__', - pagination=True -) -class ContentTypeV1: - pass diff --git a/netbox/dcim/graphql/filter_mixins_v1.py b/netbox/dcim/graphql/filter_mixins_v1.py deleted file mode 100644 index 55f329497..000000000 --- a/netbox/dcim/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,155 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry import ID -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1, ChangeLogFilterMixinV1 -from core.graphql.filters_v1 import ContentTypeFilterV1 -from netbox.graphql.filter_mixins_v1 import NetBoxModelFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1 -from .enums import * - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import IntegerLookup - from extras.graphql.filters_v1 import ConfigTemplateFilterV1 - from ipam.graphql.filters_v1 import VLANFilterV1, VLANTranslationPolicyFilterV1 - from .filters_v1 import * - -__all__ = ( - 'CabledObjectModelFilterMixinV1', - 'ComponentModelFilterMixinV1', - 'ComponentTemplateFilterMixinV1', - 'InterfaceBaseFilterMixinV1', - 'ModularComponentModelFilterMixinV1', - 'ModularComponentTemplateFilterMixinV1', - 'RackBaseFilterMixinV1', - 'RenderConfigFilterMixinV1', - 'ScopedFilterMixinV1', -) - - -@dataclass -class ScopedFilterMixinV1(BaseFilterMixinV1): - scope_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - scope_id: ID | None = strawberry_django.filter_field() - - -@dataclass -class ComponentModelFilterMixinV1(NetBoxModelFilterMixinV1): - device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - label: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@dataclass -class ModularComponentModelFilterMixinV1(ComponentModelFilterMixinV1): - module: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - module_id: ID | None = strawberry_django.filter_field() - inventory_items: Annotated['InventoryItemFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class CabledObjectModelFilterMixinV1(BaseFilterMixinV1): - cable: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cable_id: ID | None = strawberry_django.filter_field() - cable_end: CableEndEnum | None = strawberry_django.filter_field() - mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@dataclass -class ComponentTemplateFilterMixinV1(ChangeLogFilterMixinV1): - device_type: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_type_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - label: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@dataclass -class ModularComponentTemplateFilterMixinV1(ComponentTemplateFilterMixinV1): - module_type: Annotated['ModuleTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class RenderConfigFilterMixinV1(BaseFilterMixinV1): - config_template: Annotated['ConfigTemplateFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - config_template_id: ID | None = strawberry_django.filter_field() - - -@dataclass -class InterfaceBaseFilterMixinV1(BaseFilterMixinV1): - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - mtu: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - mode: InterfaceModeEnum | None = strawberry_django.filter_field() - bridge: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - bridge_id: ID | None = strawberry_django.filter_field() - untagged_vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tagged_vlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - qinq_svlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_translation_policy: Annotated[ - 'VLANTranslationPolicyFilterV1', strawberry.lazy('ipam.graphql.filters_v1') - ] | None = strawberry_django.filter_field() - primary_mac_address: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_mac_address_id: ID | None = strawberry_django.filter_field() - - -@dataclass -class RackBaseFilterMixinV1(WeightFilterMixinV1, PrimaryModelFilterMixinV1): - width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - u_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - starting_unit: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - desc_units: FilterLookup[bool] | None = strawberry_django.filter_field() - outer_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - outer_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - outer_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - mounting_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - max_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/dcim/graphql/filters_v1.py b/netbox/dcim/graphql/filters_v1.py deleted file mode 100644 index be87c3618..000000000 --- a/netbox/dcim/graphql/filters_v1.py +++ /dev/null @@ -1,1020 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -from django.db.models import Q -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import ChangeLogFilterMixinV1 -from dcim import models -from dcim.constants import * -from dcim.graphql.enums import InterfaceKindEnum -from extras.graphql.filter_mixins_v1 import ConfigContextFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - PrimaryModelFilterMixinV1, - OrganizationalModelFilterMixinV1, - NestedGroupModelFilterMixinV1, - ImageAttachmentFilterMixinV1, - WeightFilterMixinV1, -) -from tenancy.graphql.filter_mixins_v1 import TenancyFilterMixinV1, ContactFilterMixinV1 -from .filter_mixins_v1 import ( - CabledObjectModelFilterMixinV1, - ComponentModelFilterMixinV1, - ComponentTemplateFilterMixinV1, - InterfaceBaseFilterMixinV1, - ModularComponentModelFilterMixinV1, - ModularComponentTemplateFilterMixinV1, - RackBaseFilterMixinV1, - RenderConfigFilterMixinV1, -) - -if TYPE_CHECKING: - from core.graphql.filters_v1 import ContentTypeFilterV1 - from extras.graphql.filters_v1 import ConfigTemplateFilterV1, ImageAttachmentFilterV1 - from ipam.graphql.filters_v1 import ( - ASNFilterV1, FHRPGroupAssignmentFilterV1, IPAddressFilterV1, PrefixFilterV1, VLANGroupFilterV1, VRFFilterV1, - ) - from netbox.graphql.enums import ColorEnum - from netbox.graphql.filter_lookups import FloatLookup, IntegerArrayLookup, IntegerLookup, TreeNodeFilter - from users.graphql.filters_v1 import UserFilterV1 - from virtualization.graphql.filters_v1 import ClusterFilterV1 - from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelTerminationFilterV1 - from wireless.graphql.enums import WirelessChannelEnum, WirelessRoleEnum - from wireless.graphql.filters_v1 import WirelessLANFilterV1, WirelessLinkFilterV1 - from .enums import * - -__all__ = ( - 'CableFilterV1', - 'CableTerminationFilterV1', - 'ConsolePortFilterV1', - 'ConsolePortTemplateFilterV1', - 'ConsoleServerPortFilterV1', - 'ConsoleServerPortTemplateFilterV1', - 'DeviceFilterV1', - 'DeviceBayFilterV1', - 'DeviceBayTemplateFilterV1', - 'DeviceRoleFilterV1', - 'DeviceTypeFilterV1', - 'FrontPortFilterV1', - 'FrontPortTemplateFilterV1', - 'InterfaceFilterV1', - 'InterfaceTemplateFilterV1', - 'InventoryItemFilterV1', - 'InventoryItemRoleFilterV1', - 'InventoryItemTemplateFilterV1', - 'LocationFilterV1', - 'MACAddressFilterV1', - 'ManufacturerFilterV1', - 'ModuleFilterV1', - 'ModuleBayFilterV1', - 'ModuleBayTemplateFilterV1', - 'ModuleTypeFilterV1', - 'ModuleTypeProfileFilterV1', - 'PlatformFilterV1', - 'PowerFeedFilterV1', - 'PowerOutletFilterV1', - 'PowerOutletTemplateFilterV1', - 'PowerPanelFilterV1', - 'PowerPortFilterV1', - 'PowerPortTemplateFilterV1', - 'RackFilterV1', - 'RackReservationFilterV1', - 'RackRoleFilterV1', - 'RackTypeFilterV1', - 'RearPortFilterV1', - 'RearPortTemplateFilterV1', - 'RegionFilterV1', - 'SiteFilterV1', - 'SiteGroupFilterV1', - 'VirtualChassisFilterV1', - 'VirtualDeviceContextFilterV1', -) - - -@strawberry_django.filter_type(models.Cable, lookups=True) -class CableFilterV1(PrimaryModelFilterMixinV1, TenancyFilterMixinV1): - type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - label: FilterLookup[str] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - terminations: Annotated['CableTerminationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.CableTermination, lookups=True) -class CableTerminationFilterV1(ChangeLogFilterMixinV1): - cable: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cable_id: ID | None = strawberry_django.filter_field() - cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - termination_type: Annotated['CableTerminationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - termination_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ConsolePort, lookups=True) -class ConsolePortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True) -class ConsolePortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ConsoleServerPort, lookups=True) -class ConsoleServerPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True) -class ConsoleServerPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Device, lookups=True) -class DeviceFilterV1( - ContactFilterMixinV1, - TenancyFilterMixinV1, - ImageAttachmentFilterMixinV1, - RenderConfigFilterMixinV1, - ConfigContextFilterMixinV1, - PrimaryModelFilterMixinV1, -): - device_type: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_type_id: ID | None = strawberry_django.filter_field() - role: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - serial: FilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rack_id: ID | None = strawberry_django.filter_field() - position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip4_id: ID | None = strawberry_django.filter_field() - primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip6_id: ID | None = strawberry_django.filter_field() - oob_ip: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - oob_ip_id: ID | None = strawberry_django.filter_field() - cluster: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cluster_id: ID | None = strawberry_django.filter_field() - virtual_chassis: Annotated['VirtualChassisFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - virtual_chassis_id: ID | None = strawberry_django.filter_field() - vc_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - vc_priority: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - longitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - console_ports: Annotated['ConsolePortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - console_server_ports: Annotated['ConsoleServerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_outlets: Annotated['PowerOutletFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_ports: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interfaces: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - front_ports: Annotated['FrontPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rear_ports: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_bays: Annotated['DeviceBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - module_bays: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - modules: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - console_port_count: FilterLookup[int] | None = strawberry_django.filter_field() - console_server_port_count: FilterLookup[int] | None = strawberry_django.filter_field() - power_port_count: FilterLookup[int] | None = strawberry_django.filter_field() - power_outlet_count: FilterLookup[int] | None = strawberry_django.filter_field() - interface_count: FilterLookup[int] | None = strawberry_django.filter_field() - front_port_count: FilterLookup[int] | None = strawberry_django.filter_field() - rear_port_count: FilterLookup[int] | None = strawberry_django.filter_field() - device_bay_count: FilterLookup[int] | None = strawberry_django.filter_field() - module_bay_count: FilterLookup[int] | None = strawberry_django.filter_field() - inventory_item_count: FilterLookup[int] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.DeviceBay, lookups=True) -class DeviceBayFilterV1(ComponentModelFilterMixinV1): - installed_device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - installed_device_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.DeviceBayTemplate, lookups=True) -class DeviceBayTemplateFilterV1(ComponentTemplateFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.InventoryItemTemplate, lookups=True) -class InventoryItemTemplateFilterV1(ComponentTemplateFilterMixinV1): - parent: Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - component_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - component_id: ID | None = strawberry_django.filter_field() - role: Annotated['InventoryItemRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - part_id: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.DeviceRole, lookups=True) -class DeviceRoleFilterV1(OrganizationalModelFilterMixinV1, RenderConfigFilterMixinV1): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - vm_role: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.DeviceType, lookups=True) -class DeviceTypeFilterV1(ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1): - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - model: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - default_platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - default_platform_id: ID | None = strawberry_django.filter_field() - part_number: FilterLookup[str] | None = strawberry_django.filter_field() - u_height: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field() - is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field() - subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - front_image: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rear_image: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - console_port_templates: ( - Annotated['ConsolePortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - console_server_port_templates: ( - Annotated['ConsoleServerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - power_port_templates: ( - Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - power_outlet_templates: ( - Annotated['PowerOutletTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - interface_templates: ( - Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - front_port_templates: ( - Annotated['FrontPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - rear_port_templates: ( - Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - device_bay_templates: ( - Annotated['DeviceBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - module_bay_templates: ( - Annotated['ModuleBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - inventory_item_templates: ( - Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - console_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - console_server_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - power_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - power_outlet_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - interface_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - front_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - rear_port_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - device_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - module_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - inventory_item_template_count: FilterLookup[int] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.FrontPort, lookups=True) -class FrontPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - rear_port: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rear_port_id: ID | None = strawberry_django.filter_field() - rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.FrontPortTemplate, lookups=True) -class FrontPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - rear_port: Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rear_port_id: ID | None = strawberry_django.filter_field() - rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.MACAddress, lookups=True) -class MACAddressFilterV1(PrimaryModelFilterMixinV1): - mac_address: FilterLookup[str] | None = strawberry_django.filter_field() - assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - assigned_object_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Interface, lookups=True) -class InterfaceFilterV1(ModularComponentModelFilterMixinV1, InterfaceBaseFilterMixinV1, CabledObjectModelFilterMixinV1): - vcdcs: Annotated['VirtualDeviceContextFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - lag: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - lag_id: ID | None = strawberry_django.filter_field() - type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() - speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - wwn: FilterLookup[str] | None = strawberry_django.filter_field() - parent: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_id: ID | None = strawberry_django.filter_field() - rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - rf_channel_width: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - wireless_link: Annotated['WirelessLinkFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - wireless_link_id: ID | None = strawberry_django.filter_field() - wireless_lans: Annotated['WirelessLANFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - vrf_id: ID | None = strawberry_django.filter_field() - ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - mac_addresses: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - fhrp_group_assignments: Annotated[ - 'FHRPGroupAssignmentFilterV1', strawberry.lazy('ipam.graphql.filters_v1') - ] | None = ( - strawberry_django.filter_field() - ) - tunnel_terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - @strawberry_django.filter_field - def connected(self, queryset, value: bool, prefix: str): - if value is True: - return queryset, Q(**{f"{prefix}_path__is_active": True}) - else: - return queryset, Q(**{f"{prefix}_path__isnull": True}) | Q(**{f"{prefix}_path__is_active": False}) - - @strawberry_django.filter_field - def kind( - self, - queryset, - value: Annotated['InterfaceKindEnum', strawberry.lazy('dcim.graphql.enums')], - prefix: str - ): - if value == InterfaceKindEnum.KIND_PHYSICAL: - return queryset, ~Q(**{f"{prefix}type__in": NONCONNECTABLE_IFACE_TYPES}) - elif value == InterfaceKindEnum.KIND_VIRTUAL: - return queryset, Q(**{f"{prefix}type__in": VIRTUAL_IFACE_TYPES}) - elif value == InterfaceKindEnum.KIND_WIRELESS: - return queryset, Q(**{f"{prefix}type__in": WIRELESS_IFACE_TYPES}) - - -@strawberry_django.filter_type(models.InterfaceTemplate, lookups=True) -class InterfaceTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() - bridge: Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - bridge_id: ID | None = strawberry_django.filter_field() - poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.InventoryItem, lookups=True) -class InventoryItemFilterV1(ComponentModelFilterMixinV1): - parent: Annotated['InventoryItemFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_id: ID | None = strawberry_django.filter_field() - component_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - component_id: ID | None = strawberry_django.filter_field() - status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - role: Annotated['InventoryItemRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - part_id: FilterLookup[str] | None = strawberry_django.filter_field() - serial: FilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - discovered: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.InventoryItemRole, lookups=True) -class InventoryItemRoleFilterV1(OrganizationalModelFilterMixinV1): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Location, lookups=True) -class LocationFilterV1( - ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, NestedGroupModelFilterMixinV1 -): - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - facility: FilterLookup[str] | None = strawberry_django.filter_field() - prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Manufacturer, lookups=True) -class ManufacturerFilterV1(ContactFilterMixinV1, OrganizationalModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.Module, lookups=True) -class ModuleFilterV1(PrimaryModelFilterMixinV1, ConfigContextFilterMixinV1): - device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_id: ID | None = strawberry_django.filter_field() - module_bay: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - module_bay_id: ID | None = strawberry_django.filter_field() - module_type: Annotated['ModuleTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - module_type_id: ID | None = strawberry_django.filter_field() - status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - serial: FilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - console_ports: Annotated['ConsolePortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - console_server_ports: Annotated['ConsoleServerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_outlets: Annotated['PowerOutletFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_ports: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interfaces: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - front_ports: Annotated['FrontPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rear_ports: Annotated['RearPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_bays: Annotated['DeviceBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - module_bays: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - modules: Annotated['ModuleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ModuleBay, lookups=True) -class ModuleBayFilterV1(ModularComponentModelFilterMixinV1): - parent: Annotated['ModuleBayFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_id: ID | None = strawberry_django.filter_field() - position: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True) -class ModuleBayTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - position: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True) -class ModuleTypeProfileFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ModuleType, lookups=True) -class ModuleTypeFilterV1(ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1, WeightFilterMixinV1): - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - profile: Annotated['ModuleTypeProfileFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - profile_id: ID | None = strawberry_django.filter_field() - model: FilterLookup[str] | None = strawberry_django.filter_field() - part_number: FilterLookup[str] | None = strawberry_django.filter_field() - airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - console_port_templates: ( - Annotated['ConsolePortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - console_server_port_templates: ( - Annotated['ConsoleServerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - power_port_templates: ( - Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - power_outlet_templates: ( - Annotated['PowerOutletTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - interface_templates: ( - Annotated['InterfaceTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - front_port_templates: ( - Annotated['FrontPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - rear_port_templates: ( - Annotated['RearPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - device_bay_templates: ( - Annotated['DeviceBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - module_bay_templates: ( - Annotated['ModuleBayTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - inventory_item_templates: ( - Annotated['InventoryItemTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Platform, lookups=True) -class PlatformFilterV1(OrganizationalModelFilterMixinV1): - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - config_template: Annotated['ConfigTemplateFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - config_template_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.PowerFeed, lookups=True) -class PowerFeedFilterV1(CabledObjectModelFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - power_panel: Annotated['PowerPanelFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_panel_id: ID | None = strawberry_django.filter_field() - rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rack_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - amperage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - max_utilization: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - available_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.PowerOutlet, lookups=True) -class PowerOutletFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - power_port: Annotated['PowerPortFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True) -class PowerOutletTemplateFilterV1(ModularComponentModelFilterMixinV1): - type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - power_port: Annotated['PowerPortTemplateFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_port_id: ID | None = strawberry_django.filter_field() - feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.PowerPanel, lookups=True) -class PowerPanelFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, PrimaryModelFilterMixinV1): - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.PowerPort, lookups=True) -class PowerPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - allocated_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.PowerPortTemplate, lookups=True) -class PowerPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - allocated_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.RackType, lookups=True) -class RackTypeFilterV1(RackBaseFilterMixinV1): - form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - manufacturer: Annotated['ManufacturerFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - manufacturer_id: ID | None = strawberry_django.filter_field() - model: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Rack, lookups=True) -class RackFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, RackBaseFilterMixinV1): - form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - rack_type: Annotated['RackTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rack_type_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - facility_id: FilterLookup[str] | None = strawberry_django.filter_field() - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - location: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - role: Annotated['RackRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - serial: FilterLookup[str] | None = strawberry_django.filter_field() - asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() - airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.RackReservation, lookups=True) -class RackReservationFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - rack: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rack_id: ID | None = strawberry_django.filter_field() - units: Annotated['IntegerArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - user_id: ID | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.RackRole, lookups=True) -class RackRoleFilterV1(OrganizationalModelFilterMixinV1): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.RearPort, lookups=True) -class RearPortFilterV1(ModularComponentModelFilterMixinV1, CabledObjectModelFilterMixinV1): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.RearPortTemplate, lookups=True) -class RearPortTemplateFilterV1(ModularComponentTemplateFilterMixinV1): - type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Region, lookups=True) -class RegionFilterV1(ContactFilterMixinV1, NestedGroupModelFilterMixinV1): - prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Site, lookups=True) -class SiteFilterV1(ContactFilterMixinV1, ImageAttachmentFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() - region: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - group: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - facility: FilterLookup[str] | None = strawberry_django.filter_field() - asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - time_zone: FilterLookup[str] | None = strawberry_django.filter_field() - physical_address: FilterLookup[str] | None = strawberry_django.filter_field() - shipping_address: FilterLookup[str] | None = strawberry_django.filter_field() - latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - longitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.SiteGroup, lookups=True) -class SiteGroupFilterV1(ContactFilterMixinV1, NestedGroupModelFilterMixinV1): - prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VirtualChassis, lookups=True) -class VirtualChassisFilterV1(PrimaryModelFilterMixinV1): - master: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - master_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - domain: FilterLookup[str] | None = strawberry_django.filter_field() - members: ( - Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - member_count: FilterLookup[int] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.VirtualDeviceContext, lookups=True) -class VirtualDeviceContextFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip4_id: ID | None = strawberry_django.filter_field() - primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip6_id: ID | None = strawberry_django.filter_field() - comments: FilterLookup[str] | None = strawberry_django.filter_field() - interfaces: ( - Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() diff --git a/netbox/dcim/graphql/gfk_mixins_v1.py b/netbox/dcim/graphql/gfk_mixins_v1.py deleted file mode 100644 index 378b3a916..000000000 --- a/netbox/dcim/graphql/gfk_mixins_v1.py +++ /dev/null @@ -1,137 +0,0 @@ -from strawberry.types import Info - -from circuits.graphql.types_v1 import CircuitTerminationTypeV1, ProviderNetworkTypeV1 -from circuits.models import CircuitTermination, ProviderNetwork -from dcim.graphql.types_v1 import ( - ConsolePortTemplateTypeV1, - ConsolePortTypeV1, - ConsoleServerPortTemplateTypeV1, - ConsoleServerPortTypeV1, - FrontPortTemplateTypeV1, - FrontPortTypeV1, - InterfaceTemplateTypeV1, - InterfaceTypeV1, - PowerFeedTypeV1, - PowerOutletTemplateTypeV1, - PowerOutletTypeV1, - PowerPortTemplateTypeV1, - PowerPortTypeV1, - RearPortTemplateTypeV1, - RearPortTypeV1, -) -from dcim.models import ( - ConsolePort, - ConsolePortTemplate, - ConsoleServerPort, - ConsoleServerPortTemplate, - FrontPort, - FrontPortTemplate, - Interface, - InterfaceTemplate, - PowerFeed, - PowerOutlet, - PowerOutletTemplate, - PowerPort, - PowerPortTemplate, - RearPort, - RearPortTemplate, -) - - -class InventoryItemTemplateComponentTypeV1: - class Meta: - types = ( - ConsolePortTemplateTypeV1, - ConsoleServerPortTemplateTypeV1, - FrontPortTemplateTypeV1, - InterfaceTemplateTypeV1, - PowerOutletTemplateTypeV1, - PowerPortTemplateTypeV1, - RearPortTemplateTypeV1, - ) - - @classmethod - def resolve_type(cls, instance, info: Info): - if type(instance) is ConsolePortTemplate: - return ConsolePortTemplateTypeV1 - if type(instance) is ConsoleServerPortTemplate: - return ConsoleServerPortTemplateTypeV1 - if type(instance) is FrontPortTemplate: - return FrontPortTemplateTypeV1 - if type(instance) is InterfaceTemplate: - return InterfaceTemplateTypeV1 - if type(instance) is PowerOutletTemplate: - return PowerOutletTemplateTypeV1 - if type(instance) is PowerPortTemplate: - return PowerPortTemplateTypeV1 - if type(instance) is RearPortTemplate: - return RearPortTemplateTypeV1 - - -class InventoryItemComponentTypeV1: - class Meta: - types = ( - ConsolePortTypeV1, - ConsoleServerPortTypeV1, - FrontPortTypeV1, - InterfaceTypeV1, - PowerOutletTypeV1, - PowerPortTypeV1, - RearPortTypeV1, - ) - - @classmethod - def resolve_type(cls, instance, info: Info): - if type(instance) is ConsolePort: - return ConsolePortTypeV1 - if type(instance) is ConsoleServerPort: - return ConsoleServerPortTypeV1 - if type(instance) is FrontPort: - return FrontPortTypeV1 - if type(instance) is Interface: - return InterfaceTypeV1 - if type(instance) is PowerOutlet: - return PowerOutletTypeV1 - if type(instance) is PowerPort: - return PowerPortTypeV1 - if type(instance) is RearPort: - return RearPortTypeV1 - - -class ConnectedEndpointTypeV1: - class Meta: - types = ( - CircuitTerminationTypeV1, - ConsolePortTypeV1, - ConsoleServerPortTypeV1, - FrontPortTypeV1, - InterfaceTypeV1, - PowerFeedTypeV1, - PowerOutletTypeV1, - PowerPortTypeV1, - ProviderNetworkTypeV1, - RearPortTypeV1, - ) - - @classmethod - def resolve_type(cls, instance, info: Info): - if type(instance) is CircuitTermination: - return CircuitTerminationTypeV1 - if type(instance) is ConsolePort: - return ConsolePortTypeV1 - if type(instance) is ConsoleServerPort: - return ConsoleServerPortTypeV1 - if type(instance) is FrontPort: - return FrontPortTypeV1 - if type(instance) is Interface: - return InterfaceTypeV1 - if type(instance) is PowerFeed: - return PowerFeedTypeV1 - if type(instance) is PowerOutlet: - return PowerOutletTypeV1 - if type(instance) is PowerPort: - return PowerPortTypeV1 - if type(instance) is ProviderNetwork: - return ProviderNetworkTypeV1 - if type(instance) is RearPort: - return RearPortTypeV1 diff --git a/netbox/dcim/graphql/mixins_v1.py b/netbox/dcim/graphql/mixins_v1.py deleted file mode 100644 index 694283e3c..000000000 --- a/netbox/dcim/graphql/mixins_v1.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Annotated, List, Union - -import strawberry - -__all__ = ( - 'CabledObjectMixinV1', - 'PathEndpointMixinV1', -) - - -@strawberry.type -class CabledObjectMixinV1: - cable: Annotated["CableTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None # noqa: F821 - - link_peers: List[Annotated[Union[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - ], strawberry.union("LinkPeerType")]] - - -@strawberry.type -class PathEndpointMixinV1: - - connected_endpoints: List[Annotated[Union[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 - Annotated["VirtualCircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - Annotated["ProviderNetworkTypeV1", strawberry.lazy('circuits.graphql.types_v1')], # noqa: F821 - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], # noqa: F821 - ], strawberry.union("ConnectedEndpointTypeV1")]] diff --git a/netbox/dcim/graphql/schema_v1.py b/netbox/dcim/graphql/schema_v1.py deleted file mode 100644 index 29d2f09ba..000000000 --- a/netbox/dcim/graphql/schema_v1.py +++ /dev/null @@ -1,138 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class DCIMQueryV1: - cable: CableTypeV1 = strawberry_django.field() - cable_list: List[CableTypeV1] = strawberry_django.field() - - console_port: ConsolePortTypeV1 = strawberry_django.field() - console_port_list: List[ConsolePortTypeV1] = strawberry_django.field() - - console_port_template: ConsolePortTemplateTypeV1 = strawberry_django.field() - console_port_template_list: List[ConsolePortTemplateTypeV1] = strawberry_django.field() - - console_server_port: ConsoleServerPortTypeV1 = strawberry_django.field() - console_server_port_list: List[ConsoleServerPortTypeV1] = strawberry_django.field() - - console_server_port_template: ConsoleServerPortTemplateTypeV1 = strawberry_django.field() - console_server_port_template_list: List[ConsoleServerPortTemplateTypeV1] = strawberry_django.field() - - device: DeviceTypeV1 = strawberry_django.field() - device_list: List[DeviceTypeV1] = strawberry_django.field() - - device_bay: DeviceBayTypeV1 = strawberry_django.field() - device_bay_list: List[DeviceBayTypeV1] = strawberry_django.field() - - device_bay_template: DeviceBayTemplateTypeV1 = strawberry_django.field() - device_bay_template_list: List[DeviceBayTemplateTypeV1] = strawberry_django.field() - - device_role: DeviceRoleTypeV1 = strawberry_django.field() - device_role_list: List[DeviceRoleTypeV1] = strawberry_django.field() - - device_type: DeviceTypeTypeV1 = strawberry_django.field() - device_type_list: List[DeviceTypeTypeV1] = strawberry_django.field() - - front_port: FrontPortTypeV1 = strawberry_django.field() - front_port_list: List[FrontPortTypeV1] = strawberry_django.field() - - front_port_template: FrontPortTemplateTypeV1 = strawberry_django.field() - front_port_template_list: List[FrontPortTemplateTypeV1] = strawberry_django.field() - - mac_address: MACAddressTypeV1 = strawberry_django.field() - mac_address_list: List[MACAddressTypeV1] = strawberry_django.field() - - interface: InterfaceTypeV1 = strawberry_django.field() - interface_list: List[InterfaceTypeV1] = strawberry_django.field() - - interface_template: InterfaceTemplateTypeV1 = strawberry_django.field() - interface_template_list: List[InterfaceTemplateTypeV1] = strawberry_django.field() - - inventory_item: InventoryItemTypeV1 = strawberry_django.field() - inventory_item_list: List[InventoryItemTypeV1] = strawberry_django.field() - - inventory_item_role: InventoryItemRoleTypeV1 = strawberry_django.field() - inventory_item_role_list: List[InventoryItemRoleTypeV1] = strawberry_django.field() - - inventory_item_template: InventoryItemTemplateTypeV1 = strawberry_django.field() - inventory_item_template_list: List[InventoryItemTemplateTypeV1] = strawberry_django.field() - - location: LocationTypeV1 = strawberry_django.field() - location_list: List[LocationTypeV1] = strawberry_django.field() - - manufacturer: ManufacturerTypeV1 = strawberry_django.field() - manufacturer_list: List[ManufacturerTypeV1] = strawberry_django.field() - - module: ModuleTypeV1 = strawberry_django.field() - module_list: List[ModuleTypeV1] = strawberry_django.field() - - module_bay: ModuleBayTypeV1 = strawberry_django.field() - module_bay_list: List[ModuleBayTypeV1] = strawberry_django.field() - - module_bay_template: ModuleBayTemplateTypeV1 = strawberry_django.field() - module_bay_template_list: List[ModuleBayTemplateTypeV1] = strawberry_django.field() - - module_type_profile: ModuleTypeProfileTypeV1 = strawberry_django.field() - module_type_profile_list: List[ModuleTypeProfileTypeV1] = strawberry_django.field() - - module_type: ModuleTypeTypeV1 = strawberry_django.field() - module_type_list: List[ModuleTypeTypeV1] = strawberry_django.field() - - platform: PlatformTypeV1 = strawberry_django.field() - platform_list: List[PlatformTypeV1] = strawberry_django.field() - - power_feed: PowerFeedTypeV1 = strawberry_django.field() - power_feed_list: List[PowerFeedTypeV1] = strawberry_django.field() - - power_outlet: PowerOutletTypeV1 = strawberry_django.field() - power_outlet_list: List[PowerOutletTypeV1] = strawberry_django.field() - - power_outlet_template: PowerOutletTemplateTypeV1 = strawberry_django.field() - power_outlet_template_list: List[PowerOutletTemplateTypeV1] = strawberry_django.field() - - power_panel: PowerPanelTypeV1 = strawberry_django.field() - power_panel_list: List[PowerPanelTypeV1] = strawberry_django.field() - - power_port: PowerPortTypeV1 = strawberry_django.field() - power_port_list: List[PowerPortTypeV1] = strawberry_django.field() - - power_port_template: PowerPortTemplateTypeV1 = strawberry_django.field() - power_port_template_list: List[PowerPortTemplateTypeV1] = strawberry_django.field() - - rack_type: RackTypeTypeV1 = strawberry_django.field() - rack_type_list: List[RackTypeTypeV1] = strawberry_django.field() - - rack: RackTypeV1 = strawberry_django.field() - rack_list: List[RackTypeV1] = strawberry_django.field() - - rack_reservation: RackReservationTypeV1 = strawberry_django.field() - rack_reservation_list: List[RackReservationTypeV1] = strawberry_django.field() - - rack_role: RackRoleTypeV1 = strawberry_django.field() - rack_role_list: List[RackRoleTypeV1] = strawberry_django.field() - - rear_port: RearPortTypeV1 = strawberry_django.field() - rear_port_list: List[RearPortTypeV1] = strawberry_django.field() - - rear_port_template: RearPortTemplateTypeV1 = strawberry_django.field() - rear_port_template_list: List[RearPortTemplateTypeV1] = strawberry_django.field() - - region: RegionTypeV1 = strawberry_django.field() - region_list: List[RegionTypeV1] = strawberry_django.field() - - site: SiteTypeV1 = strawberry_django.field() - site_list: List[SiteTypeV1] = strawberry_django.field() - - site_group: SiteGroupTypeV1 = strawberry_django.field() - site_group_list: List[SiteGroupTypeV1] = strawberry_django.field() - - virtual_chassis: VirtualChassisTypeV1 = strawberry_django.field() - virtual_chassis_list: List[VirtualChassisTypeV1] = strawberry_django.field() - - virtual_device_context: VirtualDeviceContextTypeV1 = strawberry_django.field() - virtual_device_context_list: List[VirtualDeviceContextTypeV1] = strawberry_django.field() diff --git a/netbox/dcim/graphql/types_v1.py b/netbox/dcim/graphql/types_v1.py deleted file mode 100644 index ab0ba5f07..000000000 --- a/netbox/dcim/graphql/types_v1.py +++ /dev/null @@ -1,903 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from core.graphql.mixins_v1 import ChangelogMixinV1 -from dcim import models -from extras.graphql.mixins_v1 import ( - ConfigContextMixinV1, - ContactsMixinV1, - ImageAttachmentsMixinV1, -) -from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 -from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 -) -from users.graphql.mixins_v1 import OwnerMixinV1 -from .filters_v1 import * -from .mixins_v1 import CabledObjectMixinV1, PathEndpointMixinV1 - -if TYPE_CHECKING: - from circuits.graphql.types_v1 import CircuitTerminationTypeV1 - from extras.graphql.types_v1 import ConfigTemplateTypeV1 - from ipam.graphql.types_v1 import ( - ASNTypeV1, - IPAddressTypeV1, - PrefixTypeV1, - ServiceTypeV1, - VLANTranslationPolicyTypeV1, - VLANTypeV1, - VRFTypeV1, - ) - from tenancy.graphql.types_v1 import TenantTypeV1 - from users.graphql.types_v1 import UserTypeV1 - from virtualization.graphql.types_v1 import ClusterTypeV1, VMInterfaceTypeV1, VirtualMachineTypeV1 - from vpn.graphql.types_v1 import L2VPNTerminationTypeV1 - from wireless.graphql.types_v1 import WirelessLANTypeV1, WirelessLinkTypeV1 - -__all__ = ( - 'CableTypeV1', - 'ComponentTypeV1', - 'ConsolePortTypeV1', - 'ConsolePortTemplateTypeV1', - 'ConsoleServerPortTypeV1', - 'ConsoleServerPortTemplateTypeV1', - 'DeviceTypeV1', - 'DeviceBayTypeV1', - 'DeviceBayTemplateTypeV1', - 'DeviceRoleTypeV1', - 'DeviceTypeTypeV1', - 'FrontPortTypeV1', - 'FrontPortTemplateTypeV1', - 'InterfaceTypeV1', - 'InterfaceTemplateTypeV1', - 'InventoryItemTypeV1', - 'InventoryItemRoleTypeV1', - 'InventoryItemTemplateTypeV1', - 'LocationTypeV1', - 'MACAddressTypeV1', - 'ManufacturerTypeV1', - 'ModularComponentTypeV1', - 'ModuleTypeV1', - 'ModuleBayTypeV1', - 'ModuleBayTemplateTypeV1', - 'ModuleTypeProfileTypeV1', - 'ModuleTypeTypeV1', - 'PlatformTypeV1', - 'PowerFeedTypeV1', - 'PowerOutletTypeV1', - 'PowerOutletTemplateTypeV1', - 'PowerPanelTypeV1', - 'PowerPortTypeV1', - 'PowerPortTemplateTypeV1', - 'RackTypeV1', - 'RackReservationTypeV1', - 'RackRoleTypeV1', - 'RackTypeTypeV1', - 'RearPortTypeV1', - 'RearPortTemplateTypeV1', - 'RegionTypeV1', - 'SiteTypeV1', - 'SiteGroupTypeV1', - 'VirtualChassisTypeV1', - 'VirtualDeviceContextTypeV1', -) - - -# -# Base types -# - - -@strawberry.type -class ComponentTypeV1(OwnerMixinV1, NetBoxObjectTypeV1): - """ - Base type for device/VM components - """ - device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - -@strawberry.type -class ModularComponentTypeV1(ComponentTypeV1): - module: Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - -@strawberry.type -class ComponentTemplateTypeV1( - ChangelogMixinV1, - BaseObjectTypeV1 -): - """ - Base type for device/VM components - """ - device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - -@strawberry.type -class ModularComponentTemplateTypeV1(ComponentTemplateTypeV1): - """ - Base type for ComponentTemplateModel which supports optional assignment to a ModuleType. - """ - device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - module_type: Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - -# -# Model types -# - - -@strawberry_django.type( - models.CableTermination, - exclude=['termination_type', 'termination_id', '_device', '_rack', '_location', '_site'], - filters=CableTerminationFilterV1, - pagination=True -) -class CableTerminationTypeV1(NetBoxObjectTypeV1): - cable: Annotated["CableTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - termination: Annotated[Union[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("CableTerminationTerminationTypeV1")] | None - - -@strawberry_django.type( - models.Cable, - fields='__all__', - filters=CableFilterV1, - pagination=True -) -class CableTypeV1(PrimaryObjectTypeV1): - color: str - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - terminations: List[CableTerminationTypeV1] - - a_terminations: List[Annotated[Union[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("CableTerminationTerminationTypeV1")]] - - b_terminations: List[Annotated[Union[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')], - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("CableTerminationTerminationTypeV1")]] - - -@strawberry_django.type( - models.ConsolePort, - exclude=['_path'], - filters=ConsolePortFilterV1, - pagination=True -) -class ConsolePortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): - pass - - -@strawberry_django.type( - models.ConsolePortTemplate, - fields='__all__', - filters=ConsolePortTemplateFilterV1, - pagination=True -) -class ConsolePortTemplateTypeV1(ModularComponentTemplateTypeV1): - pass - - -@strawberry_django.type( - models.ConsoleServerPort, - exclude=['_path'], - filters=ConsoleServerPortFilterV1, - pagination=True -) -class ConsoleServerPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): - pass - - -@strawberry_django.type( - models.ConsoleServerPortTemplate, - fields='__all__', - filters=ConsoleServerPortTemplateFilterV1, - pagination=True -) -class ConsoleServerPortTemplateTypeV1(ModularComponentTemplateTypeV1): - pass - - -@strawberry_django.type( - models.Device, - fields='__all__', - filters=DeviceFilterV1, - pagination=True -) -class DeviceTypeV1(ConfigContextMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): - console_port_count: BigInt - console_server_port_count: BigInt - power_port_count: BigInt - power_outlet_count: BigInt - interface_count: BigInt - front_port_count: BigInt - rear_port_count: BigInt - device_bay_count: BigInt - module_bay_count: BigInt - inventory_item_count: BigInt - config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None - device_type: Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - role: Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - oob_ip: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - cluster: Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - virtual_chassis: Annotated["VirtualChassisTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - modules: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - rearports: List[Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleports: List[Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerports: List[Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleserverports: List[Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - devicebays: List[Annotated["DeviceBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - modulebays: List[Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - inventoryitems: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - vdcs: List[Annotated["VirtualDeviceContextTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def vc_master_for(self) -> Annotated["VirtualChassisTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.vc_master_for if hasattr(self, 'vc_master_for') else None - - @strawberry_django.field - def parent_bay(self) -> Annotated["DeviceBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent_bay if hasattr(self, 'parent_bay') else None - - -@strawberry_django.type( - models.DeviceBay, - fields='__all__', - filters=DeviceBayFilterV1, - pagination=True -) -class DeviceBayTypeV1(ComponentTypeV1): - installed_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - -@strawberry_django.type( - models.DeviceBayTemplate, - fields='__all__', - filters=DeviceBayTemplateFilterV1, - pagination=True -) -class DeviceBayTemplateTypeV1(ComponentTemplateTypeV1): - pass - - -@strawberry_django.type( - models.InventoryItemTemplate, - exclude=['component_type', 'component_id', 'parent'], - filters=InventoryItemTemplateFilterV1, - pagination=True -) -class InventoryItemTemplateTypeV1(ComponentTemplateTypeV1): - role: Annotated["InventoryItemRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - @strawberry_django.field - def parent(self) -> Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent - - child_items: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - component: Annotated[Union[ - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("InventoryItemTemplateComponentTypeV1")] | None - - -@strawberry_django.type( - models.DeviceRole, - fields='__all__', - filters=DeviceRoleFilterV1, - pagination=True -) -class DeviceRoleTypeV1(OrganizationalObjectTypeV1): - parent: Annotated['DeviceRoleTypeV1', strawberry.lazy('dcim.graphql.types_v1')] | None - children: List[Annotated['DeviceRoleTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - color: str - config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None - - virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.DeviceType, - fields='__all__', - filters=DeviceTypeFilterV1, - pagination=True -) -class DeviceTypeTypeV1(PrimaryObjectTypeV1): - console_port_template_count: BigInt - console_server_port_template_count: BigInt - power_port_template_count: BigInt - power_outlet_template_count: BigInt - interface_template_count: BigInt - front_port_template_count: BigInt - rear_port_template_count: BigInt - device_bay_template_count: BigInt - module_bay_template_count: BigInt - inventory_item_template_count: BigInt - front_image: strawberry_django.fields.types.DjangoImageType | None - rear_image: strawberry_django.fields.types.DjangoImageType | None - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - default_platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - frontporttemplates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - modulebaytemplates: List[Annotated["ModuleBayTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - instances: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - poweroutlettemplates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerporttemplates: List[Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - inventoryitemtemplates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - rearporttemplates: List[Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleserverporttemplates: List[ - Annotated["ConsoleServerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - ] - interfacetemplates: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - devicebaytemplates: List[Annotated["DeviceBayTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleporttemplates: List[Annotated["ConsolePortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.FrontPort, - fields='__all__', - filters=FrontPortFilterV1, - pagination=True -) -class FrontPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1): - color: str - rear_port: Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - -@strawberry_django.type( - models.FrontPortTemplate, - fields='__all__', - filters=FrontPortTemplateFilterV1, - pagination=True -) -class FrontPortTemplateTypeV1(ModularComponentTemplateTypeV1): - color: str - rear_port: Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - -@strawberry_django.type( - models.MACAddress, - exclude=['assigned_object_type', 'assigned_object_id'], - filters=MACAddressFilterV1, - pagination=True -) -class MACAddressTypeV1(PrimaryObjectTypeV1): - mac_address: str - - @strawberry_django.field - def assigned_object(self) -> Annotated[Union[ - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - ], strawberry.union("MACAddressAssignmentTypeV1")] | None: - return self.assigned_object - - -@strawberry_django.type( - models.Interface, - exclude=['_path'], - filters=InterfaceFilterV1, - pagination=True -) -class InterfaceTypeV1(IPAddressesMixinV1, ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): - _name: str - wwn: str | None - parent: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - bridge: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - lag: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - wireless_link: Annotated["WirelessLinkTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None - untagged_vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - primary_mac_address: Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - qinq_svlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - vlan_translation_policy: Annotated["VLANTranslationPolicyTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - l2vpn_termination: Annotated["L2VPNTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None - - vdcs: List[Annotated["VirtualDeviceContextTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - tagged_vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - bridge_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - wireless_lans: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] - member_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - child_interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - mac_addresses: List[Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.InterfaceTemplate, - fields='__all__', - filters=InterfaceTemplateFilterV1, - pagination=True -) -class InterfaceTemplateTypeV1(ModularComponentTemplateTypeV1): - _name: str - bridge: Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - bridge_interfaces: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.InventoryItem, - exclude=['component_type', 'component_id', 'parent'], - filters=InventoryItemFilterV1, - pagination=True -) -class InventoryItemTypeV1(ComponentTypeV1): - role: Annotated["InventoryItemRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - child_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def parent(self) -> Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent - - component: Annotated[Union[ - Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("InventoryItemComponentTypeV1")] | None - - -@strawberry_django.type( - models.InventoryItemRole, - fields='__all__', - filters=InventoryItemRoleFilterV1, - pagination=True -) -class InventoryItemRoleTypeV1(OrganizationalObjectTypeV1): - color: str - - inventory_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - inventory_item_templates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.Location, - # fields='__all__', - exclude=['parent'], # bug - temp - filters=LocationFilterV1, - pagination=True -) -class LocationTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): - site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - parent: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - powerpanel_set: List[Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - children: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: - return self.cluster_set.all() - - @strawberry_django.field - def circuit_terminations(self) -> List[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - ]: - return self.circuit_terminations.all() - - -@strawberry_django.type( - models.Manufacturer, - fields='__all__', - filters=ManufacturerFilterV1, - pagination=True -) -class ManufacturerTypeV1(OrganizationalObjectTypeV1, ContactsMixinV1): - - platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - device_types: List[Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - inventory_item_templates: List[Annotated["InventoryItemTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - inventory_items: List[Annotated["InventoryItemTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - module_types: List[Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.Module, - fields='__all__', - filters=ModuleFilterV1, - pagination=True -) -class ModuleTypeV1(PrimaryObjectTypeV1): - device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - module_bay: Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - module_type: Annotated["ModuleTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerports: List[Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleserverports: List[Annotated["ConsoleServerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleports: List[Annotated["ConsolePortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - rearports: List[Annotated["RearPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.ModuleBay, - # fields='__all__', - exclude=['parent'], - filters=ModuleBayFilterV1, - pagination=True -) -class ModuleBayTypeV1(ModularComponentTypeV1): - - installed_module: Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - children: List[Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def parent(self) -> Annotated["ModuleBayTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent - - -@strawberry_django.type( - models.ModuleBayTemplate, - fields='__all__', - filters=ModuleBayTemplateFilterV1, - pagination=True -) -class ModuleBayTemplateTypeV1(ModularComponentTemplateTypeV1): - pass - - -@strawberry_django.type( - models.ModuleTypeProfile, - fields='__all__', - filters=ModuleTypeProfileFilterV1, - pagination=True -) -class ModuleTypeProfileTypeV1(PrimaryObjectTypeV1): - module_types: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.ModuleType, - fields='__all__', - filters=ModuleTypeFilterV1, - pagination=True -) -class ModuleTypeTypeV1(PrimaryObjectTypeV1): - profile: Annotated["ModuleTypeProfileTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - frontporttemplates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleserverporttemplates: List[ - Annotated["ConsoleServerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - ] - interfacetemplates: List[Annotated["InterfaceTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerporttemplates: List[Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - poweroutlettemplates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - rearporttemplates: List[Annotated["RearPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - instances: List[Annotated["ModuleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - consoleporttemplates: List[Annotated["ConsolePortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.Platform, - fields='__all__', - filters=PlatformFilterV1, - pagination=True -) -class PlatformTypeV1(OrganizationalObjectTypeV1): - parent: Annotated['PlatformTypeV1', strawberry.lazy('dcim.graphql.types_v1')] | None - children: List[Annotated['PlatformTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None - - virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.PowerFeed, - exclude=['_path'], - filters=PowerFeedFilterV1, - pagination=True -) -class PowerFeedTypeV1(CabledObjectMixinV1, PathEndpointMixinV1, PrimaryObjectTypeV1): - power_panel: Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - -@strawberry_django.type( - models.PowerOutlet, - exclude=['_path'], - filters=PowerOutletFilterV1, - pagination=True -) -class PowerOutletTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): - power_port: Annotated["PowerPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - color: str - - -@strawberry_django.type( - models.PowerOutletTemplate, - fields='__all__', - filters=PowerOutletTemplateFilterV1, - pagination=True -) -class PowerOutletTemplateTypeV1(ModularComponentTemplateTypeV1): - power_port: Annotated["PowerPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - color: str - - -@strawberry_django.type( - models.PowerPanel, - fields='__all__', - filters=PowerPanelFilterV1, - pagination=True -) -class PowerPanelTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - powerfeeds: List[Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.PowerPort, - exclude=['_path'], - filters=PowerPortFilterV1, - pagination=True -) -class PowerPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1, PathEndpointMixinV1): - - poweroutlets: List[Annotated["PowerOutletTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.PowerPortTemplate, - fields='__all__', - filters=PowerPortTemplateFilterV1, - pagination=True -) -class PowerPortTemplateTypeV1(ModularComponentTemplateTypeV1): - poweroutlet_templates: List[Annotated["PowerOutletTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.RackType, - fields='__all__', - filters=RackTypeFilterV1, - pagination=True -) -class RackTypeTypeV1(PrimaryObjectTypeV1): - manufacturer: Annotated["ManufacturerTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - - -@strawberry_django.type( - models.Rack, - fields='__all__', - filters=RackFilterV1, - pagination=True -) -class RackTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): - site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - location: Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - role: Annotated["RackRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - rack_type: Annotated["RackTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - reservations: List[Annotated["RackReservationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerfeeds: List[Annotated["PowerFeedTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.RackReservation, - fields='__all__', - filters=RackReservationFilterV1, - pagination=True -) -class RackReservationTypeV1(PrimaryObjectTypeV1): - units: List[int] - rack: Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] - - -@strawberry_django.type( - models.RackRole, - fields='__all__', - filters=RackRoleFilterV1, - pagination=True -) -class RackRoleTypeV1(OrganizationalObjectTypeV1): - color: str - - racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.RearPort, - fields='__all__', - filters=RearPortFilterV1, - pagination=True -) -class RearPortTypeV1(ModularComponentTypeV1, CabledObjectMixinV1): - color: str - - frontports: List[Annotated["FrontPortTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.RearPortTemplate, - fields='__all__', - filters=RearPortTemplateFilterV1, - pagination=True -) -class RearPortTemplateTypeV1(ModularComponentTemplateTypeV1): - color: str - - frontport_templates: List[Annotated["FrontPortTemplateTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.Region, - exclude=['parent'], - filters=RegionFilterV1, - pagination=True -) -class RegionTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): - - sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - children: List[Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def parent(self) -> Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent - - @strawberry_django.field - def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: - return self.cluster_set.all() - - @strawberry_django.field - def circuit_terminations(self) -> List[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - ]: - return self.circuit_terminations.all() - - -@strawberry_django.type( - models.Site, - fields='__all__', - filters=SiteFilterV1, - pagination=True -) -class SiteTypeV1(VLANGroupsMixinV1, ImageAttachmentsMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): - time_zone: str | None - region: Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - group: Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - racks: List[Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - cabletermination_set: List[Annotated["CableTerminationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - powerpanel_set: List[Annotated["PowerPanelTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - locations: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - circuit_terminations: List[Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')]] - clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - @strawberry_django.field - def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: - return self.cluster_set.all() - - @strawberry_django.field - def circuit_terminations(self) -> List[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - ]: - return self.circuit_terminations.all() - - -@strawberry_django.type( - models.SiteGroup, - exclude=['parent'], # bug - temp - filters=SiteGroupFilterV1, - pagination=True -) -class SiteGroupTypeV1(VLANGroupsMixinV1, ContactsMixinV1, OrganizationalObjectTypeV1): - - sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - children: List[Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def parent(self) -> Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None: - return self.parent - - @strawberry_django.field - def clusters(self) -> List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]]: - return self.cluster_set.all() - - @strawberry_django.field - def circuit_terminations(self) -> List[ - Annotated["CircuitTerminationTypeV1", strawberry.lazy('circuits.graphql.types_v1')] - ]: - return self.circuit_terminations.all() - - -@strawberry_django.type( - models.VirtualChassis, - fields='__all__', - filters=VirtualChassisFilterV1, - pagination=True -) -class VirtualChassisTypeV1(PrimaryObjectTypeV1): - member_count: BigInt - master: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - - members: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.VirtualDeviceContext, - fields='__all__', - filters=VirtualDeviceContextFilterV1, - pagination=True -) -class VirtualDeviceContextTypeV1(PrimaryObjectTypeV1): - device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] diff --git a/netbox/extras/graphql/filter_mixins_v1.py b/netbox/extras/graphql/filter_mixins_v1.py deleted file mode 100644 index 48611cc83..000000000 --- a/netbox/extras/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,52 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import JSONFilter - from .filters_v1 import * - -__all__ = ( - 'CustomFieldsFilterMixinV1', - 'JournalEntriesFilterMixinV1', - 'TagsFilterMixinV1', - 'ConfigContextFilterMixinV1', - 'TagBaseFilterMixinV1', -) - - -@dataclass -class CustomFieldsFilterMixinV1(BaseFilterMixinV1): - custom_field_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class JournalEntriesFilterMixinV1(BaseFilterMixinV1): - journal_entries: Annotated['JournalEntryFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class TagsFilterMixinV1(BaseFilterMixinV1): - tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field() - - -@dataclass -class ConfigContextFilterMixinV1(BaseFilterMixinV1): - local_context_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class TagBaseFilterMixinV1(BaseFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/filters_v1.py b/netbox/extras/graphql/filters_v1.py deleted file mode 100644 index b903fee34..000000000 --- a/netbox/extras/graphql/filters_v1.py +++ /dev/null @@ -1,357 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 -from extras import models -from extras.graphql.filter_mixins_v1 import TagBaseFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import PrimaryModelFilterMixinV1, SyncedDataFilterMixinV1 - -if TYPE_CHECKING: - from core.graphql.filters_v1 import ContentTypeFilterV1 - from dcim.graphql.filters_v1 import ( - DeviceRoleFilterV1, - DeviceTypeFilterV1, - LocationFilterV1, - PlatformFilterV1, - RegionFilterV1, - SiteFilterV1, - SiteGroupFilterV1, - ) - from tenancy.graphql.filters_v1 import TenantFilterV1, TenantGroupFilterV1 - from netbox.graphql.enums import ColorEnum - from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter - from virtualization.graphql.filters_v1 import ClusterFilterV1, ClusterGroupFilterV1, ClusterTypeFilterV1 - from .enums import * - -__all__ = ( - 'ConfigContextFilterV1', - 'ConfigContextProfileFilterV1', - 'ConfigTemplateFilterV1', - 'CustomFieldFilterV1', - 'CustomFieldChoiceSetFilterV1', - 'CustomLinkFilterV1', - 'EventRuleFilterV1', - 'ExportTemplateFilterV1', - 'ImageAttachmentFilterV1', - 'JournalEntryFilterV1', - 'NotificationGroupFilterV1', - 'SavedFilterFilterV1', - 'TableConfigFilterV1', - 'TagFilterV1', - 'WebhookFilterV1', -) - - -@strawberry_django.filter_type(models.ConfigContext, lookups=True) -class ConfigContextFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - description: FilterLookup[str] | None = strawberry_django.filter_field() - is_active: FilterLookup[bool] | None = strawberry_django.filter_field() - regions: Annotated['RegionFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - site_groups: Annotated['SiteGroupFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - sites: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - locations: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_types: Annotated['DeviceTypeFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - roles: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - platforms: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cluster_types: Annotated['ClusterTypeFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cluster_groups: Annotated['ClusterGroupFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - clusters: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tenant_groups: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tenant_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - tenants: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tags: Annotated['TagFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ConfigContextProfile, lookups=True) -class ConfigContextProfileFilterV1(SyncedDataFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] = strawberry_django.filter_field() - description: FilterLookup[str] = strawberry_django.filter_field() - tags: Annotated['TagFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ConfigTemplate, lookups=True) -class ConfigTemplateFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - template_code: FilterLookup[str] | None = strawberry_django.filter_field() - environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - mime_type: FilterLookup[str] | None = strawberry_django.filter_field() - file_name: FilterLookup[str] | None = strawberry_django.filter_field() - file_extension: FilterLookup[str] | None = strawberry_django.filter_field() - as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.CustomField, lookups=True) -class CustomFieldFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - object_types: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - related_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - label: FilterLookup[str] | None = strawberry_django.filter_field() - group_name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - required: FilterLookup[bool] | None = strawberry_django.filter_field() - unique: FilterLookup[bool] | None = strawberry_django.filter_field() - search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - default: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - related_object_filter: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - validation_minimum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - validation_regex: FilterLookup[str] | None = strawberry_django.filter_field() - choice_set: Annotated['CustomFieldChoiceSetFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - choice_set_id: ID | None = strawberry_django.filter_field() - ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field() - comments: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True) -class CustomFieldChoiceSetFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - order_alphabetically: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.CustomLink, lookups=True) -class CustomLinkFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - link_text: FilterLookup[str] | None = strawberry_django.filter_field() - link_url: FilterLookup[str] | None = strawberry_django.filter_field() - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - group_name: FilterLookup[str] | None = strawberry_django.filter_field() - button_class: Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - new_window: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ExportTemplate, lookups=True) -class ExportTemplateFilterV1(BaseObjectTypeFilterMixinV1, SyncedDataFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - template_code: FilterLookup[str] | None = strawberry_django.filter_field() - environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - mime_type: FilterLookup[str] | None = strawberry_django.filter_field() - file_name: FilterLookup[str] | None = strawberry_django.filter_field() - file_extension: FilterLookup[str] | None = strawberry_django.filter_field() - as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ImageAttachment, lookups=True) -class ImageAttachmentFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - object_id: ID | None = strawberry_django.filter_field() - image_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - image_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.JournalEntry, lookups=True) -class JournalEntryFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - assigned_object_type_id: ID | None = strawberry_django.filter_field() - assigned_object_id: ID | None = strawberry_django.filter_field() - created_by: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - comments: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.NotificationGroup, lookups=True) -class NotificationGroupFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - users: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.SavedFilter, lookups=True) -class SavedFilterFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - user_id: ID | None = strawberry_django.filter_field() - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - shared: FilterLookup[bool] | None = strawberry_django.filter_field() - parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.TableConfig, lookups=True) -class TableConfigFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - user: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - user_id: ID | None = strawberry_django.filter_field() - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - shared: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Tag, lookups=True) -class TagFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1, TagBaseFilterMixinV1): - color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Webhook, lookups=True) -class WebhookFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - payload_url: FilterLookup[str] | None = strawberry_django.filter_field() - http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - http_content_type: FilterLookup[str] | None = strawberry_django.filter_field() - additional_headers: FilterLookup[str] | None = strawberry_django.filter_field() - body_template: FilterLookup[str] | None = strawberry_django.filter_field() - secret: FilterLookup[str] | None = strawberry_django.filter_field() - ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field() - ca_file_path: FilterLookup[str] | None = strawberry_django.filter_field() - events: Annotated['EventRuleFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.EventRule, lookups=True) -class EventRuleFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - action_object_type: FilterLookup[str] | None = strawberry_django.filter_field() - action_object_type_id: ID | None = strawberry_django.filter_field() - action_object_id: ID | None = strawberry_django.filter_field() - action_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - comments: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/mixins_v1.py b/netbox/extras/graphql/mixins_v1.py deleted file mode 100644 index 4f56549d7..000000000 --- a/netbox/extras/graphql/mixins_v1.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import TYPE_CHECKING, Annotated, List - -import strawberry -import strawberry_django -from strawberry.types import Info - -__all__ = ( - 'ConfigContextMixinV1', - 'ContactsMixinV1', - 'CustomFieldsMixinV1', - 'ImageAttachmentsMixinV1', - 'JournalEntriesMixinV1', - 'TagsMixinV1', -) - -if TYPE_CHECKING: - from .types_v1 import ImageAttachmentTypeV1, JournalEntryTypeV1, TagTypeV1 - from tenancy.graphql.types_v1 import ContactAssignmentTypeV1 - - -@strawberry.type -class ConfigContextMixinV1: - - @strawberry_django.field - def config_context(self) -> strawberry.scalars.JSON: - return self.get_config_context() - - -@strawberry.type -class CustomFieldsMixinV1: - - @strawberry_django.field - def custom_fields(self) -> strawberry.scalars.JSON: - return self.custom_field_data - - -@strawberry.type -class ImageAttachmentsMixinV1: - - @strawberry_django.field - def image_attachments(self, info: Info) -> List[Annotated['ImageAttachmentTypeV1', strawberry.lazy('.types_v1')]]: - return self.images.restrict(info.context.request.user, 'view') - - -@strawberry.type -class JournalEntriesMixinV1: - - @strawberry_django.field - def journal_entries(self, info: Info) -> List[Annotated['JournalEntryTypeV1', strawberry.lazy('.types_v1')]]: - return self.journal_entries.all() - - -@strawberry.type -class TagsMixinV1: - - tags: List[Annotated['TagTypeV1', strawberry.lazy('.types_v1')]] - - -@strawberry.type -class ContactsMixinV1: - - contacts: List[Annotated['ContactAssignmentTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] diff --git a/netbox/extras/graphql/schema_v1.py b/netbox/extras/graphql/schema_v1.py deleted file mode 100644 index 3a2757105..000000000 --- a/netbox/extras/graphql/schema_v1.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class ExtrasQueryV1: - config_context: ConfigContextTypeV1 = strawberry_django.field() - config_context_list: List[ConfigContextTypeV1] = strawberry_django.field() - - config_context_profile: ConfigContextProfileTypeV1 = strawberry_django.field() - config_context_profile_list: List[ConfigContextProfileTypeV1] = strawberry_django.field() - - config_template: ConfigTemplateTypeV1 = strawberry_django.field() - config_template_list: List[ConfigTemplateTypeV1] = strawberry_django.field() - - custom_field: CustomFieldTypeV1 = strawberry_django.field() - custom_field_list: List[CustomFieldTypeV1] = strawberry_django.field() - - custom_field_choice_set: CustomFieldChoiceSetTypeV1 = strawberry_django.field() - custom_field_choice_set_list: List[CustomFieldChoiceSetTypeV1] = strawberry_django.field() - - custom_link: CustomLinkTypeV1 = strawberry_django.field() - custom_link_list: List[CustomLinkTypeV1] = strawberry_django.field() - - export_template: ExportTemplateTypeV1 = strawberry_django.field() - export_template_list: List[ExportTemplateTypeV1] = strawberry_django.field() - - image_attachment: ImageAttachmentTypeV1 = strawberry_django.field() - image_attachment_list: List[ImageAttachmentTypeV1] = strawberry_django.field() - - saved_filter: SavedFilterTypeV1 = strawberry_django.field() - saved_filter_list: List[SavedFilterTypeV1] = strawberry_django.field() - - table_config: TableConfigTypeV1 = strawberry_django.field() - table_config_list: List[TableConfigTypeV1] = strawberry_django.field() - - journal_entry: JournalEntryTypeV1 = strawberry_django.field() - journal_entry_list: List[JournalEntryTypeV1] = strawberry_django.field() - - notification: NotificationTypeV1 = strawberry_django.field() - notification_list: List[NotificationTypeV1] = strawberry_django.field() - - notification_group: NotificationGroupTypeV1 = strawberry_django.field() - notification_group_list: List[NotificationGroupTypeV1] = strawberry_django.field() - - subscription: SubscriptionTypeV1 = strawberry_django.field() - subscription_list: List[SubscriptionTypeV1] = strawberry_django.field() - - tag: TagTypeV1 = strawberry_django.field() - tag_list: List[TagTypeV1] = strawberry_django.field() - - webhook: WebhookTypeV1 = strawberry_django.field() - webhook_list: List[WebhookTypeV1] = strawberry_django.field() - - event_rule: EventRuleTypeV1 = strawberry_django.field() - event_rule_list: List[EventRuleTypeV1] = strawberry_django.field() diff --git a/netbox/extras/graphql/types_v1.py b/netbox/extras/graphql/types_v1.py deleted file mode 100644 index 1693fe1da..000000000 --- a/netbox/extras/graphql/types_v1.py +++ /dev/null @@ -1,240 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING - -import strawberry -import strawberry_django - -from core.graphql.mixins_v1 import SyncedDataMixinV1 -from extras import models -from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1 -from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, ContentTypeTypeV1, ObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 -) -from users.graphql.mixins_v1 import OwnerMixinV1 -from .filters_v1 import * - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import ( - DeviceRoleTypeV1, - DeviceTypeV1, - DeviceTypeTypeV1, - LocationTypeV1, - PlatformTypeV1, - RegionTypeV1, - SiteGroupTypeV1, - SiteTypeV1, - ) - from tenancy.graphql.types_v1 import TenantGroupTypeV1, TenantTypeV1 - from users.graphql.types_v1 import GroupTypeV1, UserTypeV1 - from virtualization.graphql.types_v1 import ( - ClusterGroupTypeV1, ClusterTypeV1, ClusterTypeTypeV1, VirtualMachineTypeV1 - ) - -__all__ = ( - 'ConfigContextProfileTypeV1', - 'ConfigContextTypeV1', - 'ConfigTemplateTypeV1', - 'CustomFieldChoiceSetTypeV1', - 'CustomFieldTypeV1', - 'CustomLinkTypeV1', - 'EventRuleTypeV1', - 'ExportTemplateTypeV1', - 'ImageAttachmentTypeV1', - 'JournalEntryTypeV1', - 'NotificationGroupTypeV1', - 'NotificationTypeV1', - 'SavedFilterTypeV1', - 'SubscriptionTypeV1', - 'TableConfigTypeV1', - 'TagTypeV1', - 'WebhookTypeV1', -) - - -@strawberry_django.type( - models.ConfigContextProfile, - fields='__all__', - filters=ConfigContextProfileFilterV1, - pagination=True -) -class ConfigContextProfileTypeV1(SyncedDataMixinV1, PrimaryObjectTypeV1): - pass - - -@strawberry_django.type( - models.ConfigContext, - fields='__all__', - filters=ConfigContextFilterV1, - pagination=True -) -class ConfigContextTypeV1(SyncedDataMixinV1, OwnerMixinV1, ObjectTypeV1): - profile: ConfigContextProfileTypeV1 | None - roles: List[Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - device_types: List[Annotated["DeviceTypeTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - tags: List[Annotated["TagTypeV1", strawberry.lazy('extras.graphql.types_v1')]] - platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - regions: List[Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - cluster_groups: List[Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - tenant_groups: List[Annotated["TenantGroupTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] - cluster_types: List[Annotated["ClusterTypeTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - locations: List[Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - sites: List[Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - tenants: List[Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] - site_groups: List[Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.ConfigTemplate, - fields='__all__', - filters=ConfigTemplateFilterV1, - pagination=True -) -class ConfigTemplateTypeV1(SyncedDataMixinV1, OwnerMixinV1, TagsMixinV1, ObjectTypeV1): - virtualmachines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - platforms: List[Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - device_roles: List[Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.CustomField, - fields='__all__', - filters=CustomFieldFilterV1, - pagination=True -) -class CustomFieldTypeV1(OwnerMixinV1, ObjectTypeV1): - related_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None - choice_set: Annotated["CustomFieldChoiceSetTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None - - -@strawberry_django.type( - models.CustomFieldChoiceSet, - exclude=['extra_choices'], - filters=CustomFieldChoiceSetFilterV1, - pagination=True -) -class CustomFieldChoiceSetTypeV1(OwnerMixinV1, ObjectTypeV1): - - choices_for: List[Annotated["CustomFieldTypeV1", strawberry.lazy('extras.graphql.types_v1')]] - extra_choices: List[List[str]] | None - - -@strawberry_django.type( - models.CustomLink, - fields='__all__', - filters=CustomLinkFilterV1, - pagination=True -) -class CustomLinkTypeV1(OwnerMixinV1, ObjectTypeV1): - pass - - -@strawberry_django.type( - models.ExportTemplate, - fields='__all__', - filters=ExportTemplateFilterV1, - pagination=True -) -class ExportTemplateTypeV1(SyncedDataMixinV1, OwnerMixinV1, ObjectTypeV1): - pass - - -@strawberry_django.type( - models.ImageAttachment, - fields='__all__', - filters=ImageAttachmentFilterV1, - pagination=True -) -class ImageAttachmentTypeV1(BaseObjectTypeV1): - object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None - - -@strawberry_django.type( - models.JournalEntry, - fields='__all__', - filters=JournalEntryFilterV1, - pagination=True -) -class JournalEntryTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): - assigned_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None - created_by: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Notification, - # filters=NotificationFilter - pagination=True -) -class NotificationTypeV1(ObjectTypeV1): - user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None - - -@strawberry_django.type( - models.NotificationGroup, - filters=NotificationGroupFilterV1, - pagination=True -) -class NotificationGroupTypeV1(ObjectTypeV1): - users: List[Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')]] - groups: List[Annotated["GroupTypeV1", strawberry.lazy('users.graphql.types_v1')]] - - -@strawberry_django.type( - models.SavedFilter, - exclude=['content_types',], - filters=SavedFilterFilterV1, - pagination=True -) -class SavedFilterTypeV1(OwnerMixinV1, ObjectTypeV1): - user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Subscription, - # filters=NotificationFilter - pagination=True -) -class SubscriptionTypeV1(ObjectTypeV1): - user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None - - -@strawberry_django.type( - models.TableConfig, - fields='__all__', - filters=TableConfigFilterV1, - pagination=True -) -class TableConfigTypeV1(ObjectTypeV1): - user: Annotated["UserTypeV1", strawberry.lazy('users.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Tag, - exclude=['extras_taggeditem_items', ], - filters=TagFilterV1, - pagination=True -) -class TagTypeV1(OwnerMixinV1, ObjectTypeV1): - color: str - - object_types: List[ContentTypeTypeV1] - - -@strawberry_django.type( - models.Webhook, - exclude=['content_types',], - filters=WebhookFilterV1, - pagination=True -) -class WebhookTypeV1(OrganizationalObjectTypeV1): - pass - - -@strawberry_django.type( - models.EventRule, - exclude=['content_types',], - filters=EventRuleFilterV1, - pagination=True -) -class EventRuleTypeV1(OrganizationalObjectTypeV1): - action_object_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None diff --git a/netbox/ipam/graphql/filter_mixins_v1.py b/netbox/ipam/graphql/filter_mixins_v1.py deleted file mode 100644 index 0ba314d5b..000000000 --- a/netbox/ipam/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,25 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import IntegerLookup - from .enums import * - -__all__ = ( - 'ServiceBaseFilterMixinV1', -) - - -@dataclass -class ServiceBaseFilterMixinV1(BaseFilterMixinV1): - protocol: Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - ports: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/ipam/graphql/filters_v1.py b/netbox/ipam/graphql/filters_v1.py deleted file mode 100644 index 7e2171e76..000000000 --- a/netbox/ipam/graphql/filters_v1.py +++ /dev/null @@ -1,392 +0,0 @@ -from datetime import date -from typing import Annotated, TYPE_CHECKING - -import netaddr -import strawberry -import strawberry_django -from django.db.models import Q -from netaddr.core import AddrFormatError -from strawberry.scalars import ID -from strawberry_django import FilterLookup, DateFilterLookup - -from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 -from dcim.graphql.filter_mixins_v1 import ScopedFilterMixinV1 -from dcim.models import Device -from ipam import models -from ipam.graphql.filter_mixins_v1 import ServiceBaseFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - NetBoxModelFilterMixinV1, OrganizationalModelFilterMixinV1, PrimaryModelFilterMixinV1 -) -from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 -from virtualization.models import VMInterface - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import IntegerLookup, IntegerRangeArrayLookup - from circuits.graphql.filters_v1 import ProviderFilterV1 - from core.graphql.filters_v1 import ContentTypeFilterV1 - from dcim.graphql.filters_v1 import SiteFilterV1 - from vpn.graphql.filters_v1 import L2VPNFilterV1 - from .enums import * - -__all__ = ( - 'ASNFilterV1', - 'ASNRangeFilterV1', - 'AggregateFilterV1', - 'FHRPGroupFilterV1', - 'FHRPGroupAssignmentFilterV1', - 'IPAddressFilterV1', - 'IPRangeFilterV1', - 'PrefixFilterV1', - 'RIRFilterV1', - 'RoleFilterV1', - 'RouteTargetFilterV1', - 'ServiceFilterV1', - 'ServiceTemplateFilterV1', - 'VLANFilterV1', - 'VLANGroupFilterV1', - 'VLANTranslationPolicyFilterV1', - 'VLANTranslationRuleFilterV1', - 'VRFFilterV1', -) - - -@strawberry_django.filter_type(models.ASN, lookups=True) -class ASNFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - rir_id: ID | None = strawberry_django.filter_field() - asn: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - sites: ( - Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - providers: ( - Annotated['ProviderFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None - ) = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ASNRange, lookups=True) -class ASNRangeFilterV1(TenancyFilterMixinV1, OrganizationalModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - rir_id: ID | None = strawberry_django.filter_field() - start: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - end: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Aggregate, lookups=True) -class AggregateFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - prefix: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - prefix_id: ID | None = strawberry_django.filter_field() - rir: Annotated['RIRFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - rir_id: ID | None = strawberry_django.filter_field() - date_added: DateFilterLookup[date] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.FHRPGroup, lookups=True) -class FHRPGroupFilterV1(PrimaryModelFilterMixinV1): - group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - auth_key: FilterLookup[str] | None = strawberry_django.filter_field() - ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.FHRPGroupAssignment, lookups=True) -class FHRPGroupAssignmentFilterV1(BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1): - interface_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interface_id: FilterLookup[str] | None = strawberry_django.filter_field() - group: Annotated['FHRPGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - priority: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - @strawberry_django.filter_field() - def device_id(self, queryset, value: list[str], prefix) -> Q: - return self.filter_device('id', value) - - @strawberry_django.filter_field() - def device(self, value: list[str], prefix) -> Q: - return self.filter_device('name', value) - - @strawberry_django.filter_field() - def virtual_machine_id(self, value: list[str], prefix) -> Q: - return Q(interface_id__in=VMInterface.objects.filter(virtual_machine_id__in=value)) - - @strawberry_django.filter_field() - def virtual_machine(self, value: list[str], prefix) -> Q: - return Q(interface_id__in=VMInterface.objects.filter(virtual_machine__name__in=value)) - - def filter_device(self, field, value) -> Q: - """Helper to standardize logic for device and device_id filters""" - devices = Device.objects.filter(**{f'{field}__in': value}) - interface_ids = [] - for device in devices: - interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) - return Q(interface_id__in=interface_ids) - - -@strawberry_django.filter_type(models.IPAddress, lookups=True) -class IPAddressFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - address: FilterLookup[str] | None = strawberry_django.filter_field() - vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - assigned_object_id: ID | None = strawberry_django.filter_field() - nat_inside: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - nat_inside_id: ID | None = strawberry_django.filter_field() - nat_outside: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - nat_outside_id: ID | None = strawberry_django.filter_field() - dns_name: FilterLookup[str] | None = strawberry_django.filter_field() - - @strawberry_django.filter_field() - def assigned(self, value: bool, prefix) -> Q: - return Q(assigned_object_id__isnull=(not value)) - - @strawberry_django.filter_field() - def parent(self, value: list[str], prefix) -> Q: - if not value: - return Q() - q = Q() - for subnet in value: - try: - query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(address__net_host_contained=query) - except (AddrFormatError, ValueError): - return Q() - return q - - @strawberry_django.filter_field() - def family( - self, - value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], - prefix, - ) -> Q: - return Q(**{f"{prefix}address__family": value.value}) - - -@strawberry_django.filter_type(models.IPRange, lookups=True) -class IPRangeFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - start_address: FilterLookup[str] | None = strawberry_django.filter_field() - end_address: FilterLookup[str] | None = strawberry_django.filter_field() - size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() - - @strawberry_django.filter_field() - def parent(self, value: list[str], prefix) -> Q: - if not value: - return Q() - q = Q() - for subnet in value: - try: - query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(start_address__net_host_contained=query, end_address__net_host_contained=query) - except (AddrFormatError, ValueError): - return Q() - return q - - @strawberry_django.filter_field() - def contains(self, value: list[str], prefix) -> Q: - if not value: - return Q() - q = Q() - for subnet in value: - net = netaddr.IPNetwork(subnet.strip()) - q |= Q( - start_address__host__inet__lte=str(netaddr.IPAddress(net.first)), - end_address__host__inet__gte=str(netaddr.IPAddress(net.last)), - ) - return q - - -@strawberry_django.filter_type(models.Prefix, lookups=True) -class PrefixFilterV1(ContactFilterMixinV1, ScopedFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - prefix: FilterLookup[str] | None = strawberry_django.filter_field() - vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - vrf_id: ID | None = strawberry_django.filter_field() - vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_id: ID | None = strawberry_django.filter_field() - status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - is_pool: FilterLookup[bool] | None = strawberry_django.filter_field() - mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() - - @strawberry_django.filter_field() - def contains(self, value: list[str], prefix) -> Q: - if not value: - return Q() - q = Q() - for subnet in value: - query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(prefix__net_contains=query) - return q - - -@strawberry_django.filter_type(models.RIR, lookups=True) -class RIRFilterV1(OrganizationalModelFilterMixinV1): - is_private: FilterLookup[bool] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Role, lookups=True) -class RoleFilterV1(OrganizationalModelFilterMixinV1): - weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.RouteTarget, lookups=True) -class RouteTargetFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - importing_vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - exporting_vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - importing_l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - exporting_l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Service, lookups=True) -class ServiceFilterV1(ContactFilterMixinV1, ServiceBaseFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_object_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.ServiceTemplate, lookups=True) -class ServiceTemplateFilterV1(ServiceBaseFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.VLAN, lookups=True) -class VLANFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - group: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field() - role: Annotated['RoleFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - qinq_svlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - qinq_svlan_id: ID | None = strawberry_django.filter_field() - qinq_cvlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VLANGroup, lookups=True) -class VLANGroupFilterV1(ScopedFilterMixinV1, OrganizationalModelFilterMixinV1): - vid_ranges: Annotated['IntegerRangeArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True) -class VLANTranslationPolicyFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.VLANTranslationRule, lookups=True) -class VLANTranslationRuleFilterV1(NetBoxModelFilterMixinV1): - policy: Annotated['VLANTranslationPolicyFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - policy_id: ID | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - remote_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VRF, lookups=True) -class VRFFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - rd: FilterLookup[str] | None = strawberry_django.filter_field() - enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() - import_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - export_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/ipam/graphql/mixins_v1.py b/netbox/ipam/graphql/mixins_v1.py deleted file mode 100644 index 6d3e31197..000000000 --- a/netbox/ipam/graphql/mixins_v1.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Annotated, List - -import strawberry - -__all__ = ( - 'IPAddressesMixinV1', - 'VLANGroupsMixinV1', -) - - -@strawberry.type -class IPAddressesMixinV1: - ip_addresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] # noqa: F821 - - -@strawberry.type -class VLANGroupsMixinV1: - vlan_groups: List[Annotated["VLANGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] # noqa: F821 diff --git a/netbox/ipam/graphql/schema_v1.py b/netbox/ipam/graphql/schema_v1.py deleted file mode 100644 index 3bc7fdd97..000000000 --- a/netbox/ipam/graphql/schema_v1.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class IPAMQueryV1: - asn: ASNTypeV1 = strawberry_django.field() - asn_list: List[ASNTypeV1] = strawberry_django.field() - - asn_range: ASNRangeTypeV1 = strawberry_django.field() - asn_range_list: List[ASNRangeTypeV1] = strawberry_django.field() - - aggregate: AggregateTypeV1 = strawberry_django.field() - aggregate_list: List[AggregateTypeV1] = strawberry_django.field() - - ip_address: IPAddressTypeV1 = strawberry_django.field() - ip_address_list: List[IPAddressTypeV1] = strawberry_django.field() - - ip_range: IPRangeTypeV1 = strawberry_django.field() - ip_range_list: List[IPRangeTypeV1] = strawberry_django.field() - - prefix: PrefixTypeV1 = strawberry_django.field() - prefix_list: List[PrefixTypeV1] = strawberry_django.field() - - rir: RIRTypeV1 = strawberry_django.field() - rir_list: List[RIRTypeV1] = strawberry_django.field() - - role: RoleTypeV1 = strawberry_django.field() - role_list: List[RoleTypeV1] = strawberry_django.field() - - route_target: RouteTargetTypeV1 = strawberry_django.field() - route_target_list: List[RouteTargetTypeV1] = strawberry_django.field() - - service: ServiceTypeV1 = strawberry_django.field() - service_list: List[ServiceTypeV1] = strawberry_django.field() - - service_template: ServiceTemplateTypeV1 = strawberry_django.field() - service_template_list: List[ServiceTemplateTypeV1] = strawberry_django.field() - - fhrp_group: FHRPGroupTypeV1 = strawberry_django.field() - fhrp_group_list: List[FHRPGroupTypeV1] = strawberry_django.field() - - fhrp_group_assignment: FHRPGroupAssignmentTypeV1 = strawberry_django.field() - fhrp_group_assignment_list: List[FHRPGroupAssignmentTypeV1] = strawberry_django.field() - - vlan: VLANTypeV1 = strawberry_django.field() - vlan_list: List[VLANTypeV1] = strawberry_django.field() - - vlan_group: VLANGroupTypeV1 = strawberry_django.field() - vlan_group_list: List[VLANGroupTypeV1] = strawberry_django.field() - - vlan_translation_policy: VLANTranslationPolicyTypeV1 = strawberry_django.field() - vlan_translation_policy_list: List[VLANTranslationPolicyTypeV1] = strawberry_django.field() - - vlan_translation_rule: VLANTranslationRuleTypeV1 = strawberry_django.field() - vlan_translation_rule_list: List[VLANTranslationRuleTypeV1] = strawberry_django.field() - - vrf: VRFTypeV1 = strawberry_django.field() - vrf_list: List[VRFTypeV1] = strawberry_django.field() diff --git a/netbox/ipam/graphql/types_v1.py b/netbox/ipam/graphql/types_v1.py deleted file mode 100644 index 091a2f215..000000000 --- a/netbox/ipam/graphql/types_v1.py +++ /dev/null @@ -1,361 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from circuits.graphql.types_v1 import ProviderTypeV1 -from dcim.graphql.types_v1 import SiteTypeV1 -from extras.graphql.mixins_v1 import ContactsMixinV1 -from ipam import models -from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, NetBoxObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 -) -from .filters_v1 import * -from .mixins_v1 import IPAddressesMixinV1 - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import ( - DeviceTypeV1, - InterfaceTypeV1, - LocationTypeV1, - RackTypeV1, - RegionTypeV1, - SiteGroupTypeV1, - SiteTypeV1, - ) - from tenancy.graphql.types_v1 import TenantTypeV1 - from virtualization.graphql.types_v1 import ( - ClusterGroupTypeV1, ClusterTypeV1, VMInterfaceTypeV1, VirtualMachineTypeV1 - ) - from vpn.graphql.types_v1 import L2VPNTypeV1, TunnelTerminationTypeV1 - from wireless.graphql.types_v1 import WirelessLANTypeV1 - -__all__ = ( - 'ASNTypeV1', - 'ASNRangeTypeV1', - 'AggregateTypeV1', - 'FHRPGroupTypeV1', - 'FHRPGroupAssignmentTypeV1', - 'IPAddressTypeV1', - 'IPRangeTypeV1', - 'PrefixTypeV1', - 'RIRTypeV1', - 'RoleTypeV1', - 'RouteTargetTypeV1', - 'ServiceTypeV1', - 'ServiceTemplateTypeV1', - 'VLANTypeV1', - 'VLANGroupTypeV1', - 'VLANTranslationPolicyTypeV1', - 'VLANTranslationRuleTypeV1', - 'VRFTypeV1', -) - - -@strawberry.type -class IPAddressFamilyTypeV1: - value: int - label: str - - -@strawberry.type -class BaseIPAddressFamilyTypeV1: - """ - Base type for models that need to expose their IPAddress family type. - """ - - @strawberry.field - def family(self) -> IPAddressFamilyTypeV1: - # Note that self, is an instance of models.IPAddress - # thus resolves to the address family value. - return IPAddressFamilyTypeV1(value=self.family, label=f'IPv{self.family}') - - -@strawberry_django.type( - models.ASN, - fields='__all__', - filters=ASNFilterV1, - pagination=True -) -class ASNTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - asn: BigInt - rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - sites: List[SiteTypeV1] - providers: List[ProviderTypeV1] - - -@strawberry_django.type( - models.ASNRange, - fields='__all__', - filters=ASNRangeFilterV1, - pagination=True -) -class ASNRangeTypeV1(OrganizationalObjectTypeV1): - start: BigInt - end: BigInt - rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Aggregate, - fields='__all__', - filters=AggregateFilterV1, - pagination=True -) -class AggregateTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): - prefix: str - rir: Annotated["RIRTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - -@strawberry_django.type( - models.FHRPGroup, - fields='__all__', - filters=FHRPGroupFilterV1, - pagination=True -) -class FHRPGroupTypeV1(IPAddressesMixinV1, PrimaryObjectTypeV1): - fhrpgroupassignment_set: List[Annotated["FHRPGroupAssignmentTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.FHRPGroupAssignment, - exclude=['interface_type', 'interface_id'], - filters=FHRPGroupAssignmentFilterV1, - pagination=True -) -class FHRPGroupAssignmentTypeV1(BaseObjectTypeV1): - group: Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')] - - @strawberry_django.field - def interface(self) -> Annotated[Union[ - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - ], strawberry.union("FHRPGroupInterfaceTypeV1")]: - return self.interface - - -@strawberry_django.type( - models.IPAddress, - exclude=['assigned_object_type', 'assigned_object_id', 'address'], - filters=IPAddressFilterV1, - pagination=True -) -class IPAddressTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): - address: str - vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - nat_inside: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - nat_outside: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - tunnel_terminations: List[Annotated["TunnelTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - @strawberry_django.field - def assigned_object(self) -> Annotated[Union[ - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')], - Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - ], strawberry.union("IPAddressAssignmentTypeV1")] | None: - return self.assigned_object - - -@strawberry_django.type( - models.IPRange, - fields='__all__', - filters=IPRangeFilterV1, - pagination=True -) -class IPRangeTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - start_address: str - end_address: str - vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Prefix, - exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], - filters=PrefixFilterV1, - pagination=True -) -class PrefixTypeV1(ContactsMixinV1, BaseIPAddressFamilyTypeV1, PrimaryObjectTypeV1): - prefix: str - vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - @strawberry_django.field - def scope(self) -> Annotated[Union[ - Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("PrefixScopeTypeV1")] | None: - return self.scope - - -@strawberry_django.type( - models.RIR, - fields='__all__', - filters=RIRFilterV1, - pagination=True -) -class RIRTypeV1(OrganizationalObjectTypeV1): - - asn_ranges: List[Annotated["ASNRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - asns: List[Annotated["ASNTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - aggregates: List[Annotated["AggregateTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.Role, - fields='__all__', - filters=RoleFilterV1, - pagination=True -) -class RoleTypeV1(OrganizationalObjectTypeV1): - - prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - ip_ranges: List[Annotated["IPRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.RouteTarget, - fields='__all__', - filters=RouteTargetFilterV1, - pagination=True -) -class RouteTargetTypeV1(PrimaryObjectTypeV1): - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - importing_l2vpns: List[Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - exporting_l2vpns: List[Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - importing_vrfs: List[Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - exporting_vrfs: List[Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.Service, - exclude=('parent_object_type', 'parent_object_id'), - filters=ServiceFilterV1, - pagination=True -) -class ServiceTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - ports: List[int] - ipaddresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - @strawberry_django.field - def parent(self) -> Annotated[Union[ - Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - Annotated["FHRPGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')], - ], strawberry.union("ServiceParentTypeV1")] | None: - return self.parent - - -@strawberry_django.type( - models.ServiceTemplate, - fields='__all__', - filters=ServiceTemplateFilterV1, - pagination=True -) -class ServiceTemplateTypeV1(PrimaryObjectTypeV1): - ports: List[int] - - -@strawberry_django.type( - models.VLAN, - exclude=['qinq_svlan'], - filters=VLANFilterV1, - pagination=True -) -class VLANTypeV1(PrimaryObjectTypeV1): - site: Annotated["SiteTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - group: Annotated["VLANGroupTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - role: Annotated["RoleTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - interfaces_as_untagged: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - vminterfaces_as_untagged: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - wirelesslan_set: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] - prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - interfaces_as_tagged: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - vminterfaces_as_tagged: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - - @strawberry_django.field - def qinq_svlan(self) -> Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None: - return self.qinq_svlan - - -@strawberry_django.type( - models.VLANGroup, - exclude=['scope_type', 'scope_id'], - filters=VLANGroupFilterV1, - pagination=True -) -class VLANGroupTypeV1(OrganizationalObjectTypeV1): - - vlans: List[VLANTypeV1] - vid_ranges: List[str] - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - @strawberry_django.field - def scope(self) -> Annotated[Union[ - Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RackTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("VLANGroupScopeTypeV1")] | None: - return self.scope - - -@strawberry_django.type( - models.VLANTranslationPolicy, - fields='__all__', - filters=VLANTranslationPolicyFilterV1, - pagination=True -) -class VLANTranslationPolicyTypeV1(PrimaryObjectTypeV1): - rules: List[Annotated["VLANTranslationRuleTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.VLANTranslationRule, - fields='__all__', - filters=VLANTranslationRuleFilterV1, - pagination=True -) -class VLANTranslationRuleTypeV1(NetBoxObjectTypeV1): - policy: Annotated[ - "VLANTranslationPolicyTypeV1", - strawberry.lazy('ipam.graphql.types_v1') - ] = strawberry_django.field(select_related=["policy"]) - - -@strawberry_django.type( - models.VRF, - fields='__all__', - filters=VRFFilterV1, - pagination=True -) -class VRFTypeV1(PrimaryObjectTypeV1): - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - ip_addresses: List[Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - vminterfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - ip_ranges: List[Annotated["IPRangeTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - export_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - import_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - prefixes: List[Annotated["PrefixTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 6256af00a..3e552e944 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -51,6 +51,3 @@ LOGGING = { 'version': 1, 'disable_existing_loggers': True } - -# TODO: Switch to 2 -GRAPHQL_DEFAULT_VERSION = 1 diff --git a/netbox/netbox/graphql/filter_mixins_v1.py b/netbox/netbox/graphql/filter_mixins_v1.py deleted file mode 100644 index 2d34ff12d..000000000 --- a/netbox/netbox/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,104 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from typing import TypeVar, TYPE_CHECKING, Annotated - -import strawberry -import strawberry_django -from strawberry import ID -from strawberry_django import FilterLookup, DatetimeFilterLookup - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1, BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 -from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, JournalEntriesFilterMixinV1, TagsFilterMixinV1 - -__all__ = ( - 'DistanceFilterMixinV1', - 'ImageAttachmentFilterMixinV1', - 'NestedGroupModelFilterMixinV1', - 'NetBoxModelFilterMixinV1', - 'OrganizationalModelFilterMixinV1', - 'PrimaryModelFilterMixinV1', - 'SyncedDataFilterMixinV1', - 'WeightFilterMixinV1', -) - -T = TypeVar('T') - - -if TYPE_CHECKING: - from .enums import * - from core.graphql.filters_v1 import * - from extras.graphql.filters_v1 import * - - -class NetBoxModelFilterMixinV1( - ChangeLogFilterMixinV1, - CustomFieldsFilterMixinV1, - JournalEntriesFilterMixinV1, - TagsFilterMixinV1, - BaseObjectTypeFilterMixinV1, -): - pass - - -@dataclass -class NestedGroupModelFilterMixinV1(NetBoxModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - parent_id: ID | None = strawberry_django.filter_field() - - -@dataclass -class OrganizationalModelFilterMixinV1( - ChangeLogFilterMixinV1, - CustomFieldsFilterMixinV1, - TagsFilterMixinV1, - BaseObjectTypeFilterMixinV1, -): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@dataclass -class PrimaryModelFilterMixinV1(NetBoxModelFilterMixinV1): - description: FilterLookup[str] | None = strawberry_django.filter_field() - comments: FilterLookup[str] | None = strawberry_django.filter_field() - - -@dataclass -class ImageAttachmentFilterMixinV1(BaseFilterMixinV1): - images: Annotated['ImageAttachmentFilterV1', strawberry.lazy('extras.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class WeightFilterMixinV1(BaseFilterMixinV1): - weight: FilterLookup[float] | None = strawberry_django.filter_field() - weight_unit: Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class SyncedDataFilterMixinV1(BaseFilterMixinV1): - data_source: Annotated['DataSourceFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - data_source_id: FilterLookup[int] | None = strawberry_django.filter_field() - data_file: Annotated['DataFileFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - data_file_id: FilterLookup[int] | None = strawberry_django.filter_field() - data_path: FilterLookup[str] | None = strawberry_django.filter_field() - auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field() - data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - - -@dataclass -class DistanceFilterMixinV1(BaseFilterMixinV1): - distance: FilterLookup[float] | None = strawberry_django.filter_field() - distance_unit: Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py index 54fe61712..a7609c9d2 100644 --- a/netbox/netbox/graphql/schema.py +++ b/netbox/netbox/graphql/schema.py @@ -4,57 +4,21 @@ from strawberry_django.optimizer import DjangoOptimizerExtension from strawberry.extensions import MaxAliasesLimiter from strawberry.schema.config import StrawberryConfig -from circuits.graphql.schema_v1 import CircuitsQueryV1 from circuits.graphql.schema import CircuitsQuery -from core.graphql.schema_v1 import CoreQueryV1 from core.graphql.schema import CoreQuery -from dcim.graphql.schema_v1 import DCIMQueryV1 from dcim.graphql.schema import DCIMQuery -from extras.graphql.schema_v1 import ExtrasQueryV1 from extras.graphql.schema import ExtrasQuery -from ipam.graphql.schema_v1 import IPAMQueryV1 from ipam.graphql.schema import IPAMQuery from netbox.registry import registry -from tenancy.graphql.schema_v1 import TenancyQueryV1 from tenancy.graphql.schema import TenancyQuery -from users.graphql.schema_v1 import UsersQueryV1 from users.graphql.schema import UsersQuery -from virtualization.graphql.schema_v1 import VirtualizationQueryV1 from virtualization.graphql.schema import VirtualizationQuery -from vpn.graphql.schema_v1 import VPNQueryV1 from vpn.graphql.schema import VPNQuery -from wireless.graphql.schema_v1 import WirelessQueryV1 from wireless.graphql.schema import WirelessQuery -__all__ = ( - 'Query', - 'QueryV1', - 'QueryV2', - 'schema_v1', - 'schema_v2', -) - @strawberry.type -class QueryV1( - UsersQueryV1, - CircuitsQueryV1, - CoreQueryV1, - DCIMQueryV1, - ExtrasQueryV1, - IPAMQueryV1, - TenancyQueryV1, - VirtualizationQueryV1, - VPNQueryV1, - WirelessQueryV1, - *registry['plugins']['graphql_schemas'], # Append plugin schemas -): - """Query class for GraphQL API v1""" - pass - - -@strawberry.type -class QueryV2( +class Query( UsersQuery, CircuitsQuery, CoreQuery, @@ -67,26 +31,11 @@ class QueryV2( WirelessQuery, *registry['plugins']['graphql_schemas'], # Append plugin schemas ): - """Query class for GraphQL API v2""" pass -# Expose a default Query class for the configured default GraphQL version -class Query(QueryV2 if settings.GRAPHQL_DEFAULT_VERSION == 2 else QueryV1): - pass - - -# Generate schemas for both versions of the GraphQL API -schema_v1 = strawberry.Schema( - query=QueryV1, - config=StrawberryConfig(auto_camel_case=False), - extensions=[ - DjangoOptimizerExtension(prefetch_custom_queryset=True), - MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES), - ] -) -schema_v2 = strawberry.Schema( - query=QueryV2, +schema = strawberry.Schema( + query=Query, config=StrawberryConfig(auto_camel_case=False), extensions=[ DjangoOptimizerExtension(prefetch_custom_queryset=True), diff --git a/netbox/netbox/graphql/types_v1.py b/netbox/netbox/graphql/types_v1.py deleted file mode 100644 index 5fc776d8d..000000000 --- a/netbox/netbox/graphql/types_v1.py +++ /dev/null @@ -1,133 +0,0 @@ -import strawberry -import strawberry_django -from strawberry.types import Info -from django.contrib.contenttypes.models import ContentType - -from core.graphql.mixins_v1 import ChangelogMixinV1 -from core.models import ObjectType as ObjectType_ -from extras.graphql.mixins_v1 import CustomFieldsMixinV1, JournalEntriesMixinV1, TagsMixinV1 -from users.graphql.mixins_v1 import OwnerMixinV1 - -__all__ = ( - 'BaseObjectTypeV1', - 'ContentTypeTypeV1', - 'NestedGroupObjectTypeV1', - 'NetBoxObjectTypeV1', - 'ObjectTypeV1', - 'OrganizationalObjectTypeV1', - 'PrimaryObjectTypeV1', -) - - -# -# Base types -# - -@strawberry.type -class BaseObjectTypeV1: - """ - Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions. - """ - - @classmethod - def get_queryset(cls, queryset, info: Info, **kwargs): - # Enforce object permissions on the queryset - if hasattr(queryset, 'restrict'): - return queryset.restrict(info.context.request.user, 'view') - else: - return queryset - - @strawberry_django.field - def display(self) -> str: - return str(self) - - @strawberry_django.field - def class_type(self) -> str: - return self.__class__.__name__ - - -class ObjectTypeV1( - ChangelogMixinV1, - BaseObjectTypeV1 -): - """ - Base GraphQL object type for unclassified models which support change logging - """ - pass - - -class PrimaryObjectTypeV1( - ChangelogMixinV1, - CustomFieldsMixinV1, - JournalEntriesMixinV1, - TagsMixinV1, - OwnerMixinV1, - BaseObjectTypeV1 -): - """ - Base GraphQL type for models which inherit from PrimaryModel. - """ - pass - - -class OrganizationalObjectTypeV1( - ChangelogMixinV1, - CustomFieldsMixinV1, - JournalEntriesMixinV1, - TagsMixinV1, - OwnerMixinV1, - BaseObjectTypeV1 -): - """ - Base type for organizational models - """ - pass - - -class NestedGroupObjectTypeV1( - ChangelogMixinV1, - CustomFieldsMixinV1, - JournalEntriesMixinV1, - TagsMixinV1, - OwnerMixinV1, - BaseObjectTypeV1 -): - """ - Base GraphQL type for models which inherit from NestedGroupModel. - """ - pass - - -class NetBoxObjectTypeV1( - ChangelogMixinV1, - CustomFieldsMixinV1, - JournalEntriesMixinV1, - TagsMixinV1, - BaseObjectTypeV1 -): - """ - GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags. - """ - pass - - -# -# Miscellaneous types -# - -@strawberry_django.type( - ContentType, - fields=['id', 'app_label', 'model'], - pagination=True -) -class ContentTypeTypeV1: - pass - - -@strawberry_django.type( - ObjectType_, - fields=['id', 'app_label', 'model'], - pagination=True -) -class ObjectTypeTypeV1: - pass diff --git a/netbox/netbox/graphql/utils.py b/netbox/netbox/graphql/utils.py deleted file mode 100644 index b97b8cecc..000000000 --- a/netbox/netbox/graphql/utils.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.conf import settings - -from netbox.graphql.schema import schema_v1, schema_v2 - -__all__ = ( - 'get_default_schema', -) - - -def get_default_schema(): - """ - Returns the GraphQL schema corresponding to the value of the NETBOX_GRAPHQL_DEFAULT_SCHEMA setting. - """ - if settings.GRAPHQL_DEFAULT_VERSION == 2: - return schema_v2 - return schema_v1 diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 437689732..15d5b39b1 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -6,8 +6,7 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, Spec from account.views import LoginView, LogoutView from netbox.api.views import APIRootView, StatusView -from netbox.graphql.schema import schema_v1, schema_v2 -from netbox.graphql.utils import get_default_schema +from netbox.graphql.schema import schema from netbox.graphql.views import NetBoxGraphQLView from netbox.plugins.urls import plugin_patterns, plugin_api_patterns from netbox.views import HomeView, MediaView, StaticMediaFailureView, SearchView, htmx @@ -66,10 +65,8 @@ _patterns = [ path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='api_docs'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'), - # GraphQL API - path('graphql/', NetBoxGraphQLView.as_view(schema=get_default_schema()), name='graphql'), - path('graphql/v1/', NetBoxGraphQLView.as_view(schema=schema_v1), name='graphql_v1'), - path('graphql/v2/', NetBoxGraphQLView.as_view(schema=schema_v2), name='graphql_v2'), + # GraphQL + path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'), # Serving static media in Django to pipe it through LoginRequiredMiddleware path('media/', MediaView.as_view(), name='media'), diff --git a/netbox/tenancy/graphql/filter_mixins_v1.py b/netbox/tenancy/graphql/filter_mixins_v1.py deleted file mode 100644 index c5ffd914f..000000000 --- a/netbox/tenancy/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,38 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry import ID - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 - -if TYPE_CHECKING: - from netbox.graphql.filter_lookups import TreeNodeFilter - from .filters_v1 import ContactAssignmentFilterV1, TenantFilterV1, TenantGroupFilterV1 - -__all__ = ( - 'ContactFilterMixinV1', - 'TenancyFilterMixinV1', -) - - -@dataclass -class ContactFilterMixinV1(BaseFilterMixinV1): - contacts: Annotated['ContactAssignmentFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@dataclass -class TenancyFilterMixinV1(BaseFilterMixinV1): - tenant: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tenant_id: ID | None = strawberry_django.filter_field() - tenant_group: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tenant_group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/tenancy/graphql/filters_v1.py b/netbox/tenancy/graphql/filters_v1.py deleted file mode 100644 index fb101858b..000000000 --- a/netbox/tenancy/graphql/filters_v1.py +++ /dev/null @@ -1,210 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import ChangeLogFilterMixinV1 -from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - NestedGroupModelFilterMixinV1, - OrganizationalModelFilterMixinV1, - PrimaryModelFilterMixinV1, -) -from tenancy import models -from .filter_mixins_v1 import ContactFilterMixinV1 - -if TYPE_CHECKING: - from core.graphql.filters_v1 import ContentTypeFilterV1 - from circuits.graphql.filters_v1 import CircuitFilterV1, CircuitGroupFilterV1, VirtualCircuitFilterV1 - from dcim.graphql.filters_v1 import ( - CableFilterV1, - DeviceFilterV1, - LocationFilterV1, - PowerFeedFilterV1, - RackFilterV1, - RackReservationFilterV1, - SiteFilterV1, - VirtualDeviceContextFilterV1, - ) - from ipam.graphql.filters_v1 import ( - AggregateFilterV1, - ASNFilterV1, - ASNRangeFilterV1, - IPAddressFilterV1, - IPRangeFilterV1, - PrefixFilterV1, - RouteTargetFilterV1, - VLANFilterV1, - VLANGroupFilterV1, - VRFFilterV1, - ) - from netbox.graphql.filter_lookups import TreeNodeFilter - from wireless.graphql.filters_v1 import WirelessLANFilterV1, WirelessLinkFilterV1 - from virtualization.graphql.filters_v1 import ClusterFilterV1, VirtualMachineFilterV1 - from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelFilterV1 - from .enums import * - -__all__ = ( - 'TenantFilterV1', - 'TenantGroupFilterV1', - 'ContactFilterV1', - 'ContactRoleFilterV1', - 'ContactGroupFilterV1', - 'ContactAssignmentFilterV1', -) - - -@strawberry_django.filter_type(models.Tenant, lookups=True) -class TenantFilterV1(PrimaryModelFilterMixinV1, ContactFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - group: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - - # Reverse relations - aggregates: Annotated['AggregateFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - asns: Annotated['ASNFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - asn_ranges: Annotated['ASNRangeFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cables: Annotated['CableFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - circuit_groups: Annotated['CircuitGroupFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - circuits: Annotated['CircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - clusters: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - devices: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - ip_ranges: Annotated['IPRangeFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - l2vpns: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - locations: Annotated['LocationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - power_feeds: Annotated['PowerFeedFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - prefixes: Annotated['PrefixFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - racks: Annotated['RackFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - rackreservations: Annotated['RackReservationFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - route_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - sites: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tunnels: Annotated['TunnelFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vdcs: Annotated['VirtualDeviceContextFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - virtual_machines: Annotated[ - 'VirtualMachineFilterV1', strawberry.lazy('virtualization.graphql.filters_v1') - ] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlans: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - virtual_circuits: Annotated['VirtualCircuitFilterV1', strawberry.lazy('circuits.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vrfs: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - wireless_lans: Annotated['WirelessLANFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - wireless_links: Annotated['WirelessLinkFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.TenantGroup, lookups=True) -class TenantGroupFilterV1(OrganizationalModelFilterMixinV1): - parent: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_id: ID | None = strawberry.UNSET - tenants: Annotated['TenantFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - children: Annotated['TenantGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1'), True] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.Contact, lookups=True) -class ContactFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - title: FilterLookup[str] | None = strawberry_django.filter_field() - phone: FilterLookup[str] | None = strawberry_django.filter_field() - email: FilterLookup[str] | None = strawberry_django.filter_field() - address: FilterLookup[str] | None = strawberry_django.filter_field() - link: FilterLookup[str] | None = strawberry_django.filter_field() - groups: Annotated['ContactGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - assignments: Annotated['ContactAssignmentFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ContactRole, lookups=True) -class ContactRoleFilterV1(OrganizationalModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.ContactGroup, lookups=True) -class ContactGroupFilterV1(NestedGroupModelFilterMixinV1): - parent: Annotated['ContactGroupFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ContactAssignment, lookups=True) -class ContactAssignmentFilterV1(CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1): - object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - object_id: ID | None = strawberry_django.filter_field() - contact: Annotated['ContactFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - contact_id: ID | None = strawberry_django.filter_field() - role: Annotated['ContactRoleFilterV1', strawberry.lazy('tenancy.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - priority: Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/tenancy/graphql/mixins_v1.py b/netbox/tenancy/graphql/mixins_v1.py deleted file mode 100644 index a6c31b68a..000000000 --- a/netbox/tenancy/graphql/mixins_v1.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Annotated, List - -import strawberry - -__all__ = ( - 'ContactAssignmentsMixinV1', -) - - -@strawberry.type -class ContactAssignmentsMixinV1: - assignments: List[Annotated["ContactAssignmentTypeV1", strawberry.lazy('tenancy.graphql.types_v1')]] # noqa: F821 diff --git a/netbox/tenancy/graphql/schema_v1.py b/netbox/tenancy/graphql/schema_v1.py deleted file mode 100644 index 135f1573f..000000000 --- a/netbox/tenancy/graphql/schema_v1.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class TenancyQueryV1: - tenant: TenantTypeV1 = strawberry_django.field() - tenant_list: List[TenantTypeV1] = strawberry_django.field() - - tenant_group: TenantGroupTypeV1 = strawberry_django.field() - tenant_group_list: List[TenantGroupTypeV1] = strawberry_django.field() - - contact: ContactTypeV1 = strawberry_django.field() - contact_list: List[ContactTypeV1] = strawberry_django.field() - - contact_role: ContactRoleTypeV1 = strawberry_django.field() - contact_role_list: List[ContactRoleTypeV1] = strawberry_django.field() - - contact_group: ContactGroupTypeV1 = strawberry_django.field() - contact_group_list: List[ContactGroupTypeV1] = strawberry_django.field() - - contact_assignment: ContactAssignmentTypeV1 = strawberry_django.field() - contact_assignment_list: List[ContactAssignmentTypeV1] = strawberry_django.field() diff --git a/netbox/tenancy/graphql/types_v1.py b/netbox/tenancy/graphql/types_v1.py deleted file mode 100644 index 82e7d7610..000000000 --- a/netbox/tenancy/graphql/types_v1.py +++ /dev/null @@ -1,149 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING - -import strawberry -import strawberry_django - -from extras.graphql.mixins_v1 import CustomFieldsMixinV1, TagsMixinV1, ContactsMixinV1 -from netbox.graphql.types_v1 import ( - BaseObjectTypeV1, OrganizationalObjectTypeV1, PrimaryObjectTypeV1 -) -from tenancy import models -from .filters_v1 import * -from .mixins_v1 import ContactAssignmentsMixinV1 - -if TYPE_CHECKING: - from circuits.graphql.types_v1 import CircuitTypeV1 - from dcim.graphql.types_v1 import ( - CableTypeV1, - DeviceTypeV1, - LocationTypeV1, - PowerFeedTypeV1, - RackTypeV1, - RackReservationTypeV1, - SiteTypeV1, - VirtualDeviceContextTypeV1, - ) - from ipam.graphql.types_v1 import ( - AggregateTypeV1, - ASNTypeV1, - ASNRangeTypeV1, - IPAddressTypeV1, - IPRangeTypeV1, - PrefixTypeV1, - RouteTargetTypeV1, - VLANTypeV1, - VRFTypeV1, - ) - from netbox.graphql.types_v1 import ContentTypeTypeV1 - from wireless.graphql.types_v1 import WirelessLANTypeV1, WirelessLinkTypeV1 - from virtualization.graphql.types_v1 import ClusterTypeV1, VirtualMachineTypeV1 - from vpn.graphql.types_v1 import L2VPNTypeV1, TunnelTypeV1 - -__all__ = ( - 'ContactAssignmentTypeV1', - 'ContactGroupTypeV1', - 'ContactRoleTypeV1', - 'ContactTypeV1', - 'TenantTypeV1', - 'TenantGroupTypeV1', -) - - -# -# Tenants -# - -@strawberry_django.type( - models.Tenant, - fields='__all__', - filters=TenantFilterV1, - pagination=True -) -class TenantTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - group: Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None - asns: List[Annotated['ASNTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - circuits: List[Annotated['CircuitTypeV1', strawberry.lazy('circuits.graphql.types_v1')]] - sites: List[Annotated['SiteTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - vlans: List[Annotated['VLANTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - wireless_lans: List[Annotated['WirelessLANTypeV1', strawberry.lazy('wireless.graphql.types_v1')]] - route_targets: List[Annotated['RouteTargetTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - locations: List[Annotated['LocationTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - ip_ranges: List[Annotated['IPRangeTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - rackreservations: List[Annotated['RackReservationTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - racks: List[Annotated['RackTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - vdcs: List[Annotated['VirtualDeviceContextTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - prefixes: List[Annotated['PrefixTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - cables: List[Annotated['CableTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - virtual_machines: List[Annotated['VirtualMachineTypeV1', strawberry.lazy('virtualization.graphql.types_v1')]] - vrfs: List[Annotated['VRFTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - asn_ranges: List[Annotated['ASNRangeTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - wireless_links: List[Annotated['WirelessLinkTypeV1', strawberry.lazy('wireless.graphql.types_v1')]] - aggregates: List[Annotated['AggregateTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - power_feeds: List[Annotated['PowerFeedTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - devices: List[Annotated['DeviceTypeV1', strawberry.lazy('dcim.graphql.types_v1')]] - tunnels: List[Annotated['TunnelTypeV1', strawberry.lazy('vpn.graphql.types_v1')]] - ip_addresses: List[Annotated['IPAddressTypeV1', strawberry.lazy('ipam.graphql.types_v1')]] - clusters: List[Annotated['ClusterTypeV1', strawberry.lazy('virtualization.graphql.types_v1')]] - l2vpns: List[Annotated['L2VPNTypeV1', strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.TenantGroup, - fields='__all__', - filters=TenantGroupFilterV1, - pagination=True -) -class TenantGroupTypeV1(OrganizationalObjectTypeV1): - parent: Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None - - tenants: List[TenantTypeV1] - children: List[Annotated['TenantGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] - - -# -# Contacts -# - -@strawberry_django.type( - models.Contact, - fields='__all__', - filters=ContactFilterV1, - pagination=True -) -class ContactTypeV1(ContactAssignmentsMixinV1, PrimaryObjectTypeV1): - groups: List[Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] - - -@strawberry_django.type( - models.ContactRole, - fields='__all__', - filters=ContactRoleFilterV1, - pagination=True -) -class ContactRoleTypeV1(ContactAssignmentsMixinV1, OrganizationalObjectTypeV1): - pass - - -@strawberry_django.type( - models.ContactGroup, - fields='__all__', - filters=ContactGroupFilterV1, - pagination=True -) -class ContactGroupTypeV1(OrganizationalObjectTypeV1): - parent: Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None - - contacts: List[ContactTypeV1] - children: List[Annotated['ContactGroupTypeV1', strawberry.lazy('tenancy.graphql.types_v1')]] - - -@strawberry_django.type( - models.ContactAssignment, - fields='__all__', - filters=ContactAssignmentFilterV1, - pagination=True -) -class ContactAssignmentTypeV1(CustomFieldsMixinV1, TagsMixinV1, BaseObjectTypeV1): - object_type: Annotated['ContentTypeTypeV1', strawberry.lazy('netbox.graphql.types_v1')] | None - contact: Annotated['ContactTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None - role: Annotated['ContactRoleTypeV1', strawberry.lazy('tenancy.graphql.types_v1')] | None diff --git a/netbox/users/graphql/filters_v1.py b/netbox/users/graphql/filters_v1.py deleted file mode 100644 index 951c666ae..000000000 --- a/netbox/users/graphql/filters_v1.py +++ /dev/null @@ -1,57 +0,0 @@ -from datetime import datetime -from typing import Annotated - -import strawberry -import strawberry_django -from strawberry_django import DatetimeFilterLookup, FilterLookup - -from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1 -from users import models - -__all__ = ( - 'GroupFilterV1', - 'OwnerFilterV1', - 'OwnerGroupFilterV1', - 'UserFilterV1', -) - - -@strawberry_django.filter_type(models.Group, lookups=True) -class GroupFilterV1(BaseObjectTypeFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.User, lookups=True) -class UserFilterV1(BaseObjectTypeFilterMixinV1): - username: FilterLookup[str] | None = strawberry_django.filter_field() - first_name: FilterLookup[str] | None = strawberry_django.filter_field() - last_name: FilterLookup[str] | None = strawberry_django.filter_field() - email: FilterLookup[str] | None = strawberry_django.filter_field() - is_superuser: FilterLookup[bool] | None = strawberry_django.filter_field() - is_active: FilterLookup[bool] | None = strawberry_django.filter_field() - date_joined: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - last_login: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() - groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field()) - - -@strawberry_django.filter_type(models.Owner, lookups=True) -class OwnerFilterV1(BaseObjectTypeFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() - group: Annotated['OwnerGroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - user_groups: Annotated['GroupFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - users: Annotated['UserFilterV1', strawberry.lazy('users.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.OwnerGroup, lookups=True) -class OwnerGroupFilterV1(BaseObjectTypeFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/users/graphql/mixins_v1.py b/netbox/users/graphql/mixins_v1.py deleted file mode 100644 index b2fb22e6d..000000000 --- a/netbox/users/graphql/mixins_v1.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry - -if TYPE_CHECKING: - from users.graphql.types_v1 import OwnerTypeV1 - -__all__ = ( - 'OwnerMixinV1', -) - - -@strawberry.type -class OwnerMixinV1: - owner: Annotated['OwnerTypeV1', strawberry.lazy('users.graphql.types_v1')] | None diff --git a/netbox/users/graphql/schema_v1.py b/netbox/users/graphql/schema_v1.py deleted file mode 100644 index b5389f35e..000000000 --- a/netbox/users/graphql/schema_v1.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class UsersQueryV1: - group: GroupTypeV1 = strawberry_django.field() - group_list: List[GroupTypeV1] = strawberry_django.field() - - user: UserTypeV1 = strawberry_django.field() - user_list: List[UserTypeV1] = strawberry_django.field() - - owner_group: OwnerGroupTypeV1 = strawberry_django.field() - owner_group_list: List[OwnerGroupTypeV1] = strawberry_django.field() - - owner: OwnerTypeV1 = strawberry_django.field() - owner_list: List[OwnerTypeV1] = strawberry_django.field() diff --git a/netbox/users/graphql/types_v1.py b/netbox/users/graphql/types_v1.py deleted file mode 100644 index 16b0aecde..000000000 --- a/netbox/users/graphql/types_v1.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import List - -import strawberry_django - -from netbox.graphql.types_v1 import BaseObjectTypeV1 -from users.models import Group, Owner, OwnerGroup, User -from .filters_v1 import * - -__all__ = ( - 'GroupTypeV1', - 'OwnerGroupTypeV1', - 'OwnerTypeV1', - 'UserTypeV1', -) - - -@strawberry_django.type( - Group, - fields=['id', 'name'], - filters=GroupFilterV1, - pagination=True -) -class GroupTypeV1(BaseObjectTypeV1): - pass - - -@strawberry_django.type( - User, - fields=[ - 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'date_joined', 'groups', - ], - filters=UserFilterV1, - pagination=True -) -class UserTypeV1(BaseObjectTypeV1): - groups: List[GroupTypeV1] - - -@strawberry_django.type( - OwnerGroup, - fields=['id', 'name', 'description'], - filters=OwnerGroupFilterV1, - pagination=True -) -class OwnerGroupTypeV1(BaseObjectTypeV1): - pass - - -@strawberry_django.type( - Owner, - fields=['id', 'group', 'name', 'description', 'user_groups', 'users'], - filters=OwnerFilterV1, - pagination=True -) -class OwnerTypeV1(BaseObjectTypeV1): - group: OwnerGroupTypeV1 | None diff --git a/netbox/virtualization/graphql/filter_mixins_v1.py b/netbox/virtualization/graphql/filter_mixins_v1.py deleted file mode 100644 index 7de4192c8..000000000 --- a/netbox/virtualization/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,28 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry import ID -from strawberry_django import FilterLookup - -from netbox.graphql.filter_mixins_v1 import NetBoxModelFilterMixinV1 - -if TYPE_CHECKING: - from .filters_v1 import VirtualMachineFilterV1 - -__all__ = ( - 'VMComponentFilterMixinV1', -) - - -@dataclass -class VMComponentFilterMixinV1(NetBoxModelFilterMixinV1): - virtual_machine: Annotated[ - 'VirtualMachineFilterV1', strawberry.lazy('virtualization.graphql.filters_v1') - ] | None = ( - strawberry_django.filter_field() - ) - virtual_machine_id: ID | None = strawberry_django.filter_field() - name: FilterLookup[str] | None = strawberry_django.filter_field() - description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/filters_v1.py b/netbox/virtualization/graphql/filters_v1.py deleted file mode 100644 index d1ba0cff4..000000000 --- a/netbox/virtualization/graphql/filters_v1.py +++ /dev/null @@ -1,170 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from dcim.graphql.filter_mixins_v1 import InterfaceBaseFilterMixinV1, RenderConfigFilterMixinV1, ScopedFilterMixinV1 -from extras.graphql.filter_mixins_v1 import ConfigContextFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - ImageAttachmentFilterMixinV1, - OrganizationalModelFilterMixinV1, - PrimaryModelFilterMixinV1, -) -from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 -from virtualization import models -from virtualization.graphql.filter_mixins_v1 import VMComponentFilterMixinV1 - -if TYPE_CHECKING: - from .enums import * - from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup - from dcim.graphql.filters_v1 import ( - DeviceFilterV1, DeviceRoleFilterV1, MACAddressFilterV1, PlatformFilterV1, SiteFilterV1 - ) - from ipam.graphql.filters_v1 import ( - FHRPGroupAssignmentFilterV1, - IPAddressFilterV1, - ServiceFilterV1, - VLANGroupFilterV1, - VRFFilterV1, - ) - from vpn.graphql.filters_v1 import L2VPNFilterV1, TunnelTerminationFilterV1 - -__all__ = ( - 'ClusterFilterV1', - 'ClusterGroupFilterV1', - 'ClusterTypeFilterV1', - 'VirtualMachineFilterV1', - 'VMInterfaceFilterV1', - 'VirtualDiskFilterV1', -) - - -@strawberry_django.filter_type(models.Cluster, lookups=True) -class ClusterFilterV1(ContactFilterMixinV1, ScopedFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - type: Annotated['ClusterTypeFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - type_id: ID | None = strawberry_django.filter_field() - group: Annotated['ClusterGroupFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - status: Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ClusterGroup, lookups=True) -class ClusterGroupFilterV1(ContactFilterMixinV1, OrganizationalModelFilterMixinV1): - vlan_groups: Annotated['VLANGroupFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.ClusterType, lookups=True) -class ClusterTypeFilterV1(OrganizationalModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.VirtualMachine, lookups=True) -class VirtualMachineFilterV1( - ContactFilterMixinV1, - ImageAttachmentFilterMixinV1, - RenderConfigFilterMixinV1, - ConfigContextFilterMixinV1, - TenancyFilterMixinV1, - PrimaryModelFilterMixinV1, -): - name: FilterLookup[str] | None = strawberry_django.filter_field() - site: Annotated['SiteFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - site_id: ID | None = strawberry_django.filter_field() - cluster: Annotated['ClusterFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - cluster_id: ID | None = strawberry_django.filter_field() - device: Annotated['DeviceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - device_id: ID | None = strawberry_django.filter_field() - platform: Annotated['PlatformFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - platform_id: ID | None = strawberry_django.filter_field() - status: Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - role: Annotated['DeviceRoleFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - role_id: ID | None = strawberry_django.filter_field() - primary_ip4: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip4_id: ID | None = strawberry_django.filter_field() - primary_ip6: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - primary_ip6_id: ID | None = strawberry_django.filter_field() - vcpus: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - memory: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - disk: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - serial: FilterLookup[str] | None = strawberry_django.filter_field() - interface_count: FilterLookup[int] | None = strawberry_django.filter_field() - virtual_disk_count: FilterLookup[int] | None = strawberry_django.filter_field() - interfaces: Annotated['VMInterfaceFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - services: Annotated['ServiceFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - virtual_disks: Annotated['VirtualDiskFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VMInterface, lookups=True) -class VMInterfaceFilterV1(VMComponentFilterMixinV1, InterfaceBaseFilterMixinV1): - ip_addresses: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vrf: Annotated['VRFFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = strawberry_django.filter_field() - vrf_id: ID | None = strawberry_django.filter_field() - parent: Annotated['VMInterfaceFilterV1', strawberry.lazy('virtualization.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - parent_id: ID | None = strawberry_django.filter_field() - fhrp_group_assignments: Annotated[ - 'FHRPGroupAssignmentFilterV1', strawberry.lazy('ipam.graphql.filters_v1') - ] | None = ( - strawberry_django.filter_field() - ) - tunnel_terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - l2vpn_terminations: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - mac_addresses: Annotated['MACAddressFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.VirtualDisk, lookups=True) -class VirtualDiskFilterV1(VMComponentFilterMixinV1): - size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/virtualization/graphql/schema_v1.py b/netbox/virtualization/graphql/schema_v1.py deleted file mode 100644 index 85994411f..000000000 --- a/netbox/virtualization/graphql/schema_v1.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class VirtualizationQueryV1: - cluster: ClusterTypeV1 = strawberry_django.field() - cluster_list: List[ClusterTypeV1] = strawberry_django.field() - - cluster_group: ClusterGroupTypeV1 = strawberry_django.field() - cluster_group_list: List[ClusterGroupTypeV1] = strawberry_django.field() - - cluster_type: ClusterTypeTypeV1 = strawberry_django.field() - cluster_type_list: List[ClusterTypeTypeV1] = strawberry_django.field() - - virtual_machine: VirtualMachineTypeV1 = strawberry_django.field() - virtual_machine_list: List[VirtualMachineTypeV1] = strawberry_django.field() - - vm_interface: VMInterfaceTypeV1 = strawberry_django.field() - vm_interface_list: List[VMInterfaceTypeV1] = strawberry_django.field() - - virtual_disk: VirtualDiskTypeV1 = strawberry_django.field() - virtual_disk_list: List[VirtualDiskTypeV1] = strawberry_django.field() diff --git a/netbox/virtualization/graphql/types_v1.py b/netbox/virtualization/graphql/types_v1.py deleted file mode 100644 index ba6add3cb..000000000 --- a/netbox/virtualization/graphql/types_v1.py +++ /dev/null @@ -1,147 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from extras.graphql.mixins_v1 import ConfigContextMixinV1, ContactsMixinV1 -from ipam.graphql.mixins_v1 import IPAddressesMixinV1, VLANGroupsMixinV1 -from netbox.graphql.scalars import BigInt -from netbox.graphql.types_v1 import OrganizationalObjectTypeV1, NetBoxObjectTypeV1, PrimaryObjectTypeV1 -from users.graphql.mixins_v1 import OwnerMixinV1 -from virtualization import models -from .filters_v1 import * - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import ( - DeviceRoleTypeV1, - DeviceTypeV1, - LocationTypeV1, - MACAddressTypeV1, - PlatformTypeV1, - RegionTypeV1, - SiteGroupTypeV1, - SiteTypeV1, - ) - from extras.graphql.types_v1 import ConfigTemplateTypeV1 - from ipam.graphql.types_v1 import IPAddressTypeV1, ServiceTypeV1, VLANTranslationPolicyTypeV1, VLANTypeV1, VRFTypeV1 - from tenancy.graphql.types_v1 import TenantTypeV1 - -__all__ = ( - 'ClusterTypeV1', - 'ClusterGroupTypeV1', - 'ClusterTypeTypeV1', - 'VirtualDiskTypeV1', - 'VirtualMachineTypeV1', - 'VMInterfaceTypeV1', -) - - -@strawberry.type -class ComponentTypeV1(OwnerMixinV1, NetBoxObjectTypeV1): - """ - Base type for device/VM components - """ - virtual_machine: Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] - - -@strawberry_django.type( - models.Cluster, - exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], - filters=ClusterFilterV1, - pagination=True -) -class ClusterTypeV1(ContactsMixinV1, VLANGroupsMixinV1, PrimaryObjectTypeV1): - type: Annotated["ClusterTypeTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - group: Annotated["ClusterGroupTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - virtual_machines: List[Annotated["VirtualMachineTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - devices: List[Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def scope(self) -> Annotated[Union[ - Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("ClusterScopeTypeV1")] | None: - return self.scope - - -@strawberry_django.type( - models.ClusterGroup, - fields='__all__', - filters=ClusterGroupFilterV1, - pagination=True -) -class ClusterGroupTypeV1(ContactsMixinV1, VLANGroupsMixinV1, OrganizationalObjectTypeV1): - - clusters: List[Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - - -@strawberry_django.type( - models.ClusterType, - fields='__all__', - filters=ClusterTypeFilterV1, - pagination=True -) -class ClusterTypeTypeV1(OrganizationalObjectTypeV1): - - clusters: List[ClusterTypeV1] - - -@strawberry_django.type( - models.VirtualMachine, - fields='__all__', - filters=VirtualMachineFilterV1, - pagination=True -) -class VirtualMachineTypeV1(ConfigContextMixinV1, ContactsMixinV1, PrimaryObjectTypeV1): - interface_count: BigInt - virtual_disk_count: BigInt - interface_count: BigInt - config_template: Annotated["ConfigTemplateTypeV1", strawberry.lazy('extras.graphql.types_v1')] | None - site: Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - cluster: Annotated["ClusterTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - platform: Annotated["PlatformTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - role: Annotated["DeviceRoleTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - primary_ip4: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - primary_ip6: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - services: List[Annotated["ServiceTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - virtualdisks: List[Annotated["VirtualDiskTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - - -@strawberry_django.type( - models.VMInterface, - fields='__all__', - filters=VMInterfaceFilterV1, - pagination=True -) -class VMInterfaceTypeV1(IPAddressesMixinV1, ComponentTypeV1): - _name: str - mac_address: str | None - parent: Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - bridge: Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')] | None - untagged_vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - vrf: Annotated["VRFTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - primary_mac_address: Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - qinq_svlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - vlan_translation_policy: Annotated["VLANTranslationPolicyTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - tagged_vlans: List[Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - bridge_interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - child_interfaces: List[Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')]] - mac_addresses: List[Annotated["MACAddressTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - -@strawberry_django.type( - models.VirtualDisk, - fields='__all__', - filters=VirtualDiskFilterV1, - pagination=True -) -class VirtualDiskTypeV1(ComponentTypeV1): - pass diff --git a/netbox/vpn/graphql/filters_v1.py b/netbox/vpn/graphql/filters_v1.py deleted file mode 100644 index 9b70b9a9e..000000000 --- a/netbox/vpn/graphql/filters_v1.py +++ /dev/null @@ -1,192 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import BaseObjectTypeFilterMixinV1, ChangeLogFilterMixinV1 -from extras.graphql.filter_mixins_v1 import CustomFieldsFilterMixinV1, TagsFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - NetBoxModelFilterMixinV1, OrganizationalModelFilterMixinV1, PrimaryModelFilterMixinV1 -) -from tenancy.graphql.filter_mixins_v1 import ContactFilterMixinV1, TenancyFilterMixinV1 -from vpn import models - -if TYPE_CHECKING: - from core.graphql.filters_v1 import ContentTypeFilterV1 - from ipam.graphql.filters_v1 import IPAddressFilterV1, RouteTargetFilterV1 - from netbox.graphql.filter_lookups import IntegerLookup - from .enums import * - -__all__ = ( - 'TunnelGroupFilterV1', - 'TunnelTerminationFilterV1', - 'TunnelFilterV1', - 'IKEProposalFilterV1', - 'IKEPolicyFilterV1', - 'IPSecProposalFilterV1', - 'IPSecPolicyFilterV1', - 'IPSecProfileFilterV1', - 'L2VPNFilterV1', - 'L2VPNTerminationFilterV1', -) - - -@strawberry_django.filter_type(models.TunnelGroup, lookups=True) -class TunnelGroupFilterV1(OrganizationalModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.TunnelTermination, lookups=True) -class TunnelTerminationFilterV1( - BaseObjectTypeFilterMixinV1, CustomFieldsFilterMixinV1, TagsFilterMixinV1, ChangeLogFilterMixinV1 -): - tunnel: Annotated['TunnelFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tunnel_id: ID | None = strawberry_django.filter_field() - role: Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - termination_type: Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - termination_type_id: ID | None = strawberry_django.filter_field() - termination_id: ID | None = strawberry_django.filter_field() - outside_ip: Annotated['IPAddressFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - outside_ip_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.Tunnel, lookups=True) -class TunnelFilterV1(TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - group: Annotated['TunnelGroupFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - encapsulation: Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - ipsec_profile: Annotated['IPSecProfileFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - tunnel_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - terminations: Annotated['TunnelTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.IKEProposal, lookups=True) -class IKEProposalFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - authentication_method: Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - ike_policies: Annotated['IKEPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.IKEPolicy, lookups=True) -class IKEPolicyFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - version: Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - mode: Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - proposals: Annotated['IKEProposalFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - preshared_key: FilterLookup[str] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.IPSecProposal, lookups=True) -class IPSecProposalFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - sa_lifetime_data: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - ipsec_policies: Annotated['IPSecPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.IPSecPolicy, lookups=True) -class IPSecPolicyFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - proposals: Annotated['IPSecProposalFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - pfs_group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.IPSecProfile, lookups=True) -class IPSecProfileFilterV1(PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - mode: Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - ike_policy: Annotated['IKEPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - ike_policy_id: ID | None = strawberry_django.filter_field() - ipsec_policy: Annotated['IPSecPolicyFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - ipsec_policy_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.L2VPN, lookups=True) -class L2VPNFilterV1(ContactFilterMixinV1, TenancyFilterMixinV1, PrimaryModelFilterMixinV1): - name: FilterLookup[str] | None = strawberry_django.filter_field() - slug: FilterLookup[str] | None = strawberry_django.filter_field() - type: Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() - identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) - import_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - export_targets: Annotated['RouteTargetFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - terminations: Annotated['L2VPNTerminationFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - - -@strawberry_django.filter_type(models.L2VPNTermination, lookups=True) -class L2VPNTerminationFilterV1(NetBoxModelFilterMixinV1): - l2vpn: Annotated['L2VPNFilterV1', strawberry.lazy('vpn.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - l2vpn_id: ID | None = strawberry_django.filter_field() - assigned_object_type: Annotated['ContentTypeFilterV1', strawberry.lazy('core.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - assigned_object_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/vpn/graphql/schema_v1.py b/netbox/vpn/graphql/schema_v1.py deleted file mode 100644 index 7ed22333c..000000000 --- a/netbox/vpn/graphql/schema_v1.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class VPNQueryV1: - ike_policy: IKEPolicyTypeV1 = strawberry_django.field() - ike_policy_list: List[IKEPolicyTypeV1] = strawberry_django.field() - - ike_proposal: IKEProposalTypeV1 = strawberry_django.field() - ike_proposal_list: List[IKEProposalTypeV1] = strawberry_django.field() - - ipsec_policy: IPSecPolicyTypeV1 = strawberry_django.field() - ipsec_policy_list: List[IPSecPolicyTypeV1] = strawberry_django.field() - - ipsec_profile: IPSecProfileTypeV1 = strawberry_django.field() - ipsec_profile_list: List[IPSecProfileTypeV1] = strawberry_django.field() - - ipsec_proposal: IPSecProposalTypeV1 = strawberry_django.field() - ipsec_proposal_list: List[IPSecProposalTypeV1] = strawberry_django.field() - - l2vpn: L2VPNTypeV1 = strawberry_django.field() - l2vpn_list: List[L2VPNTypeV1] = strawberry_django.field() - - l2vpn_termination: L2VPNTerminationTypeV1 = strawberry_django.field() - l2vpn_termination_list: List[L2VPNTerminationTypeV1] = strawberry_django.field() - - tunnel: TunnelTypeV1 = strawberry_django.field() - tunnel_list: List[TunnelTypeV1] = strawberry_django.field() - - tunnel_group: TunnelGroupTypeV1 = strawberry_django.field() - tunnel_group_list: List[TunnelGroupTypeV1] = strawberry_django.field() - - tunnel_termination: TunnelTerminationTypeV1 = strawberry_django.field() - tunnel_termination_list: List[TunnelTerminationTypeV1] = strawberry_django.field() diff --git a/netbox/vpn/graphql/types_v1.py b/netbox/vpn/graphql/types_v1.py deleted file mode 100644 index 6872cf6e2..000000000 --- a/netbox/vpn/graphql/types_v1.py +++ /dev/null @@ -1,156 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from extras.graphql.mixins_v1 import ContactsMixinV1, CustomFieldsMixinV1, TagsMixinV1 -from netbox.graphql.types_v1 import ObjectTypeV1, OrganizationalObjectTypeV1, NetBoxObjectTypeV1, PrimaryObjectTypeV1 -from vpn import models -from .filters_v1 import * - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import InterfaceTypeV1 - from ipam.graphql.types_v1 import IPAddressTypeV1, RouteTargetTypeV1, VLANTypeV1 - from netbox.graphql.types_v1 import ContentTypeTypeV1 - from tenancy.graphql.types_v1 import TenantTypeV1 - from virtualization.graphql.types_v1 import VMInterfaceTypeV1 - -__all__ = ( - 'IKEPolicyTypeV1', - 'IKEProposalTypeV1', - 'IPSecPolicyTypeV1', - 'IPSecProfileTypeV1', - 'IPSecProposalTypeV1', - 'L2VPNTypeV1', - 'L2VPNTerminationTypeV1', - 'TunnelGroupTypeV1', - 'TunnelTerminationTypeV1', - 'TunnelTypeV1', -) - - -@strawberry_django.type( - models.TunnelGroup, - fields='__all__', - filters=TunnelGroupFilterV1, - pagination=True -) -class TunnelGroupTypeV1(ContactsMixinV1, OrganizationalObjectTypeV1): - - tunnels: List[Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.TunnelTermination, - fields='__all__', - filters=TunnelTerminationFilterV1, - pagination=True -) -class TunnelTerminationTypeV1(CustomFieldsMixinV1, TagsMixinV1, ObjectTypeV1): - tunnel: Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')] - termination_type: Annotated["ContentTypeTypeV1", strawberry.lazy('netbox.graphql.types_v1')] | None - outside_ip: Annotated["IPAddressTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - - -@strawberry_django.type( - models.Tunnel, - fields='__all__', - filters=TunnelFilterV1, - pagination=True -) -class TunnelTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - group: Annotated["TunnelGroupTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None - ipsec_profile: Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - terminations: List[Annotated["TunnelTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.IKEProposal, - fields='__all__', - filters=IKEProposalFilterV1, - pagination=True -) -class IKEProposalTypeV1(PrimaryObjectTypeV1): - ike_policies: List[Annotated["IKEPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.IKEPolicy, - fields='__all__', - filters=IKEPolicyFilterV1, - pagination=True -) -class IKEPolicyTypeV1(OrganizationalObjectTypeV1): - - proposals: List[Annotated["IKEProposalTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - ipsec_profiles: List[Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.IPSecProposal, - fields='__all__', - filters=IPSecProposalFilterV1, - pagination=True -) -class IPSecProposalTypeV1(PrimaryObjectTypeV1): - - ipsec_policies: List[Annotated["IPSecPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.IPSecPolicy, - fields='__all__', - filters=IPSecPolicyFilterV1, - pagination=True -) -class IPSecPolicyTypeV1(OrganizationalObjectTypeV1): - - proposals: List[Annotated["IPSecProposalTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - ipsec_profiles: List[Annotated["IPSecProfileTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.IPSecProfile, - fields='__all__', - filters=IPSecProfileFilterV1, - pagination=True -) -class IPSecProfileTypeV1(OrganizationalObjectTypeV1): - ike_policy: Annotated["IKEPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')] - ipsec_policy: Annotated["IPSecPolicyTypeV1", strawberry.lazy('vpn.graphql.types_v1')] - - tunnels: List[Annotated["TunnelTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - - -@strawberry_django.type( - models.L2VPN, - fields='__all__', - filters=L2VPNFilterV1, - pagination=True -) -class L2VPNTypeV1(ContactsMixinV1, PrimaryObjectTypeV1): - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - export_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - terminations: List[Annotated["L2VPNTerminationTypeV1", strawberry.lazy('vpn.graphql.types_v1')]] - import_targets: List[Annotated["RouteTargetTypeV1", strawberry.lazy('ipam.graphql.types_v1')]] - - -@strawberry_django.type( - models.L2VPNTermination, - exclude=['assigned_object_type', 'assigned_object_id'], - filters=L2VPNTerminationFilterV1, - pagination=True -) -class L2VPNTerminationTypeV1(NetBoxObjectTypeV1): - l2vpn: Annotated["L2VPNTypeV1", strawberry.lazy('vpn.graphql.types_v1')] - - @strawberry_django.field - def assigned_object(self) -> Annotated[Union[ - Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')], - Annotated["VMInterfaceTypeV1", strawberry.lazy('virtualization.graphql.types_v1')], - ], strawberry.union("L2VPNAssignmentTypeV1")]: - return self.assigned_object diff --git a/netbox/wireless/graphql/filter_mixins_v1.py b/netbox/wireless/graphql/filter_mixins_v1.py deleted file mode 100644 index 2e32f2cde..000000000 --- a/netbox/wireless/graphql/filter_mixins_v1.py +++ /dev/null @@ -1,26 +0,0 @@ -from dataclasses import dataclass -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry_django import FilterLookup - -from core.graphql.filter_mixins_v1 import BaseFilterMixinV1 - -if TYPE_CHECKING: - from .enums import * - -__all__ = ( - 'WirelessAuthenticationBaseFilterMixinV1', -) - - -@dataclass -class WirelessAuthenticationBaseFilterMixinV1(BaseFilterMixinV1): - auth_type: Annotated['WirelessAuthTypeEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - auth_cipher: Annotated['WirelessAuthCipherEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - auth_psk: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/wireless/graphql/filters_v1.py b/netbox/wireless/graphql/filters_v1.py deleted file mode 100644 index 166679843..000000000 --- a/netbox/wireless/graphql/filters_v1.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Annotated, TYPE_CHECKING - -import strawberry -import strawberry_django -from strawberry.scalars import ID -from strawberry_django import FilterLookup - -from dcim.graphql.filter_mixins_v1 import ScopedFilterMixinV1 -from netbox.graphql.filter_mixins_v1 import ( - DistanceFilterMixinV1, PrimaryModelFilterMixinV1, NestedGroupModelFilterMixinV1 -) -from tenancy.graphql.filter_mixins_v1 import TenancyFilterMixinV1 -from wireless import models -from .filter_mixins_v1 import WirelessAuthenticationBaseFilterMixinV1 - -if TYPE_CHECKING: - from dcim.graphql.filters_v1 import InterfaceFilterV1 - from ipam.graphql.filters_v1 import VLANFilterV1 - from .enums import * - -__all__ = ( - 'WirelessLANGroupFilterV1', - 'WirelessLANFilterV1', - 'WirelessLinkFilterV1', -) - - -@strawberry_django.filter_type(models.WirelessLANGroup, lookups=True) -class WirelessLANGroupFilterV1(NestedGroupModelFilterMixinV1): - pass - - -@strawberry_django.filter_type(models.WirelessLAN, lookups=True) -class WirelessLANFilterV1( - WirelessAuthenticationBaseFilterMixinV1, - ScopedFilterMixinV1, - TenancyFilterMixinV1, - PrimaryModelFilterMixinV1 -): - ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) - group: Annotated['WirelessLANGroupFilterV1', strawberry.lazy('wireless.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - group_id: ID | None = strawberry_django.filter_field() - vlan: Annotated['VLANFilterV1', strawberry.lazy('ipam.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - vlan_id: ID | None = strawberry_django.filter_field() - - -@strawberry_django.filter_type(models.WirelessLink, lookups=True) -class WirelessLinkFilterV1( - WirelessAuthenticationBaseFilterMixinV1, - DistanceFilterMixinV1, - TenancyFilterMixinV1, - PrimaryModelFilterMixinV1 -): - interface_a: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interface_a_id: ID | None = strawberry_django.filter_field() - interface_b: Annotated['InterfaceFilterV1', strawberry.lazy('dcim.graphql.filters_v1')] | None = ( - strawberry_django.filter_field() - ) - interface_b_id: ID | None = strawberry_django.filter_field() - ssid: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( - strawberry_django.filter_field() - ) diff --git a/netbox/wireless/graphql/schema_v1.py b/netbox/wireless/graphql/schema_v1.py deleted file mode 100644 index fafe28c08..000000000 --- a/netbox/wireless/graphql/schema_v1.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import List - -import strawberry -import strawberry_django - -from .types_v1 import * - - -@strawberry.type(name="Query") -class WirelessQueryV1: - wireless_lan: WirelessLANTypeV1 = strawberry_django.field() - wireless_lan_list: List[WirelessLANTypeV1] = strawberry_django.field() - - wireless_lan_group: WirelessLANGroupTypeV1 = strawberry_django.field() - wireless_lan_group_list: List[WirelessLANGroupTypeV1] = strawberry_django.field() - - wireless_link: WirelessLinkTypeV1 = strawberry_django.field() - wireless_link_list: List[WirelessLinkTypeV1] = strawberry_django.field() diff --git a/netbox/wireless/graphql/types_v1.py b/netbox/wireless/graphql/types_v1.py deleted file mode 100644 index 2c0fbe12f..000000000 --- a/netbox/wireless/graphql/types_v1.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Annotated, List, TYPE_CHECKING, Union - -import strawberry -import strawberry_django - -from netbox.graphql.types_v1 import PrimaryObjectTypeV1, NestedGroupObjectTypeV1 -from wireless import models -from .filters_v1 import * - -if TYPE_CHECKING: - from dcim.graphql.types_v1 import ( - DeviceTypeV1, InterfaceTypeV1, LocationTypeV1, RegionTypeV1, SiteGroupTypeV1, SiteTypeV1 - ) - from ipam.graphql.types_v1 import VLANTypeV1 - from tenancy.graphql.types_v1 import TenantTypeV1 - -__all__ = ( - 'WirelessLANTypeV1', - 'WirelessLANGroupTypeV1', - 'WirelessLinkTypeV1', -) - - -@strawberry_django.type( - models.WirelessLANGroup, - fields='__all__', - filters=WirelessLANGroupFilterV1, - pagination=True -) -class WirelessLANGroupTypeV1(NestedGroupObjectTypeV1): - parent: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None - - wireless_lans: List[Annotated["WirelessLANTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] - children: List[Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')]] - - -@strawberry_django.type( - models.WirelessLAN, - exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'], - filters=WirelessLANFilterV1, - pagination=True -) -class WirelessLANTypeV1(PrimaryObjectTypeV1): - group: Annotated["WirelessLANGroupTypeV1", strawberry.lazy('wireless.graphql.types_v1')] | None - vlan: Annotated["VLANTypeV1", strawberry.lazy('ipam.graphql.types_v1')] | None - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - - interfaces: List[Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')]] - - @strawberry_django.field - def scope(self) -> Annotated[Union[ - Annotated["LocationTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["RegionTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteGroupTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - Annotated["SiteTypeV1", strawberry.lazy('dcim.graphql.types_v1')], - ], strawberry.union("WirelessLANScopeTypeV1")] | None: - return self.scope - - -@strawberry_django.type( - models.WirelessLink, - fields='__all__', - filters=WirelessLinkFilterV1, - pagination=True -) -class WirelessLinkTypeV1(PrimaryObjectTypeV1): - interface_a: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - interface_b: Annotated["InterfaceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] - tenant: Annotated["TenantTypeV1", strawberry.lazy('tenancy.graphql.types_v1')] | None - _interface_a_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None - _interface_b_device: Annotated["DeviceTypeV1", strawberry.lazy('dcim.graphql.types_v1')] | None From 38b2839a1e185d7fbdba66ae9d5bf69c8235ee36 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Wed, 19 Nov 2025 10:32:11 -0500 Subject: [PATCH 10/15] Remove version-specific unit tests --- netbox/netbox/tests/test_graphql.py | 50 ----------------------------- 1 file changed, 50 deletions(-) diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index f5d69b03e..ca231526f 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -1,15 +1,12 @@ import json -import strawberry from django.test import override_settings from django.urls import reverse from rest_framework import status -from strawberry.types.lazy_type import LazyType from core.models import ObjectType from dcim.choices import LocationStatusChoices from dcim.models import Site, Location -from netbox.graphql.schema import QueryV1, QueryV2 from users.models import ObjectPermission from utilities.testing import disable_warnings, APITestCase, TestCase @@ -48,53 +45,6 @@ class GraphQLTestCase(TestCase): class GraphQLAPITestCase(APITestCase): - def test_versioned_types(self): - """ - Check that the GraphQL types defined for each version of the schema (V1 and V2) are correct. - """ - schemas = ( - (1, QueryV1), - (2, QueryV2), - ) - - def _get_class_name(field): - try: - if type(field.type) is strawberry.types.base.StrawberryList: - # Skip scalars - if field.type.of_type in (str, int): - return - if type(field.type.of_type) is LazyType: - return field.type.of_type.type_name - return field.type.of_type.__name__ - if hasattr(field.type, 'name'): - return field.type.__name__ - except AttributeError: - # Unknown field type - return - - def _check_version(class_name, version): - if version == 1: - self.assertTrue(class_name.endswith('V1'), f"{class_name} (v1) is not a V1 type") - elif version == 2: - self.assertFalse(class_name.endswith('V1'), f"{class_name} (v2) is a V1 type") - - for version, query in schemas: - schema = strawberry.Schema(query=query) - query_type = schema.get_type_by_name(query.__name__) - - # Iterate through root fields - for field in query_type.fields: - # Check for V1 suffix on class names - if type_class := _get_class_name(field): - _check_version(type_class, version) - - # Iterate through nested fields - subquery_type = schema.get_type_by_name(type_class) - for subfield in subquery_type.fields: - # Check for V1 suffix on class names - if type_class := _get_class_name(subfield): - _check_version(type_class, version) - @override_settings(LOGIN_REQUIRED=True) def test_graphql_filter_objects(self): """ From 45fc354d45156d94040e987dfda0519a3128b643 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Wed, 19 Nov 2025 18:25:00 -0500 Subject: [PATCH 11/15] Fix unit tests --- netbox/netbox/tests/test_graphql.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index ca231526f..9dfc5d5df 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -96,11 +96,25 @@ class GraphQLAPITestCase(APITestCase): self.assertEqual(len(data['data']['location_list']), 1) self.assertIsNotNone(data['data']['location_list'][0]['site']) - # Test OR logic + # Test OR and exact logic query = """{ location_list( filters: { - status: STATUS_PLANNED, - OR: {status: STATUS_STAGING} + status: {exact: STATUS_PLANNED}, + OR: {status: {exact: STATUS_STAGING}} + }) { + id site {id} + } + }""" + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = json.loads(response.content) + self.assertNotIn('errors', data) + self.assertEqual(len(data['data']['location_list']), 2) + + # Test in_list logic + query = """{ + location_list( filters: { + status: {in_list: [STATUS_PLANNED, STATUS_STAGING]} }) { id site {id} } From 5143003c684cfe8aedbd4cbaab5f207a8bbfb178 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 21 Nov 2025 10:36:54 -0500 Subject: [PATCH 12/15] Add filters for missing fields and for enums in filter_mixins files --- netbox/core/graphql/enums.py | 11 +++++++++++ netbox/core/graphql/filter_mixins.py | 1 + netbox/core/graphql/filters.py | 11 ++++++++--- netbox/dcim/graphql/enums.py | 4 ++++ netbox/dcim/graphql/filter_mixins.py | 21 ++++++++++++++------- netbox/dcim/graphql/filters.py | 7 ++++++- netbox/netbox/graphql/filter_mixins.py | 6 +++--- netbox/virtualization/graphql/enums.py | 2 ++ netbox/virtualization/graphql/filters.py | 5 +++++ netbox/vpn/graphql/enums.py | 2 ++ netbox/vpn/graphql/filters.py | 3 +++ 11 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 netbox/core/graphql/enums.py diff --git a/netbox/core/graphql/enums.py b/netbox/core/graphql/enums.py new file mode 100644 index 000000000..863382258 --- /dev/null +++ b/netbox/core/graphql/enums.py @@ -0,0 +1,11 @@ +import strawberry + +from core.choices import * + +__all__ = ( + 'DataSourceStatusEnum', + 'ObjectChangeActionEnum', +) + +DataSourceStatusEnum = strawberry.enum(DataSourceStatusChoices.as_enum(prefix='status')) +ObjectChangeActionEnum = strawberry.enum(ObjectChangeActionChoices.as_enum(prefix='action')) diff --git a/netbox/core/graphql/filter_mixins.py b/netbox/core/graphql/filter_mixins.py index 27d950d73..4d1f4fad5 100644 --- a/netbox/core/graphql/filter_mixins.py +++ b/netbox/core/graphql/filter_mixins.py @@ -29,6 +29,7 @@ class BaseObjectTypeFilterMixin(BaseFilterMixin): @dataclass class ChangeLogFilterMixin(BaseFilterMixin): id: FilterLookup[ID] | None = strawberry_django.filter_field() + # TODO: "changelog" is not a valid field name; needs to be updated for ObjectChange changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/core/graphql/filters.py b/netbox/core/graphql/filters.py index 76ace2362..db42d4d59 100644 --- a/netbox/core/graphql/filters.py +++ b/netbox/core/graphql/filters.py @@ -5,11 +5,12 @@ import strawberry import strawberry_django from django.contrib.contenttypes.models import ContentType as DjangoContentType from strawberry.scalars import ID -from strawberry_django import DatetimeFilterLookup, FilterLookup +from strawberry_django import BaseFilterLookup, DatetimeFilterLookup, FilterLookup from core import models from core.graphql.filter_mixins import BaseFilterMixin from netbox.graphql.filter_mixins import PrimaryModelFilterMixin +from .enums import * if TYPE_CHECKING: from netbox.graphql.filter_lookups import IntegerLookup, JSONFilter @@ -44,7 +45,9 @@ class DataSourceFilter(PrimaryModelFilterMixin): name: FilterLookup[str] | None = strawberry_django.filter_field() type: FilterLookup[str] | None = strawberry_django.filter_field() source_url: FilterLookup[str] | None = strawberry_django.filter_field() - status: FilterLookup[str] | None = strawberry_django.filter_field() + status: ( + BaseFilterLookup[Annotated['DataSourceStatusEnum', strawberry.lazy('core.graphql.enums')]] | None + ) = strawberry_django.filter_field() enabled: FilterLookup[bool] | None = strawberry_django.filter_field() ignore_rules: FilterLookup[str] | None = strawberry_django.filter_field() parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -63,7 +66,9 @@ class ObjectChangeFilter(BaseFilterMixin): user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_name: FilterLookup[str] | None = strawberry_django.filter_field() request_id: FilterLookup[str] | None = strawberry_django.filter_field() - action: FilterLookup[str] | None = strawberry_django.filter_field() + action: ( + BaseFilterLookup[Annotated['ObjectChangeActionEnum', strawberry.lazy('core.graphql.enums')]] | None + ) = strawberry_django.filter_field() changed_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/dcim/graphql/enums.py b/netbox/dcim/graphql/enums.py index 62a666b45..437094d72 100644 --- a/netbox/dcim/graphql/enums.py +++ b/netbox/dcim/graphql/enums.py @@ -28,11 +28,13 @@ __all__ = ( 'PowerFeedSupplyEnum', 'PowerFeedTypeEnum', 'PowerOutletFeedLegEnum', + 'PowerOutletStatusEnum', 'PowerOutletTypeEnum', 'PowerPortTypeEnum', 'RackAirflowEnum', 'RackDimensionUnitEnum', 'RackFormFactorEnum', + 'RackReservationStatusEnum', 'RackStatusEnum', 'RackWidthEnum', 'SiteStatusEnum', @@ -65,11 +67,13 @@ PowerFeedStatusEnum = strawberry.enum(PowerFeedStatusChoices.as_enum(prefix='sta PowerFeedSupplyEnum = strawberry.enum(PowerFeedSupplyChoices.as_enum(prefix='supply')) PowerFeedTypeEnum = strawberry.enum(PowerFeedTypeChoices.as_enum(prefix='type')) PowerOutletFeedLegEnum = strawberry.enum(PowerOutletFeedLegChoices.as_enum(prefix='feed_leg')) +PowerOutletStatusEnum = strawberry.enum(PowerOutletStatusChoices.as_enum(prefix='status')) PowerOutletTypeEnum = strawberry.enum(PowerOutletTypeChoices.as_enum(prefix='type')) PowerPortTypeEnum = strawberry.enum(PowerPortTypeChoices.as_enum(prefix='type')) RackAirflowEnum = strawberry.enum(RackAirflowChoices.as_enum()) RackDimensionUnitEnum = strawberry.enum(RackDimensionUnitChoices.as_enum(prefix='unit')) RackFormFactorEnum = strawberry.enum(RackFormFactorChoices.as_enum(prefix='type')) +RackReservationStatusEnum = strawberry.enum(RackReservationStatusChoices.as_enum(prefix='status')) RackStatusEnum = strawberry.enum(RackStatusChoices.as_enum(prefix='status')) RackWidthEnum = strawberry.enum(RackWidthChoices.as_enum(prefix='width')) SiteStatusEnum = strawberry.enum(SiteStatusChoices.as_enum(prefix='status')) diff --git a/netbox/dcim/graphql/filter_mixins.py b/netbox/dcim/graphql/filter_mixins.py index 25379ad7f..c05eec1f5 100644 --- a/netbox/dcim/graphql/filter_mixins.py +++ b/netbox/dcim/graphql/filter_mixins.py @@ -4,7 +4,7 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django from strawberry import ID -from strawberry_django import FilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup from core.graphql.filter_mixins import BaseFilterMixin, ChangeLogFilterMixin from core.graphql.filters import ContentTypeFilter @@ -60,7 +60,9 @@ class ModularComponentModelFilterMixin(ComponentModelFilterMixin): class CabledObjectModelFilterMixin(BaseFilterMixin): cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() cable_id: ID | None = strawberry_django.filter_field() - cable_end: CableEndEnum | None = strawberry_django.filter_field() + cable_end: ( + BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None + ) = strawberry_django.filter_field() mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field() @@ -96,7 +98,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin): mtu: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - mode: InterfaceModeEnum | None = strawberry_django.filter_field() + mode: ( + BaseFilterLookup[Annotated['InterfaceModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None + ) = strawberry_django.filter_field() bridge: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -110,8 +114,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin): qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) - vlan_translation_policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None \ - = strawberry_django.filter_field() + vlan_translation_policy: ( + Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None + ) = strawberry_django.filter_field() primary_mac_address: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -120,7 +125,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin): @dataclass class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin): - width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + width: BaseFilterLookup[Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) u_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @@ -137,7 +144,7 @@ class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin): outer_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) - outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + outer_unit: BaseFilterLookup[Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( strawberry_django.filter_field() ) mounting_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index de156b55b..a6d076410 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -1,6 +1,5 @@ from typing import Annotated, TYPE_CHECKING -from django.db.models import Q import strawberry import strawberry_django from strawberry.scalars import ID @@ -810,6 +809,9 @@ class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilte color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) + status: BaseFilterLookup[Annotated['PowerOutletStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True) @@ -924,6 +926,9 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin): user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_id: ID | None = strawberry_django.filter_field() description: FilterLookup[str] | None = strawberry_django.filter_field() + status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.RackRole, lookups=True) diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index b77238325..cb9fcffe7 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -5,7 +5,7 @@ from typing import TypeVar, TYPE_CHECKING, Annotated import strawberry import strawberry_django from strawberry import ID -from strawberry_django import FilterLookup, DatetimeFilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup, DatetimeFilterLookup from core.graphql.filter_mixins import BaseFilterMixin, BaseObjectTypeFilterMixin, ChangeLogFilterMixin from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin @@ -76,7 +76,7 @@ class ImageAttachmentFilterMixin(BaseFilterMixin): @dataclass class WeightFilterMixin(BaseFilterMixin): weight: FilterLookup[float] | None = strawberry_django.filter_field() - weight_unit: Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + weight_unit: BaseFilterLookup[Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) @@ -99,6 +99,6 @@ class SyncedDataFilterMixin(BaseFilterMixin): @dataclass class DistanceFilterMixin(BaseFilterMixin): distance: FilterLookup[float] | None = strawberry_django.filter_field() - distance_unit: Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + distance_unit: BaseFilterLookup[Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')]] | None = ( strawberry_django.filter_field() ) diff --git a/netbox/virtualization/graphql/enums.py b/netbox/virtualization/graphql/enums.py index 3c7ebad8a..7a67e6868 100644 --- a/netbox/virtualization/graphql/enums.py +++ b/netbox/virtualization/graphql/enums.py @@ -5,7 +5,9 @@ from virtualization.choices import * __all__ = ( 'ClusterStatusEnum', 'VirtualMachineStatusEnum', + 'VirtualMachineStatusEnum', ) ClusterStatusEnum = strawberry.enum(ClusterStatusChoices.as_enum(prefix='status')) +VirtualMachineStartOnBootEnum = strawberry.enum(VirtualMachineStartOnBootChoices.as_enum(prefix='start_on_boot')) VirtualMachineStatusEnum = strawberry.enum(VirtualMachineStatusChoices.as_enum(prefix='status')) diff --git a/netbox/virtualization/graphql/filters.py b/netbox/virtualization/graphql/filters.py index 5b29e362a..ad8e40ee5 100644 --- a/netbox/virtualization/graphql/filters.py +++ b/netbox/virtualization/graphql/filters.py @@ -130,6 +130,11 @@ class VirtualMachineFilter( virtual_disks: Annotated['VirtualDiskFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( strawberry_django.filter_field() ) + start_on_boot: ( + BaseFilterLookup[Annotated['VirtualMachineStartOnBootEnum', strawberry.lazy('virtualization.graphql.enums')] + ] | None) = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.VMInterface, lookups=True) diff --git a/netbox/vpn/graphql/enums.py b/netbox/vpn/graphql/enums.py index 518528b83..5007386c2 100644 --- a/netbox/vpn/graphql/enums.py +++ b/netbox/vpn/graphql/enums.py @@ -10,6 +10,7 @@ __all__ = ( 'IKEModeEnum', 'IKEVersionEnum', 'IPSecModeEnum', + 'L2VPNStatusEnum', 'L2VPNTypeEnum', 'TunnelEncapsulationEnum', 'TunnelStatusEnum', @@ -24,6 +25,7 @@ EncryptionAlgorithmEnum = strawberry.enum(EncryptionAlgorithmChoices.as_enum(pre IKEModeEnum = strawberry.enum(IKEModeChoices.as_enum()) IKEVersionEnum = strawberry.enum(IKEVersionChoices.as_enum(prefix='version')) IPSecModeEnum = strawberry.enum(IPSecModeChoices.as_enum()) +L2VPNStatusEnum = strawberry.enum(L2VPNStatusChoices.as_enum(prefix='status')) L2VPNTypeEnum = strawberry.enum(L2VPNTypeChoices.as_enum(prefix='type')) TunnelEncapsulationEnum = strawberry.enum(TunnelEncapsulationChoices.as_enum(prefix='encap')) TunnelStatusEnum = strawberry.enum(TunnelStatusChoices.as_enum(prefix='status')) diff --git a/netbox/vpn/graphql/filters.py b/netbox/vpn/graphql/filters.py index 7955550c9..403ece3b3 100644 --- a/netbox/vpn/graphql/filters.py +++ b/netbox/vpn/graphql/filters.py @@ -200,6 +200,9 @@ class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixi terminations: Annotated['L2VPNTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( strawberry_django.filter_field() ) + status: BaseFilterLookup[Annotated['L2VPNStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter_type(models.L2VPNTermination, lookups=True) From 1dcfc05c32bba7b77ccbec716700e21ae1810bff Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Fri, 21 Nov 2025 14:59:27 -0500 Subject: [PATCH 13/15] Add import Q back in --- netbox/dcim/graphql/filters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index a6d076410..1c99beedb 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -1,5 +1,6 @@ from typing import Annotated, TYPE_CHECKING +from django.db.models import Q import strawberry import strawberry_django from strawberry.scalars import ID From ae21a6a6847a418b6efa82c14adace5b925712be Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 25 Nov 2025 13:06:24 -0500 Subject: [PATCH 14/15] Change explicitly specified id fields to FilterLookups --- netbox/core/graphql/filters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/core/graphql/filters.py b/netbox/core/graphql/filters.py index db42d4d59..b61917c7e 100644 --- a/netbox/core/graphql/filters.py +++ b/netbox/core/graphql/filters.py @@ -26,7 +26,7 @@ __all__ = ( @strawberry_django.filter_type(models.DataFile, lookups=True) class DataFileFilter(BaseFilterMixin): - id: ID | None = strawberry_django.filter_field() + id: FilterLookup[ID] | None = strawberry_django.filter_field() created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -61,7 +61,7 @@ class DataSourceFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.ObjectChange, lookups=True) class ObjectChangeFilter(BaseFilterMixin): - id: ID | None = strawberry_django.filter_field() + id: FilterLookup[ID] | None = strawberry_django.filter_field() time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() user_name: FilterLookup[str] | None = strawberry_django.filter_field() @@ -89,6 +89,6 @@ class ObjectChangeFilter(BaseFilterMixin): @strawberry_django.filter_type(DjangoContentType, lookups=True) class ContentTypeFilter(BaseFilterMixin): - id: ID | None = strawberry_django.filter_field() + id: FilterLookup[ID] | None = strawberry_django.filter_field() app_label: FilterLookup[str] | None = strawberry_django.filter_field() model: FilterLookup[str] | None = strawberry_django.filter_field() From 7cc7c7ab81e9950ebe769371e1b9a67c86517de5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 25 Nov 2025 13:18:15 -0500 Subject: [PATCH 15/15] Closes #20788: Cable profiles and and position mapping (#20802) --- docs/models/dcim/cable.md | 15 + netbox/circuits/filtersets.py | 2 +- .../migrations/0054_cable_position.py | 23 + netbox/dcim/api/serializers_/cables.py | 13 +- netbox/dcim/cable_profiles.py | 108 +++ netbox/dcim/choices.py | 13 + netbox/dcim/constants.py | 8 + netbox/dcim/filtersets.py | 17 +- netbox/dcim/forms/bulk_edit.py | 10 +- netbox/dcim/forms/bulk_import.py | 10 +- netbox/dcim/forms/filtersets.py | 7 +- netbox/dcim/forms/model_forms.py | 4 +- .../dcim/management/commands/trace_paths.py | 4 +- netbox/dcim/migrations/0219_cable_profile.py | 40 + netbox/dcim/migrations/0220_cable_position.py | 107 +++ netbox/dcim/models/cables.py | 90 +- netbox/dcim/models/device_components.py | 26 +- netbox/dcim/signals.py | 4 +- netbox/dcim/tables/cables.py | 5 +- netbox/dcim/tests/test_api.py | 5 +- netbox/dcim/tests/test_cablepaths.py | 136 ++- netbox/dcim/tests/test_cablepaths2.py | 788 ++++++++++++++++++ netbox/dcim/tests/utils.py | 88 ++ netbox/dcim/utils.py | 19 +- netbox/project-static/dist/netbox.js | 2 +- netbox/project-static/dist/netbox.js.map | 4 +- netbox/project-static/src/select/config.ts | 5 + netbox/templates/dcim/cable.html | 4 + netbox/templates/dcim/htmx/cable_edit.html | 1 + netbox/wireless/signals.py | 4 +- 30 files changed, 1418 insertions(+), 144 deletions(-) create mode 100644 netbox/circuits/migrations/0054_cable_position.py create mode 100644 netbox/dcim/cable_profiles.py create mode 100644 netbox/dcim/migrations/0219_cable_profile.py create mode 100644 netbox/dcim/migrations/0220_cable_position.py create mode 100644 netbox/dcim/tests/test_cablepaths2.py create mode 100644 netbox/dcim/tests/utils.py diff --git a/docs/models/dcim/cable.md b/docs/models/dcim/cable.md index 20f6c03c7..8a60d8353 100644 --- a/docs/models/dcim/cable.md +++ b/docs/models/dcim/cable.md @@ -21,6 +21,21 @@ The cable's operational status. Choices include: * Planned * Decommissioning +### Profile + +!!! note "This field was introduced in NetBox v4.5." + +The profile to which the cable conforms. The profile determines the mapping of termination between the two ends and enables logical tracing across complex connections, such as breakout cables. Supported profiles are listed below. + +* Straight (single position) +* Straight (multi-position) +* Shuffle (2x2 MPO8) +* Shuffle (4x4 MPO8) + +A single-position cable is allowed only one termination point at each end. There is no limit to the number of terminations a multi-position cable may have. Each end of a cable must have the same number of terminations, unless connected to a pass-through port or to a circuit termination. + +The assignment of a cable profile is optional. If no profile is assigned, legacy tracing behavior will be preserved. + ### Type The cable's physical medium or classification. diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index 9d95037ec..77f713899 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -346,7 +346,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet): model = CircuitTermination fields = ( 'id', 'termination_id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', - 'mark_connected', 'pp_info', 'cable_end', + 'mark_connected', 'pp_info', 'cable_end', 'cable_position', ) def search(self, queryset, name, value): diff --git a/netbox/circuits/migrations/0054_cable_position.py b/netbox/circuits/migrations/0054_cable_position.py new file mode 100644 index 000000000..cedc8813b --- /dev/null +++ b/netbox/circuits/migrations/0054_cable_position.py @@ -0,0 +1,23 @@ +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('circuits', '0053_owner'), + ] + + operations = [ + migrations.AddField( + model_name='circuittermination', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + ] diff --git a/netbox/dcim/api/serializers_/cables.py b/netbox/dcim/api/serializers_/cables.py index 5f3017368..0effbd536 100644 --- a/netbox/dcim/api/serializers_/cables.py +++ b/netbox/dcim/api/serializers_/cables.py @@ -25,15 +25,16 @@ class CableSerializer(PrimaryModelSerializer): a_terminations = GenericObjectSerializer(many=True, required=False) b_terminations = GenericObjectSerializer(many=True, required=False) status = ChoiceField(choices=LinkStatusChoices, required=False) + profile = ChoiceField(choices=CableProfileChoices, required=False) tenant = TenantSerializer(nested=True, required=False, allow_null=True) length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False, allow_null=True) class Meta: model = Cable fields = [ - 'id', 'url', 'display_url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'tenant', - 'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', + 'id', 'url', 'display_url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'profile', + 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'label', 'description') @@ -60,10 +61,12 @@ class CableTerminationSerializer(NetBoxModelSerializer): model = CableTermination fields = [ 'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id', - 'termination', 'created', 'last_updated', + 'termination', 'position', 'created', 'last_updated', ] read_only_fields = fields - brief_fields = ('id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id') + brief_fields = ( + 'id', 'url', 'display', 'cable', 'cable_end', 'position', 'termination_type', 'termination_id', + ) class CablePathSerializer(serializers.ModelSerializer): diff --git a/netbox/dcim/cable_profiles.py b/netbox/dcim/cable_profiles.py new file mode 100644 index 000000000..4251cd4d9 --- /dev/null +++ b/netbox/dcim/cable_profiles.py @@ -0,0 +1,108 @@ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from dcim.models import CableTermination + + +class BaseCableProfile: + # Maximum number of terminations allowed per side + a_max_connections = None + b_max_connections = None + + def clean(self, cable): + # Enforce maximum connection limits + if self.a_max_connections and len(cable.a_terminations) > self.a_max_connections: + raise ValidationError({ + 'a_terminations': _( + 'Maximum A side connections for profile {profile}: {max}' + ).format( + profile=cable.get_profile_display(), + max=self.a_max_connections, + ) + }) + if self.b_max_connections and len(cable.b_terminations) > self.b_max_connections: + raise ValidationError({ + 'b_terminations': _( + 'Maximum B side connections for profile {profile}: {max}' + ).format( + profile=cable.get_profile_display(), + max=self.b_max_connections, + ) + }) + + def get_mapped_position(self, side, position): + """ + Return the mapped position for a given cable end and position. + + By default, assume all positions are symmetrical. + """ + return position + + def get_peer_terminations(self, terminations, position_stack): + local_end = terminations[0].cable_end + qs = CableTermination.objects.filter( + cable=terminations[0].cable, + cable_end=terminations[0].opposite_cable_end + ) + + # TODO: Optimize this to use a single query under any condition + if position_stack: + # Attempt to find a peer termination at the same position currently in the stack. Pop the stack only if + # we find one. Otherwise, return any peer terminations with a null position. + position = self.get_mapped_position(local_end, position_stack[-1][0]) + if peers := qs.filter(position=position): + position_stack.pop() + return peers + + return qs.filter(position=None) + + +class StraightSingleCableProfile(BaseCableProfile): + a_max_connections = 1 + b_max_connections = 1 + + +class StraightMultiCableProfile(BaseCableProfile): + a_max_connections = None + b_max_connections = None + + +class Shuffle2x2MPO8CableProfile(BaseCableProfile): + a_max_connections = 8 + b_max_connections = 8 + _mapping = { + 1: 1, + 2: 2, + 3: 5, + 4: 6, + 5: 3, + 6: 4, + 7: 7, + 8: 8, + } + + def get_mapped_position(self, side, position): + return self._mapping.get(position) + + +class Shuffle4x4MPO8CableProfile(BaseCableProfile): + a_max_connections = 8 + b_max_connections = 8 + # A side to B side position mapping + _a_mapping = { + 1: 1, + 2: 3, + 3: 5, + 4: 7, + 5: 2, + 6: 4, + 7: 6, + 8: 8, + } + # B side to A side position mapping (reverse of _a_mapping) + _b_mapping = {v: k for k, v in _a_mapping.items()} + + def get_mapped_position(self, side, position): + if side.lower() == 'b': + return self._b_mapping.get(position) + return self._a_mapping.get(position) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index cca2dd0bb..0656d96aa 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1717,6 +1717,19 @@ class PortTypeChoices(ChoiceSet): # Cables/links # +class CableProfileChoices(ChoiceSet): + STRAIGHT_SINGLE = 'straight-single' + STRAIGHT_MULTI = 'straight-multi' + SHUFFLE_2X2_MPO8 = 'shuffle-2x2-mpo8' + SHUFFLE_4X4_MPO8 = 'shuffle-4x4-mpo8' + + CHOICES = ( + (STRAIGHT_SINGLE, _('Straight (single position)')), + (STRAIGHT_MULTI, _('Straight (multi-position)')), + (SHUFFLE_2X2_MPO8, _('Shuffle (2x2 MPO8)')), + (SHUFFLE_4X4_MPO8, _('Shuffle (4x4 MPO8)')), + ) + class CableTypeChoices(ChoiceSet): # Copper - Twisted Pair (UTP/STP) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 387b4d6a7..5e4311c13 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -20,6 +20,14 @@ RACK_ELEVATION_DEFAULT_MARGIN_WIDTH = 15 RACK_STARTING_UNIT_DEFAULT = 1 +# +# Cables +# + +CABLE_POSITION_MIN = 1 +CABLE_POSITION_MAX = 1024 + + # # RearPorts # diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 0fd7631ac..9c161aa54 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1699,7 +1699,7 @@ class ConsolePortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSe class Meta: model = ConsolePort - fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end') + fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end', 'cable_position') class ConsoleServerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet): @@ -1710,7 +1710,7 @@ class ConsoleServerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFi class Meta: model = ConsoleServerPort - fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end') + fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end', 'cable_position') class PowerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet): @@ -1723,6 +1723,7 @@ class PowerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, model = PowerPort fields = ( 'id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable_end', + 'cable_position', ) @@ -1748,6 +1749,7 @@ class PowerOutletFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSe model = PowerOutlet fields = ( 'id', 'name', 'status', 'label', 'feed_leg', 'description', 'color', 'mark_connected', 'cable_end', + 'cable_position', ) @@ -2055,7 +2057,7 @@ class InterfaceFilterSet( fields = ( 'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'poe_mode', 'poe_type', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', - 'cable_id', 'cable_end', + 'cable_id', 'cable_end', 'cable_position', ) def filter_virtual_chassis_member_or_master(self, queryset, name, value): @@ -2107,6 +2109,7 @@ class FrontPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet) model = FrontPort fields = ( 'id', 'name', 'label', 'type', 'color', 'rear_port_position', 'description', 'mark_connected', 'cable_end', + 'cable_position', ) @@ -2120,6 +2123,7 @@ class RearPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet): model = RearPort fields = ( 'id', 'name', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable_end', + 'cable_position', ) @@ -2316,6 +2320,9 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet): status = django_filters.MultipleChoiceFilter( choices=LinkStatusChoices ) + profile = django_filters.MultipleChoiceFilter( + choices=CableProfileChoices + ) color = django_filters.MultipleChoiceFilter( choices=ColorChoices ) @@ -2465,7 +2472,7 @@ class CableTerminationFilterSet(ChangeLoggedModelFilterSet): class Meta: model = CableTermination - fields = ('id', 'cable', 'cable_end', 'termination_type', 'termination_id') + fields = ('id', 'cable', 'cable_end', 'position', 'termination_type', 'termination_id') class PowerPanelFilterSet(PrimaryModelFilterSet, ContactModelFilterSet): @@ -2582,7 +2589,7 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CabledObjectFilterSet, PathEndpo model = PowerFeed fields = ( 'id', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', - 'available_power', 'mark_connected', 'cable_end', 'description', + 'available_power', 'mark_connected', 'cable_end', 'cable_position', 'description', ) def search(self, queryset, name, value): diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 6d1e4d7cc..9aa076a6a 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -780,6 +780,12 @@ class CableBulkEditForm(PrimaryModelBulkEditForm): required=False, initial='' ) + profile = forms.ChoiceField( + label=_('Profile'), + choices=add_blank_choice(CableProfileChoices), + required=False, + initial='' + ) tenant = DynamicModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), @@ -808,11 +814,11 @@ class CableBulkEditForm(PrimaryModelBulkEditForm): model = Cable fieldsets = ( - FieldSet('type', 'status', 'tenant', 'label', 'description'), + FieldSet('type', 'status', 'profile', 'tenant', 'label', 'description'), FieldSet('color', 'length', 'length_unit', name=_('Attributes')), ) nullable_fields = ( - 'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments', + 'type', 'status', 'profile', 'tenant', 'label', 'color', 'length', 'description', 'comments', ) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 127511779..ba0b44b0d 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1461,6 +1461,12 @@ class CableImportForm(PrimaryModelImportForm): required=False, help_text=_('Connection status') ) + profile = CSVChoiceField( + label=_('Profile'), + choices=CableProfileChoices, + required=False, + help_text=_('Cable connection profile') + ) type = CSVChoiceField( label=_('Type'), choices=CableTypeChoices, @@ -1491,8 +1497,8 @@ class CableImportForm(PrimaryModelImportForm): model = Cable fields = [ 'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type', - 'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', - 'owner', 'comments', 'tags', + 'side_b_name', 'type', 'status', 'profile', 'tenant', 'label', 'color', 'length', 'length_unit', + 'description', 'owner', 'comments', 'tags', ] def __init__(self, data=None, *args, **kwargs): diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 1197002a5..f874ce916 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -1119,7 +1119,7 @@ class CableFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): fieldsets = ( FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')), - FieldSet('type', 'status', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')), + FieldSet('type', 'status', 'profile', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) region_id = DynamicModelMultipleChoiceField( @@ -1175,6 +1175,11 @@ class CableFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): required=False, choices=add_blank_choice(LinkStatusChoices) ) + profile = forms.MultipleChoiceField( + label=_('Profile'), + required=False, + choices=add_blank_choice(CableProfileChoices) + ) color = ColorField( label=_('Color'), required=False diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index a774bb90f..75a827476 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -807,8 +807,8 @@ class CableForm(TenancyForm, PrimaryModelForm): class Meta: model = Cable fields = [ - 'a_terminations_type', 'b_terminations_type', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', - 'length', 'length_unit', 'description', 'owner', 'comments', 'tags', + 'a_terminations_type', 'b_terminations_type', 'type', 'status', 'profile', 'tenant_group', 'tenant', + 'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags', ] diff --git a/netbox/dcim/management/commands/trace_paths.py b/netbox/dcim/management/commands/trace_paths.py index 592aeb6a7..ded4e1780 100644 --- a/netbox/dcim/management/commands/trace_paths.py +++ b/netbox/dcim/management/commands/trace_paths.py @@ -4,7 +4,7 @@ from django.db import connection from django.db.models import Q from dcim.models import CablePath, ConsolePort, ConsoleServerPort, Interface, PowerFeed, PowerOutlet, PowerPort -from dcim.signals import create_cablepath +from dcim.signals import create_cablepaths ENDPOINT_MODELS = ( ConsolePort, @@ -81,7 +81,7 @@ class Command(BaseCommand): self.stdout.write(f'Retracing {origins_count} cabled {model._meta.verbose_name_plural}...') i = 0 for i, obj in enumerate(origins, start=1): - create_cablepath([obj]) + create_cablepaths([obj]) if not i % 100: self.draw_progress_bar(i * 100 / origins_count) self.draw_progress_bar(100) diff --git a/netbox/dcim/migrations/0219_cable_profile.py b/netbox/dcim/migrations/0219_cable_profile.py new file mode 100644 index 000000000..140ce800b --- /dev/null +++ b/netbox/dcim/migrations/0219_cable_profile.py @@ -0,0 +1,40 @@ +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0218_devicetype_device_count'), + ] + + operations = [ + migrations.AddField( + model_name='cable', + name='profile', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='cabletermination', + name='position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AlterModelOptions( + name='cabletermination', + options={'ordering': ('cable', 'cable_end', 'position', 'pk')}, + ), + migrations.AddConstraint( + model_name='cabletermination', + constraint=models.UniqueConstraint( + fields=('cable', 'cable_end', 'position'), + name='dcim_cabletermination_unique_position' + ), + ), + ] diff --git a/netbox/dcim/migrations/0220_cable_position.py b/netbox/dcim/migrations/0220_cable_position.py new file mode 100644 index 000000000..6ee74174b --- /dev/null +++ b/netbox/dcim/migrations/0220_cable_position.py @@ -0,0 +1,107 @@ +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0219_cable_profile'), + ] + + operations = [ + migrations.AddField( + model_name='consoleport', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='consoleserverport', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='frontport', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='interface', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='powerfeed', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='poweroutlet', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='powerport', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + migrations.AddField( + model_name='rearport', + name='cable_position', + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(1024), + ], + ), + ), + ] diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 73ea08ff4..94ebc7570 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -3,6 +3,7 @@ import itertools from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.dispatch import Signal from django.utils.translation import gettext_lazy as _ @@ -20,7 +21,7 @@ from utilities.fields import ColorField, GenericArrayForeignKey from utilities.querysets import RestrictedQuerySet from utilities.serialization import deserialize_object, serialize_object from wireless.models import WirelessLink -from .device_components import FrontPort, RearPort, PathEndpoint +from .device_components import FrontPort, PathEndpoint, RearPort __all__ = ( 'Cable', @@ -54,6 +55,12 @@ class Cable(PrimaryModel): choices=LinkStatusChoices, default=LinkStatusChoices.STATUS_CONNECTED ) + profile = models.CharField( + verbose_name=_('profile'), + max_length=50, + choices=CableProfileChoices, + blank=True, + ) tenant = models.ForeignKey( to='tenancy.Tenant', on_delete=models.PROTECT, @@ -92,7 +99,7 @@ class Cable(PrimaryModel): null=True ) - clone_fields = ('tenant', 'type',) + clone_fields = ('tenant', 'type', 'profile') class Meta: ordering = ('pk',) @@ -123,6 +130,16 @@ class Cable(PrimaryModel): def get_status_color(self): return LinkStatusChoices.colors.get(self.status) + @property + def profile_class(self): + from dcim import cable_profiles + return { + CableProfileChoices.STRAIGHT_SINGLE: cable_profiles.StraightSingleCableProfile, + CableProfileChoices.STRAIGHT_MULTI: cable_profiles.StraightMultiCableProfile, + CableProfileChoices.SHUFFLE_2X2_MPO8: cable_profiles.Shuffle2x2MPO8CableProfile, + CableProfileChoices.SHUFFLE_4X4_MPO8: cable_profiles.Shuffle4x4MPO8CableProfile, + }.get(self.profile) + def _get_x_terminations(self, side): """ Return the terminating objects for the given cable end (A or B). @@ -195,6 +212,10 @@ class Cable(PrimaryModel): if self._state.adding and self.pk is None and (not self.a_terminations or not self.b_terminations): raise ValidationError(_("Must define A and B terminations when creating a new cable.")) + # Validate terminations against the assigned cable profile (if any) + if self.profile: + self.profile_class().clean(self) + if self._terminations_modified: # Check that all termination objects for either end are of the same type @@ -315,12 +336,14 @@ class Cable(PrimaryModel): ct.delete() # Save any new CableTerminations - for termination in self.a_terminations: + for i, termination in enumerate(self.a_terminations, start=1): if not termination.pk or termination not in a_terminations: - CableTermination(cable=self, cable_end='A', termination=termination).save() - for termination in self.b_terminations: + position = i if self.profile and isinstance(termination, PathEndpoint) else None + CableTermination(cable=self, cable_end='A', position=position, termination=termination).save() + for i, termination in enumerate(self.b_terminations, start=1): if not termination.pk or termination not in b_terminations: - CableTermination(cable=self, cable_end='B', termination=termination).save() + position = i if self.profile and isinstance(termination, PathEndpoint) else None + CableTermination(cable=self, cable_end='B', position=position, termination=termination).save() class CableTermination(ChangeLoggedModel): @@ -347,6 +370,14 @@ class CableTermination(ChangeLoggedModel): ct_field='termination_type', fk_field='termination_id' ) + position = models.PositiveIntegerField( + blank=True, + null=True, + validators=( + MinValueValidator(CABLE_POSITION_MIN), + MaxValueValidator(CABLE_POSITION_MAX) + ) + ) # Cached associations to enable efficient filtering _device = models.ForeignKey( @@ -377,12 +408,16 @@ class CableTermination(ChangeLoggedModel): objects = RestrictedQuerySet.as_manager() class Meta: - ordering = ('cable', 'cable_end', 'pk') + ordering = ('cable', 'cable_end', 'position', 'pk') constraints = ( models.UniqueConstraint( fields=('termination_type', 'termination_id'), name='%(app_label)s_%(class)s_unique_termination' ), + models.UniqueConstraint( + fields=('cable', 'cable_end', 'position'), + name='%(app_label)s_%(class)s_unique_position' + ), ) verbose_name = _('cable termination') verbose_name_plural = _('cable terminations') @@ -446,6 +481,7 @@ class CableTermination(ChangeLoggedModel): termination.snapshot() termination.cable = self.cable termination.cable_end = self.cable_end + termination.cable_position = self.position termination.save() def delete(self, *args, **kwargs): @@ -455,6 +491,7 @@ class CableTermination(ChangeLoggedModel): termination.snapshot() termination.cable = None termination.cable_end = None + termination.cable_position = None termination.save() super().delete(*args, **kwargs) @@ -653,6 +690,9 @@ class CablePath(models.Model): path.append([ object_to_path_node(t) for t in terminations ]) + # If not null, push cable_position onto the stack + if terminations[0].cable_position is not None: + position_stack.append([terminations[0].cable_position]) # Step 2: Determine the attached links (Cable or WirelessLink), if any links = [termination.link for termination in terminations if termination.link is not None] @@ -687,23 +727,31 @@ class CablePath(models.Model): # Step 6: Determine the far-end terminations if isinstance(links[0], Cable): - termination_type = ObjectType.objects.get_for_model(terminations[0]) - local_cable_terminations = CableTermination.objects.filter( - termination_type=termination_type, - termination_id__in=[t.pk for t in terminations] - ) + # Profile-based tracing + if links[0].profile: + cable_profile = links[0].profile_class() + peer_cable_terminations = cable_profile.get_peer_terminations(terminations, position_stack) + remote_terminations = [ct.termination for ct in peer_cable_terminations] - q_filter = Q() - for lct in local_cable_terminations: - cable_end = 'A' if lct.cable_end == 'B' else 'B' - q_filter |= Q(cable=lct.cable, cable_end=cable_end) + # Legacy (positionless) behavior + else: + termination_type = ObjectType.objects.get_for_model(terminations[0]) + local_cable_terminations = CableTermination.objects.filter( + termination_type=termination_type, + termination_id__in=[t.pk for t in terminations] + ) - # Make sure this filter has been populated; if not, we have probably been given invalid data - if not q_filter: - break + q_filter = Q() + for lct in local_cable_terminations: + cable_end = 'A' if lct.cable_end == 'B' else 'B' + q_filter |= Q(cable=lct.cable, cable_end=cable_end) - remote_cable_terminations = CableTermination.objects.filter(q_filter) - remote_terminations = [ct.termination for ct in remote_cable_terminations] + # Make sure this filter has been populated; if not, we have probably been given invalid data + if not q_filter: + break + + remote_cable_terminations = CableTermination.objects.filter(q_filter) + remote_terminations = [ct.termination for ct in remote_cable_terminations] else: # WirelessLink remote_terminations = [ diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 3e801a8e9..8c9acc48f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -175,6 +175,15 @@ class CabledObjectModel(models.Model): blank=True, null=True ) + cable_position = models.PositiveIntegerField( + verbose_name=_('cable position'), + blank=True, + null=True, + validators=( + MinValueValidator(CABLE_POSITION_MIN), + MaxValueValidator(CABLE_POSITION_MAX) + ), + ) mark_connected = models.BooleanField( verbose_name=_('mark connected'), default=False, @@ -194,14 +203,23 @@ class CabledObjectModel(models.Model): def clean(self): super().clean() - if self.cable and not self.cable_end: - raise ValidationError({ - "cable_end": _("Must specify cable end (A or B) when attaching a cable.") - }) + if self.cable: + if not self.cable_end: + raise ValidationError({ + "cable_end": _("Must specify cable end (A or B) when attaching a cable.") + }) + if not self.cable_position: + raise ValidationError({ + "cable_position": _("Must specify cable termination position when attaching a cable.") + }) if self.cable_end and not self.cable: raise ValidationError({ "cable_end": _("Cable end must not be set without a cable.") }) + if self.cable_position and not self.cable: + raise ValidationError({ + "cable_position": _("Cable termination position must not be set without a cable.") + }) if self.mark_connected and self.cable: raise ValidationError({ "mark_connected": _("Cannot mark as connected with a cable attached.") diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 9295ddbdb..eb1825c1a 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -11,7 +11,7 @@ from .models import ( VirtualChassis, ) from .models.cables import trace_paths -from .utils import create_cablepath, rebuild_paths +from .utils import create_cablepaths, rebuild_paths COMPONENT_MODELS = ( ConsolePort, @@ -114,7 +114,7 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs): if not nodes: continue if isinstance(nodes[0], PathEndpoint): - create_cablepath(nodes) + create_cablepaths(nodes) else: rebuild_paths(nodes) diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index a4e3be269..72220591e 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -108,6 +108,7 @@ class CableTable(TenancyColumnsMixin, PrimaryModelTable): verbose_name=_('Site B') ) status = columns.ChoiceFieldColumn() + profile = columns.ChoiceFieldColumn() length = columns.TemplateColumn( template_code=CABLE_LENGTH, order_by=('_abs_length') @@ -125,8 +126,8 @@ class CableTable(TenancyColumnsMixin, PrimaryModelTable): model = Cable fields = ( 'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b', - 'location_a', 'location_b', 'site_a', 'site_b', 'status', 'type', 'tenant', 'tenant_group', 'color', - 'color_name', 'length', 'description', 'comments', 'tags', 'created', 'last_updated', + 'location_a', 'location_b', 'site_a', 'site_b', 'status', 'profile', 'type', 'tenant', 'tenant_group', + 'color', 'color_name', 'length', 'description', 'comments', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'status', 'type', diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 938a625b0..bdade5395 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -2396,6 +2396,7 @@ class CableTest(APIViewTestCases.APIViewTestCase): 'object_id': interfaces[14].pk, }], 'label': 'Cable 4', + 'profile': CableProfileChoices.STRAIGHT_SINGLE, }, { 'a_terminations': [{ @@ -2407,6 +2408,7 @@ class CableTest(APIViewTestCases.APIViewTestCase): 'object_id': interfaces[15].pk, }], 'label': 'Cable 5', + 'profile': CableProfileChoices.STRAIGHT_SINGLE, }, { 'a_terminations': [{ @@ -2418,6 +2420,7 @@ class CableTest(APIViewTestCases.APIViewTestCase): 'object_id': interfaces[16].pk, }], 'label': 'Cable 6', + # No profile (legacy behavior) }, ] @@ -2427,7 +2430,7 @@ class CableTerminationTest( APIViewTestCases.ListObjectsViewTestCase, ): model = CableTermination - brief_fields = ['cable', 'cable_end', 'display', 'id', 'termination_id', 'termination_type', 'url'] + brief_fields = ['cable', 'cable_end', 'display', 'id', 'position', 'termination_id', 'termination_type', 'url'] @classmethod def setUpTestData(cls): diff --git a/netbox/dcim/tests/test_cablepaths.py b/netbox/dcim/tests/test_cablepaths.py index 399478e70..d3a7cfc5e 100644 --- a/netbox/dcim/tests/test_cablepaths.py +++ b/netbox/dcim/tests/test_cablepaths.py @@ -1,100 +1,21 @@ -from django.test import TestCase - from circuits.models import * from dcim.choices import LinkStatusChoices from dcim.models import * from dcim.svg import CableTraceSVG -from dcim.utils import object_to_path_node +from dcim.tests.utils import CablePathTestCase from utilities.exceptions import AbortRequest -class CablePathTestCase(TestCase): +class LegacyCablePathTests(CablePathTestCase): """ - Test NetBox's ability to trace and retrace CablePaths in response to data model changes. Tests are numbered - as follows: + Test NetBox's ability to trace and retrace CablePaths in response to data model changes, without cable profiles. + Tests are numbered as follows: 1XX: Test direct connections between different endpoint types 2XX: Test different cable topologies 3XX: Test responses to changes in existing objects 4XX: Test to exclude specific cable topologies """ - @classmethod - def setUpTestData(cls): - - # Create a single device that will hold all components - cls.site = Site.objects.create(name='Site', slug='site') - - manufacturer = Manufacturer.objects.create(name='Generic', slug='generic') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device') - role = DeviceRole.objects.create(name='Device Role', slug='device-role') - cls.device = Device.objects.create(site=cls.site, device_type=device_type, role=role, name='Test Device') - - cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel') - - provider = Provider.objects.create(name='Provider', slug='provider') - circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type') - cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1') - - def _get_cablepath(self, nodes, **kwargs): - """ - Return a given cable path - - :param nodes: Iterable of steps, with each step being either a single node or a list of nodes - - :return: The matching CablePath (if any) - """ - path = [] - for step in nodes: - if type(step) in (list, tuple): - path.append([object_to_path_node(node) for node in step]) - else: - path.append([object_to_path_node(step)]) - return CablePath.objects.filter(path=path, **kwargs).first() - - def assertPathExists(self, nodes, **kwargs): - """ - Assert that a CablePath from origin to destination with a specific intermediate path exists. Returns the - first matching CablePath, if found. - - :param nodes: Iterable of steps, with each step being either a single node or a list of nodes - """ - cablepath = self._get_cablepath(nodes, **kwargs) - self.assertIsNotNone(cablepath, msg='CablePath not found') - - return cablepath - - def assertPathDoesNotExist(self, nodes, **kwargs): - """ - Assert that a specific CablePath does *not* exist. - - :param nodes: Iterable of steps, with each step being either a single node or a list of nodes - """ - cablepath = self._get_cablepath(nodes, **kwargs) - self.assertIsNone(cablepath, msg='Unexpected CablePath found') - - def assertPathIsSet(self, origin, cablepath, msg=None): - """ - Assert that a specific CablePath instance is set as the path on the origin. - - :param origin: The originating path endpoint - :param cablepath: The CablePath instance originating from this endpoint - :param msg: Custom failure message (optional) - """ - if msg is None: - msg = f"Path #{cablepath.pk} not set on originating endpoint {origin}" - self.assertEqual(origin._path_id, cablepath.pk, msg=msg) - - def assertPathIsNotSet(self, origin, msg=None): - """ - Assert that a specific CablePath instance is set as the path on the origin. - - :param origin: The originating path endpoint - :param msg: Custom failure message (optional) - """ - if msg is None: - msg = f"Path #{origin._path_id} set as origin on {origin}; should be None!" - self.assertIsNone(origin._path_id, msg=msg) - def test_101_interface_to_interface(self): """ [IF1] --C1-- [IF2] @@ -2270,6 +2191,55 @@ class CablePathTestCase(TestCase): CableTraceSVG(interface1).render() CableTraceSVG(interface2).render() + def test_223_single_path_via_multiple_pass_throughs_with_breakouts(self): + """ + [IF1] --C1-- [FP1] [RP1] --C2-- [IF3] + [IF2] [FP2] [RP2] [IF4] + """ + interface1 = Interface.objects.create(device=self.device, name='Interface 1') + interface2 = Interface.objects.create(device=self.device, name='Interface 2') + interface3 = Interface.objects.create(device=self.device, name='Interface 3') + interface4 = Interface.objects.create(device=self.device, name='Interface 4') + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1) + rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1) + frontport1 = FrontPort.objects.create( + device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1 + ) + frontport2 = FrontPort.objects.create( + device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1 + ) + + # Create cables + cable1 = Cable( + a_terminations=[interface1, interface2], + b_terminations=[frontport1, frontport2] + ) + cable1.save() + cable2 = Cable( + a_terminations=[rearport1, rearport2], + b_terminations=[interface3, interface4] + ) + cable2.save() + + # Validate paths + self.assertPathExists( + ( + [interface1, interface2], cable1, [frontport1, frontport2], + [rearport1, rearport2], cable2, [interface3, interface4], + ), + is_complete=True, + is_active=True + ) + self.assertPathExists( + ( + [interface3, interface4], cable2, [rearport1, rearport2], + [frontport1, frontport2], cable1, [interface1, interface2], + ), + is_complete=True, + is_active=True + ) + self.assertEqual(CablePath.objects.count(), 2) + def test_301_create_path_via_existing_cable(self): """ [IF1] --C1-- [FP1] [RP1] --C2-- [RP2] [FP2] --C3-- [IF2] diff --git a/netbox/dcim/tests/test_cablepaths2.py b/netbox/dcim/tests/test_cablepaths2.py new file mode 100644 index 000000000..c7895c6d2 --- /dev/null +++ b/netbox/dcim/tests/test_cablepaths2.py @@ -0,0 +1,788 @@ +from unittest import skipIf + +from circuits.models import CircuitTermination +from dcim.choices import CableProfileChoices +from dcim.models import * +from dcim.svg import CableTraceSVG +from dcim.tests.utils import CablePathTestCase + + +class CablePathTests(CablePathTestCase): + """ + Test the creation of CablePaths for Cables with different profiles applied. + + Tests are numbered as follows: + 1XX: Test direct connections using each profile + 2XX: Topology tests replicated from the legacy test case and adapted to use profiles + """ + + def test_101_cable_profile_straight_single(self): + """ + [IF1] --C1-- [IF2] + + Cable profile: Straight single + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + ] + + # Create cable 1 + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_SINGLE, + a_terminations=[interfaces[0]], + b_terminations=[interfaces[1]], + ) + cable1.clean() + cable1.save() + + path1 = self.assertPathExists( + (interfaces[0], cable1, interfaces[1]), + is_complete=True, + is_active=True + ) + path2 = self.assertPathExists( + (interfaces[1], cable1, interfaces[0]), + is_complete=True, + is_active=True + ) + self.assertEqual(CablePath.objects.count(), 2) + interfaces[0].refresh_from_db() + interfaces[1].refresh_from_db() + self.assertPathIsSet(interfaces[0], path1) + self.assertPathIsSet(interfaces[1], path2) + self.assertEqual(interfaces[0].cable_position, 1) + self.assertEqual(interfaces[1].cable_position, 1) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + # Delete cable 1 + cable1.delete() + + # Check that all CablePaths have been deleted + self.assertEqual(CablePath.objects.count(), 0) + + def test_102_cable_profile_straight_multi(self): + """ + [IF1] --C1-- [IF3] + [IF2] [IF4] + + Cable profile: Straight multi + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + Interface.objects.create(device=self.device, name='Interface 3'), + Interface.objects.create(device=self.device, name='Interface 4'), + ] + + # Create cable 1 + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0], interfaces[1]], + b_terminations=[interfaces[2], interfaces[3]], + ) + cable1.clean() + cable1.save() + + path1 = self.assertPathExists( + (interfaces[0], cable1, interfaces[2]), + is_complete=True, + is_active=True + ) + path2 = self.assertPathExists( + (interfaces[1], cable1, interfaces[3]), + is_complete=True, + is_active=True + ) + path3 = self.assertPathExists( + (interfaces[2], cable1, interfaces[0]), + is_complete=True, + is_active=True + ) + path4 = self.assertPathExists( + (interfaces[3], cable1, interfaces[1]), + is_complete=True, + is_active=True + ) + self.assertEqual(CablePath.objects.count(), 4) + + for interface in interfaces: + interface.refresh_from_db() + self.assertPathIsSet(interfaces[0], path1) + self.assertPathIsSet(interfaces[1], path2) + self.assertPathIsSet(interfaces[2], path3) + self.assertPathIsSet(interfaces[3], path4) + self.assertEqual(interfaces[0].cable_position, 1) + self.assertEqual(interfaces[1].cable_position, 2) + self.assertEqual(interfaces[2].cable_position, 1) + self.assertEqual(interfaces[3].cable_position, 2) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + # Delete cable 1 + cable1.delete() + + # Check that all CablePaths have been deleted + self.assertEqual(CablePath.objects.count(), 0) + + def test_103_cable_profile_2x2_mpo8(self): + """ + [IF1:1] --C1-- [IF3:1] + [IF1:2] [IF3:2] + [IF1:3] [IF3:3] + [IF1:4] [IF3:4] + [IF2:1] [IF4:1] + [IF2:2] [IF4:2] + [IF2:3] [IF4:3] + [IF2:4] [IF4:4] + + Cable profile: Shuffle (2x2 MPO8) + """ + interfaces = [ + # A side + Interface.objects.create(device=self.device, name='Interface 1:1'), + Interface.objects.create(device=self.device, name='Interface 1:2'), + Interface.objects.create(device=self.device, name='Interface 1:3'), + Interface.objects.create(device=self.device, name='Interface 1:4'), + Interface.objects.create(device=self.device, name='Interface 2:1'), + Interface.objects.create(device=self.device, name='Interface 2:2'), + Interface.objects.create(device=self.device, name='Interface 2:3'), + Interface.objects.create(device=self.device, name='Interface 2:4'), + # B side + Interface.objects.create(device=self.device, name='Interface 3:1'), + Interface.objects.create(device=self.device, name='Interface 3:2'), + Interface.objects.create(device=self.device, name='Interface 3:3'), + Interface.objects.create(device=self.device, name='Interface 3:4'), + Interface.objects.create(device=self.device, name='Interface 4:1'), + Interface.objects.create(device=self.device, name='Interface 4:2'), + Interface.objects.create(device=self.device, name='Interface 4:3'), + Interface.objects.create(device=self.device, name='Interface 4:4'), + ] + + # Create cable 1 + cable1 = Cable( + profile=CableProfileChoices.SHUFFLE_2X2_MPO8, + a_terminations=interfaces[0:8], + b_terminations=interfaces[8:16], + ) + cable1.clean() + cable1.save() + + paths = [ + # A-to-B paths + self.assertPathExists( + (interfaces[0], cable1, interfaces[8]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[1], cable1, interfaces[9]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[2], cable1, interfaces[12]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[3], cable1, interfaces[13]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[4], cable1, interfaces[10]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[5], cable1, interfaces[11]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[6], cable1, interfaces[14]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[7], cable1, interfaces[15]), is_complete=True, is_active=True + ), + # B-to-A paths + self.assertPathExists( + (interfaces[8], cable1, interfaces[0]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[9], cable1, interfaces[1]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[10], cable1, interfaces[4]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[11], cable1, interfaces[5]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[12], cable1, interfaces[2]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[13], cable1, interfaces[3]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[14], cable1, interfaces[6]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[15], cable1, interfaces[7]), is_complete=True, is_active=True + ), + ] + self.assertEqual(CablePath.objects.count(), len(paths)) + + for i, (interface, path) in enumerate(zip(interfaces, paths)): + interface.refresh_from_db() + self.assertPathIsSet(interface, path) + self.assertEqual(interface.cable_end, 'A' if i < 8 else 'B') + self.assertEqual(interface.cable_position, (i % 8) + 1) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + # Delete cable 1 + cable1.delete() + + # Check that all CablePaths have been deleted + self.assertEqual(CablePath.objects.count(), 0) + + def test_104_cable_profile_4x4_mpo8(self): + """ + [IF1:1] --C1-- [IF3:1] + [IF1:2] [IF3:2] + [IF1:3] [IF3:3] + [IF1:4] [IF3:4] + [IF2:1] [IF4:1] + [IF2:2] [IF4:2] + [IF2:3] [IF4:3] + [IF2:4] [IF4:4] + + Cable profile: Shuffle (4x4 MPO8) + """ + interfaces = [ + # A side + Interface.objects.create(device=self.device, name='Interface 1:1'), + Interface.objects.create(device=self.device, name='Interface 1:2'), + Interface.objects.create(device=self.device, name='Interface 2:1'), + Interface.objects.create(device=self.device, name='Interface 2:2'), + Interface.objects.create(device=self.device, name='Interface 3:1'), + Interface.objects.create(device=self.device, name='Interface 3:2'), + Interface.objects.create(device=self.device, name='Interface 4:1'), + Interface.objects.create(device=self.device, name='Interface 4:2'), + # B side + Interface.objects.create(device=self.device, name='Interface 5:1'), + Interface.objects.create(device=self.device, name='Interface 5:2'), + Interface.objects.create(device=self.device, name='Interface 6:1'), + Interface.objects.create(device=self.device, name='Interface 6:2'), + Interface.objects.create(device=self.device, name='Interface 7:1'), + Interface.objects.create(device=self.device, name='Interface 7:2'), + Interface.objects.create(device=self.device, name='Interface 8:1'), + Interface.objects.create(device=self.device, name='Interface 8:2'), + ] + + # Create cable 1 + cable1 = Cable( + profile=CableProfileChoices.SHUFFLE_4X4_MPO8, + a_terminations=interfaces[0:8], + b_terminations=interfaces[8:16], + ) + cable1.clean() + cable1.save() + + paths = [ + # A-to-B paths + self.assertPathExists( + (interfaces[0], cable1, interfaces[8]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[1], cable1, interfaces[10]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[2], cable1, interfaces[12]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[3], cable1, interfaces[14]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[4], cable1, interfaces[9]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[5], cable1, interfaces[11]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[6], cable1, interfaces[13]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[7], cable1, interfaces[15]), is_complete=True, is_active=True + ), + # B-to-A paths + self.assertPathExists( + (interfaces[8], cable1, interfaces[0]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[9], cable1, interfaces[4]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[10], cable1, interfaces[1]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[11], cable1, interfaces[5]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[12], cable1, interfaces[2]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[13], cable1, interfaces[6]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[14], cable1, interfaces[3]), is_complete=True, is_active=True + ), + self.assertPathExists( + (interfaces[15], cable1, interfaces[7]), is_complete=True, is_active=True + ), + ] + self.assertEqual(CablePath.objects.count(), len(paths)) + + for i, (interface, path) in enumerate(zip(interfaces, paths)): + interface.refresh_from_db() + self.assertPathIsSet(interface, path) + self.assertEqual(interface.cable_end, 'A' if i < 8 else 'B') + self.assertEqual(interface.cable_position, (i % 8) + 1) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + # Delete cable 1 + cable1.delete() + + # Check that all CablePaths have been deleted + self.assertEqual(CablePath.objects.count(), 0) + + def test_202_single_path_via_pass_through_with_breakouts(self): + """ + [IF1] --C1-- [FP1] [RP1] --C2-- [IF3] + [IF2] [IF4] + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + Interface.objects.create(device=self.device, name='Interface 3'), + Interface.objects.create(device=self.device, name='Interface 4'), + ] + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1) + frontport1 = FrontPort.objects.create( + device=self.device, + name='Front Port 1', + rear_port=rearport1, + rear_port_position=1 + ) + + # Create cables + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0], interfaces[1]], + b_terminations=[frontport1], + ) + cable1.clean() + cable1.save() + cable2 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[rearport1], + b_terminations=[interfaces[2], interfaces[3]] + ) + cable2.clean() + cable2.save() + + paths = [ + self.assertPathExists( + (interfaces[0], cable1, frontport1, rearport1, cable2, interfaces[2]), + is_complete=True, + is_active=True + ), + self.assertPathExists( + (interfaces[1], cable1, frontport1, rearport1, cable2, interfaces[3]), + is_complete=True, + is_active=True + ), + self.assertPathExists( + (interfaces[2], cable2, rearport1, frontport1, cable1, interfaces[0]), + is_complete=True, + is_active=True + ), + self.assertPathExists( + (interfaces[3], cable2, rearport1, frontport1, cable1, interfaces[1]), + is_complete=True, + is_active=True + ), + ] + self.assertEqual(CablePath.objects.count(), 4) + for interface in interfaces: + interface.refresh_from_db() + self.assertPathIsSet(interfaces[0], paths[0]) + self.assertPathIsSet(interfaces[1], paths[1]) + self.assertPathIsSet(interfaces[2], paths[2]) + self.assertPathIsSet(interfaces[3], paths[3]) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + def test_204_multiple_paths_via_pass_through_with_breakouts(self): + """ + [IF1] --C1-- [FP1:1] [RP1] --C3-- [RP2] [FP2:1] --C4-- [IF4] + [IF2] [IF5] + [IF3] --C2-- [FP1:2] [FP2:2] --C5-- [IF6] + [IF4] [IF7] + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + Interface.objects.create(device=self.device, name='Interface 3'), + Interface.objects.create(device=self.device, name='Interface 4'), + Interface.objects.create(device=self.device, name='Interface 5'), + Interface.objects.create(device=self.device, name='Interface 6'), + Interface.objects.create(device=self.device, name='Interface 7'), + Interface.objects.create(device=self.device, name='Interface 8'), + ] + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4) + rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4) + frontport1_1 = FrontPort.objects.create( + device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1 + ) + frontport1_2 = FrontPort.objects.create( + device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2 + ) + frontport2_1 = FrontPort.objects.create( + device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1 + ) + frontport2_2 = FrontPort.objects.create( + device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2 + ) + + # Create cables + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0], interfaces[1]], + b_terminations=[frontport1_1] + ) + cable1.clean() + cable1.save() + cable2 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[2], interfaces[3]], + b_terminations=[frontport1_2] + ) + cable2.clean() + cable2.save() + cable3 = Cable( + profile=CableProfileChoices.STRAIGHT_SINGLE, + a_terminations=[rearport1], + b_terminations=[rearport2] + ) + cable3.clean() + cable3.save() + cable4 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[frontport2_1], + b_terminations=[interfaces[4], interfaces[5]] + ) + cable4.clean() + cable4.save() + cable5 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[frontport2_2], + b_terminations=[interfaces[6], interfaces[7]] + ) + cable5.clean() + cable5.save() + + paths = [ + self.assertPathExists( + ( + interfaces[0], cable1, frontport1_1, rearport1, cable3, rearport2, frontport2_1, cable4, + interfaces[4], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[1], cable1, frontport1_1, rearport1, cable3, rearport2, frontport2_1, cable4, + interfaces[5], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[2], cable2, frontport1_2, rearport1, cable3, rearport2, frontport2_2, cable5, + interfaces[6], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[3], cable2, frontport1_2, rearport1, cable3, rearport2, frontport2_2, cable5, + interfaces[7], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[4], cable4, frontport2_1, rearport2, cable3, rearport1, frontport1_1, cable1, + interfaces[0], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[5], cable4, frontport2_1, rearport2, cable3, rearport1, frontport1_1, cable1, + interfaces[1], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[6], cable5, frontport2_2, rearport2, cable3, rearport1, frontport1_2, cable2, + interfaces[2], + ), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + ( + interfaces[7], cable5, frontport2_2, rearport2, cable3, rearport1, frontport1_2, cable2, + interfaces[3], + ), + is_complete=True, + is_active=True, + ), + ] + self.assertEqual(CablePath.objects.count(), 8) + + for interface in interfaces: + interface.refresh_from_db() + self.assertPathIsSet(interfaces[0], paths[0]) + self.assertPathIsSet(interfaces[1], paths[1]) + self.assertPathIsSet(interfaces[2], paths[2]) + self.assertPathIsSet(interfaces[3], paths[3]) + self.assertPathIsSet(interfaces[4], paths[4]) + self.assertPathIsSet(interfaces[5], paths[5]) + self.assertPathIsSet(interfaces[6], paths[6]) + self.assertPathIsSet(interfaces[7], paths[7]) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + def test_212_interface_to_interface_via_circuit_with_breakouts(self): + """ + [IF1] --C1-- [CT1] [CT2] --C2-- [IF3] + [IF2] [IF4] + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + Interface.objects.create(device=self.device, name='Interface 3'), + Interface.objects.create(device=self.device, name='Interface 4'), + ] + circuittermination1 = CircuitTermination.objects.create( + circuit=self.circuit, + termination=self.site, + term_side='A' + ) + circuittermination2 = CircuitTermination.objects.create( + circuit=self.circuit, + termination=self.site, + term_side='Z' + ) + + # Create cables + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0], interfaces[1]], + b_terminations=[circuittermination1] + ) + cable1.clean() + cable1.save() + cable2 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[circuittermination2], + b_terminations=[interfaces[2], interfaces[3]] + ) + cable2.clean() + cable2.save() + + # Check for two complete paths in either direction + paths = [ + self.assertPathExists( + (interfaces[0], cable1, circuittermination1, circuittermination2, cable2, interfaces[2]), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + (interfaces[1], cable1, circuittermination1, circuittermination2, cable2, interfaces[3]), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + (interfaces[2], cable2, circuittermination2, circuittermination1, cable1, interfaces[0]), + is_complete=True, + is_active=True, + ), + self.assertPathExists( + (interfaces[3], cable2, circuittermination2, circuittermination1, cable1, interfaces[1]), + is_complete=True, + is_active=True, + ), + ] + self.assertEqual(CablePath.objects.count(), 4) + + for interface in interfaces: + interface.refresh_from_db() + self.assertPathIsSet(interfaces[0], paths[0]) + self.assertPathIsSet(interfaces[1], paths[1]) + self.assertPathIsSet(interfaces[2], paths[2]) + self.assertPathIsSet(interfaces[3], paths[3]) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + def test_217_interface_to_interface_via_rear_ports(self): + """ + [IF1] --C1-- [FP1] [RP1] --C2-- [RP3] [FP3] --C3-- [IF2] + [FP2] [RP2] [RP4] [FP4] + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + ] + rear_ports = [ + RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1), + RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1), + RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1), + RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1), + ] + front_ports = [ + FrontPort.objects.create( + device=self.device, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1 + ), + FrontPort.objects.create( + device=self.device, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1 + ), + FrontPort.objects.create( + device=self.device, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1 + ), + FrontPort.objects.create( + device=self.device, name='Front Port 4', rear_port=rear_ports[3], rear_port_position=1 + ), + ] + + # Create cables + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0]], + b_terminations=[front_ports[0], front_ports[1]] + ) + cable1.clean() + cable1.save() + cable2 = Cable( + a_terminations=[rear_ports[0], rear_ports[1]], + b_terminations=[rear_ports[2], rear_ports[3]] + ) + cable2.clean() + cable2.save() + cable3 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[1]], + b_terminations=[front_ports[2], front_ports[3]] + ) + cable3.clean() + cable3.save() + + # Check for one complete path in either direction + paths = [ + self.assertPathExists( + ( + interfaces[0], cable1, (front_ports[0], front_ports[1]), (rear_ports[0], rear_ports[1]), cable2, + (rear_ports[2], rear_ports[3]), (front_ports[2], front_ports[3]), cable3, interfaces[1] + ), + is_complete=True + ), + self.assertPathExists( + ( + interfaces[1], cable3, (front_ports[2], front_ports[3]), (rear_ports[2], rear_ports[3]), cable2, + (rear_ports[0], rear_ports[1]), (front_ports[0], front_ports[1]), cable1, interfaces[0] + ), + is_complete=True + ), + ] + self.assertEqual(CablePath.objects.count(), 2) + + for interface in interfaces: + interface.refresh_from_db() + self.assertPathIsSet(interfaces[0], paths[0]) + self.assertPathIsSet(interfaces[1], paths[1]) + + # Test SVG generation + CableTraceSVG(interfaces[0]).render() + + # TODO: Revisit this test under FR #20564 + @skipIf(True, "Waiting for FR #20564") + def test_223_single_path_via_multiple_pass_throughs_with_breakouts(self): + """ + [IF1] --C1-- [FP1] [RP1] --C2-- [IF3] + [IF2] [FP2] [RP2] [IF4] + """ + interfaces = [ + Interface.objects.create(device=self.device, name='Interface 1'), + Interface.objects.create(device=self.device, name='Interface 2'), + Interface.objects.create(device=self.device, name='Interface 3'), + Interface.objects.create(device=self.device, name='Interface 4'), + ] + rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1) + rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1) + frontport1 = FrontPort.objects.create( + device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1 + ) + frontport2 = FrontPort.objects.create( + device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1 + ) + + # Create cables + cable1 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[interfaces[0], interfaces[1]], + b_terminations=[frontport1, frontport2] + ) + cable1.clean() + cable1.save() + cable2 = Cable( + profile=CableProfileChoices.STRAIGHT_MULTI, + a_terminations=[rearport1, rearport2], + b_terminations=[interfaces[2], interfaces[3]] + ) + cable2.clean() + cable2.save() + + for path in CablePath.objects.all(): + print(f'{path}: {path.path_objects}') + + # Validate paths + self.assertPathExists( + (interfaces[0], cable1, [frontport1, frontport2], [rearport1, rearport2], cable2, interfaces[2]), + is_complete=True, + is_active=True + ) + self.assertPathExists( + (interfaces[1], cable1, [frontport1, frontport2], [rearport1, rearport2], cable2, interfaces[3]), + is_complete=True, + is_active=True + ) + self.assertPathExists( + (interfaces[2], cable2, [rearport1, rearport2], [frontport1, frontport2], cable1, interfaces[0]), + is_complete=True, + is_active=True + ) + self.assertPathExists( + (interfaces[3], cable2, [rearport1, rearport2], [frontport1, frontport2], cable1, interfaces[1]), + is_complete=True, + is_active=True + ) + self.assertEqual(CablePath.objects.count(), 4) diff --git a/netbox/dcim/tests/utils.py b/netbox/dcim/tests/utils.py new file mode 100644 index 000000000..575034201 --- /dev/null +++ b/netbox/dcim/tests/utils.py @@ -0,0 +1,88 @@ +from django.test import TestCase + +from circuits.models import * +from dcim.models import * +from dcim.utils import object_to_path_node + +__all__ = ( + 'CablePathTestCase', +) + + +class CablePathTestCase(TestCase): + """ + Base class for test cases for cable paths. + """ + @classmethod + def setUpTestData(cls): + manufacturer = Manufacturer.objects.create(name='Generic', slug='generic') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device') + role = DeviceRole.objects.create(name='Device Role', slug='device-role') + provider = Provider.objects.create(name='Provider', slug='provider') + circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type') + + # Create reusable test objects + cls.site = Site.objects.create(name='Site', slug='site') + cls.device = Device.objects.create(site=cls.site, device_type=device_type, role=role, name='Test Device') + cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel') + cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1') + + def _get_cablepath(self, nodes, **kwargs): + """ + Return a given cable path + + :param nodes: Iterable of steps, with each step being either a single node or a list of nodes + + :return: The matching CablePath (if any) + """ + path = [] + for step in nodes: + if type(step) in (list, tuple): + path.append([object_to_path_node(node) for node in step]) + else: + path.append([object_to_path_node(step)]) + return CablePath.objects.filter(path=path, **kwargs).first() + + def assertPathExists(self, nodes, **kwargs): + """ + Assert that a CablePath from origin to destination with a specific intermediate path exists. Returns the + first matching CablePath, if found. + + :param nodes: Iterable of steps, with each step being either a single node or a list of nodes + """ + cablepath = self._get_cablepath(nodes, **kwargs) + self.assertIsNotNone(cablepath, msg='CablePath not found') + + return cablepath + + def assertPathDoesNotExist(self, nodes, **kwargs): + """ + Assert that a specific CablePath does *not* exist. + + :param nodes: Iterable of steps, with each step being either a single node or a list of nodes + """ + cablepath = self._get_cablepath(nodes, **kwargs) + self.assertIsNone(cablepath, msg='Unexpected CablePath found') + + def assertPathIsSet(self, origin, cablepath, msg=None): + """ + Assert that a specific CablePath instance is set as the path on the origin. + + :param origin: The originating path endpoint + :param cablepath: The CablePath instance originating from this endpoint + :param msg: Custom failure message (optional) + """ + if msg is None: + msg = f"Path #{cablepath.pk} not set on originating endpoint {origin}" + self.assertEqual(origin._path_id, cablepath.pk, msg=msg) + + def assertPathIsNotSet(self, origin, msg=None): + """ + Assert that a specific CablePath instance is set as the path on the origin. + + :param origin: The originating path endpoint + :param msg: Custom failure message (optional) + """ + if msg is None: + msg = f"Path #{origin._path_id} set as origin on {origin}; should be None!" + self.assertIsNone(origin._path_id, msg=msg) diff --git a/netbox/dcim/utils.py b/netbox/dcim/utils.py index a03790ea2..2380fbd0d 100644 --- a/netbox/dcim/utils.py +++ b/netbox/dcim/utils.py @@ -1,3 +1,5 @@ +from collections import defaultdict + from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.db import router, transaction @@ -31,17 +33,22 @@ def path_node_to_object(repr): return ct.model_class().objects.filter(pk=object_id).first() -def create_cablepath(terminations): +def create_cablepaths(objects): """ Create CablePaths for all paths originating from the specified set of nodes. - :param terminations: Iterable of CableTermination objects + :param objects: Iterable of cabled objects (e.g. Interfaces) """ from dcim.models import CablePath - cp = CablePath.from_origin(terminations) - if cp: - cp.save() + # Arrange objects by cable position. All objects with a null position are grouped together. + origins = defaultdict(list) + for obj in objects: + origins[obj.cable_position].append(obj) + + for position, objects in origins.items(): + if cp := CablePath.from_origin(objects): + cp.save() def rebuild_paths(terminations): @@ -56,7 +63,7 @@ def rebuild_paths(terminations): with transaction.atomic(using=router.db_for_write(CablePath)): for cp in cable_paths: cp.delete() - create_cablepath(cp.origins) + create_cablepaths(cp.origins) def update_interface_bridges(device, interface_templates, module=None): diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 095469011..54218a290 100644 --- a/netbox/project-static/dist/netbox.js +++ b/netbox/project-static/dist/netbox.js @@ -1,5 +1,5 @@ "use strict";(()=>{var tu=Object.create;var Mi=Object.defineProperty,nu=Object.defineProperties,iu=Object.getOwnPropertyDescriptor,ru=Object.getOwnPropertyDescriptors,ou=Object.getOwnPropertyNames,ps=Object.getOwnPropertySymbols,su=Object.getPrototypeOf,ms=Object.prototype.hasOwnProperty,au=Object.prototype.propertyIsEnumerable;var qr=(n,e,t)=>e in n?Mi(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,O=(n,e)=>{for(var t in e||(e={}))ms.call(e,t)&&qr(n,t,e[t]);if(ps)for(var t of ps(e))au.call(e,t)&&qr(n,t,e[t]);return n},ae=(n,e)=>nu(n,ru(e));var lu=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports),gs=(n,e)=>{for(var t in e)Mi(n,t,{get:e[t],enumerable:!0})},cu=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ou(e))!ms.call(n,r)&&r!==t&&Mi(n,r,{get:()=>e[r],enumerable:!(i=iu(e,r))||i.enumerable});return n};var uu=(n,e,t)=>(t=n!=null?tu(su(n)):{},cu(e||!n||!n.__esModule?Mi(t,"default",{value:n,enumerable:!0}):t,n));var ee=(n,e,t)=>qr(n,typeof e!="symbol"?e+"":e,t);var at=(n,e,t)=>new Promise((i,r)=>{var o=l=>{try{a(t.next(l))}catch(c){r(c)}},s=l=>{try{a(t.throw(l))}catch(c){r(c)}},a=l=>l.done?i(l.value):Promise.resolve(l.value).then(o,s);a((t=t.apply(n,e)).next())});var mc=lu((vi,ts)=>{(function(e,t){typeof vi=="object"&&typeof ts=="object"?ts.exports=t():typeof define=="function"&&define.amd?define([],t):typeof vi=="object"?vi.ClipboardJS=t():e.ClipboardJS=t()})(vi,function(){return(function(){var n={686:(function(i,r,o){"use strict";o.d(r,{default:function(){return Re}});var s=o(279),a=o.n(s),l=o(370),c=o.n(l),u=o(817),d=o.n(u);function p(W){try{return document.execCommand(W)}catch(M){return!1}}var y=function(M){var D=d()(M);return p("cut"),D},m=y;function v(W){var M=document.documentElement.getAttribute("dir")==="rtl",D=document.createElement("textarea");D.style.fontSize="12pt",D.style.border="0",D.style.padding="0",D.style.margin="0",D.style.position="absolute",D.style[M?"right":"left"]="-9999px";var B=window.pageYOffset||document.documentElement.scrollTop;return D.style.top="".concat(B,"px"),D.setAttribute("readonly",""),D.value=W,D}var w=function(M,D){var B=v(M);D.container.appendChild(B);var V=d()(B);return p("copy"),B.remove(),V},T=function(M){var D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},B="";return typeof M=="string"?B=w(M,D):M instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(M==null?void 0:M.type)?B=w(M.value,D):(B=d()(M),p("copy")),B},_=T;function S(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?S=function(D){return typeof D}:S=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},S(W)}var A=function(){var M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=M.action,B=D===void 0?"copy":D,V=M.container,q=M.target,U=M.text;if(B!=="copy"&&B!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&S(q)==="object"&&q.nodeType===1){if(B==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(B==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(U)return _(U,{container:V});if(q)return B==="cut"?m(q):_(q,{container:V})},K=A;function z(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?z=function(D){return typeof D}:z=function(D){return D&&typeof Symbol=="function"&&D.constructor===Symbol&&D!==Symbol.prototype?"symbol":typeof D},z(W)}function L(W,M){if(!(W instanceof M))throw new TypeError("Cannot call a class as a function")}function H(W,M){for(var D=0;D0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=z(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var q=this;this.listener=c()(V,"click",function(U){return q.onClick(U)})}},{key:"onClick",value:function(V){var q=V.delegateTarget||V.currentTarget,U=this.action(q)||"copy",Z=K({action:U,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Z?"success":"error",{action:U,text:Z,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return ne("action",V)}},{key:"defaultTarget",value:function(V){var q=ne("target",V);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(V){return ne("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return _(V,q)}},{key:"cut",value:function(V){return m(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof V=="string"?[V]:V,U=!!document.queryCommandSupported;return q.forEach(function(Z){U=U&&!!document.queryCommandSupported(Z)}),U}}]),D})(a()),Re=qe}),828:(function(i){var r=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var o=Element.prototype;o.matches=o.matchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector||o.webkitMatchesSelector}function s(a,l){for(;a&&a.nodeType!==r;){if(typeof a.matches=="function"&&a.matches(l))return a;a=a.parentNode}}i.exports=s}),438:(function(i,r,o){var s=o(828);function a(u,d,p,y,m){var v=c.apply(this,arguments);return u.addEventListener(p,v,m),{destroy:function(){u.removeEventListener(p,v,m)}}}function l(u,d,p,y,m){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof p=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,d,p,y,m)}))}function c(u,d,p,y){return function(m){m.delegateTarget=s(m.target,d),m.delegateTarget&&y.call(u,m)}}i.exports=l}),879:(function(i,r){r.node=function(o){return o!==void 0&&o instanceof HTMLElement&&o.nodeType===1},r.nodeList=function(o){var s=Object.prototype.toString.call(o);return o!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in o&&(o.length===0||r.node(o[0]))},r.string=function(o){return typeof o=="string"||o instanceof String},r.fn=function(o){var s=Object.prototype.toString.call(o);return s==="[object Function]"}}),370:(function(i,r,o){var s=o(879),a=o(438);function l(p,y,m){if(!p&&!y&&!m)throw new Error("Missing required arguments");if(!s.string(y))throw new TypeError("Second argument must be a String");if(!s.fn(m))throw new TypeError("Third argument must be a Function");if(s.node(p))return c(p,y,m);if(s.nodeList(p))return u(p,y,m);if(s.string(p))return d(p,y,m);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(p,y,m){return p.addEventListener(y,m),{destroy:function(){p.removeEventListener(y,m)}}}function u(p,y,m){return Array.prototype.forEach.call(p,function(v){v.addEventListener(y,m)}),{destroy:function(){Array.prototype.forEach.call(p,function(v){v.removeEventListener(y,m)})}}}function d(p,y,m){return a(document.body,p,y,m)}i.exports=l}),817:(function(i){function r(o){var s;if(o.nodeName==="SELECT")o.focus(),s=o.value;else if(o.nodeName==="INPUT"||o.nodeName==="TEXTAREA"){var a=o.hasAttribute("readonly");a||o.setAttribute("readonly",""),o.select(),o.setSelectionRange(0,o.value.length),a||o.removeAttribute("readonly"),s=o.value}else{o.hasAttribute("contenteditable")&&o.focus();var l=window.getSelection(),c=document.createRange();c.selectNodeContents(o),l.removeAllRanges(),l.addRange(c),s=l.toString()}return s}i.exports=r}),279:(function(i){function r(){}r.prototype={on:function(o,s,a){var l=this.e||(this.e={});return(l[o]||(l[o]=[])).push({fn:s,ctx:a}),this},once:function(o,s,a){var l=this;function c(){l.off(o,c),s.apply(a,arguments)}return c._=s,this.on(o,c,a)},emit:function(o){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[o]||[]).slice(),l=0,c=a.length;for(l;lws,afterRead:()=>Es,afterWrite:()=>Cs,applyStyles:()=>hn,arrow:()=>Ni,auto:()=>jn,basePlacements:()=>lt,beforeMain:()=>bs,beforeRead:()=>vs,beforeWrite:()=>xs,bottom:()=>me,clippingParents:()=>Ur,computeStyles:()=>mn,createPopper:()=>Jn,createPopperBase:()=>Is,createPopperLite:()=>Rs,detectOverflow:()=>ke,end:()=>bt,eventListeners:()=>gn,flip:()=>Pi,hide:()=>Fi,left:()=>he,main:()=>_s,modifierPhases:()=>Gr,offset:()=>$i,placements:()=>qn,popper:()=>$t,popperGenerator:()=>Yt,popperOffsets:()=>En,preventOverflow:()=>Bi,read:()=>ys,reference:()=>Yr,right:()=>pe,start:()=>rt,top:()=>de,variationPlacements:()=>ki,viewport:()=>Wn,write:()=>Ts});var de="top",me="bottom",pe="right",he="left",jn="auto",lt=[de,me,pe,he],rt="start",bt="end",Ur="clippingParents",Wn="viewport",$t="popper",Yr="reference",ki=lt.reduce(function(n,e){return n.concat([e+"-"+rt,e+"-"+bt])},[]),qn=[].concat(lt,[jn]).reduce(function(n,e){return n.concat([e,e+"-"+rt,e+"-"+bt])},[]),vs="beforeRead",ys="read",Es="afterRead",bs="beforeMain",_s="main",ws="afterMain",xs="beforeWrite",Ts="write",Cs="afterWrite",Gr=[vs,ys,Es,bs,_s,ws,xs,Ts,Cs];function we(n){return n?(n.nodeName||"").toLowerCase():null}function ce(n){if(n==null)return window;if(n.toString()!=="[object Window]"){var e=n.ownerDocument;return e&&e.defaultView||window}return n}function Ue(n){var e=ce(n).Element;return n instanceof e||n instanceof Element}function be(n){var e=ce(n).HTMLElement;return n instanceof e||n instanceof HTMLElement}function fn(n){if(typeof ShadowRoot=="undefined")return!1;var e=ce(n).ShadowRoot;return n instanceof e||n instanceof ShadowRoot}function du(n){var e=n.state;Object.keys(e.elements).forEach(function(t){var i=e.styles[t]||{},r=e.attributes[t]||{},o=e.elements[t];!be(o)||!we(o)||(Object.assign(o.style,i),Object.keys(r).forEach(function(s){var a=r[s];a===!1?o.removeAttribute(s):o.setAttribute(s,a===!0?"":a)}))})}function fu(n){var e=n.state,t={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,t.popper),e.styles=t,e.elements.arrow&&Object.assign(e.elements.arrow.style,t.arrow),function(){Object.keys(e.elements).forEach(function(i){var r=e.elements[i],o=e.attributes[i]||{},s=Object.keys(e.styles.hasOwnProperty(i)?e.styles[i]:t[i]),a=s.reduce(function(l,c){return l[c]="",l},{});!be(r)||!we(r)||(Object.assign(r.style,a),Object.keys(o).forEach(function(l){r.removeAttribute(l)}))})}}var hn={name:"applyStyles",enabled:!0,phase:"write",fn:du,effect:fu,requires:["computeStyles"]};function xe(n){return n.split("-")[0]}var Ze=Math.max,Bt=Math.min,ct=Math.round;function pn(){var n=navigator.userAgentData;return n!=null&&n.brands&&Array.isArray(n.brands)?n.brands.map(function(e){return e.brand+"/"+e.version}).join(" "):navigator.userAgent}function Un(){return!/^((?!chrome|android).)*safari/i.test(pn())}function Ye(n,e,t){e===void 0&&(e=!1),t===void 0&&(t=!1);var i=n.getBoundingClientRect(),r=1,o=1;e&&be(n)&&(r=n.offsetWidth>0&&ct(i.width)/n.offsetWidth||1,o=n.offsetHeight>0&&ct(i.height)/n.offsetHeight||1);var s=Ue(n)?ce(n):window,a=s.visualViewport,l=!Un()&&t,c=(i.left+(l&&a?a.offsetLeft:0))/r,u=(i.top+(l&&a?a.offsetTop:0))/o,d=i.width/r,p=i.height/o;return{width:d,height:p,top:u,right:c+d,bottom:u+p,left:c,x:c,y:u}}function Vt(n){var e=Ye(n),t=n.offsetWidth,i=n.offsetHeight;return Math.abs(e.width-t)<=1&&(t=e.width),Math.abs(e.height-i)<=1&&(i=e.height),{x:n.offsetLeft,y:n.offsetTop,width:t,height:i}}function Yn(n,e){var t=e.getRootNode&&e.getRootNode();if(n.contains(e))return!0;if(t&&fn(t)){var i=e;do{if(i&&n.isSameNode(i))return!0;i=i.parentNode||i.host}while(i)}return!1}function Me(n){return ce(n).getComputedStyle(n)}function Kr(n){return["table","td","th"].indexOf(we(n))>=0}function Ae(n){return((Ue(n)?n.ownerDocument:n.document)||window.document).documentElement}function ut(n){return we(n)==="html"?n:n.assignedSlot||n.parentNode||(fn(n)?n.host:null)||Ae(n)}function As(n){return!be(n)||Me(n).position==="fixed"?null:n.offsetParent}function hu(n){var e=/firefox/i.test(pn()),t=/Trident/i.test(pn());if(t&&be(n)){var i=Me(n);if(i.position==="fixed")return null}var r=ut(n);for(fn(r)&&(r=r.host);be(r)&&["html","body"].indexOf(we(r))<0;){var o=Me(r);if(o.transform!=="none"||o.perspective!=="none"||o.contain==="paint"||["transform","perspective"].indexOf(o.willChange)!==-1||e&&o.willChange==="filter"||e&&o.filter&&o.filter!=="none")return r;r=r.parentNode}return null}function et(n){for(var e=ce(n),t=As(n);t&&Kr(t)&&Me(t).position==="static";)t=As(t);return t&&(we(t)==="html"||we(t)==="body"&&Me(t).position==="static")?e:t||hu(n)||e}function zt(n){return["top","bottom"].indexOf(n)>=0?"x":"y"}function jt(n,e,t){return Ze(n,Bt(e,t))}function Ss(n,e,t){var i=jt(n,e,t);return i>t?t:i}function Gn(){return{top:0,right:0,bottom:0,left:0}}function Kn(n){return Object.assign({},Gn(),n)}function Xn(n,e){return e.reduce(function(t,i){return t[i]=n,t},{})}var pu=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,Kn(typeof e!="number"?e:Xn(e,lt))};function mu(n){var e,t=n.state,i=n.name,r=n.options,o=t.elements.arrow,s=t.modifiersData.popperOffsets,a=xe(t.placement),l=zt(a),c=[he,pe].indexOf(a)>=0,u=c?"height":"width";if(!(!o||!s)){var d=pu(r.padding,t),p=Vt(o),y=l==="y"?de:he,m=l==="y"?me:pe,v=t.rects.reference[u]+t.rects.reference[l]-s[l]-t.rects.popper[u],w=s[l]-t.rects.reference[l],T=et(o),_=T?l==="y"?T.clientHeight||0:T.clientWidth||0:0,S=v/2-w/2,A=d[y],K=_-p[u]-d[m],z=_/2-p[u]/2+S,L=jt(A,z,K),H=l;t.modifiersData[i]=(e={},e[H]=L,e.centerOffset=L-z,e)}}function gu(n){var e=n.state,t=n.options,i=t.element,r=i===void 0?"[data-popper-arrow]":i;r!=null&&(typeof r=="string"&&(r=e.elements.popper.querySelector(r),!r)||Yn(e.elements.popper,r)&&(e.elements.arrow=r))}var Ni={name:"arrow",enabled:!0,phase:"main",fn:mu,effect:gu,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Ge(n){return n.split("-")[1]}var vu={top:"auto",right:"auto",bottom:"auto",left:"auto"};function yu(n,e){var t=n.x,i=n.y,r=e.devicePixelRatio||1;return{x:ct(t*r)/r||0,y:ct(i*r)/r||0}}function Ds(n){var e,t=n.popper,i=n.popperRect,r=n.placement,o=n.variation,s=n.offsets,a=n.position,l=n.gpuAcceleration,c=n.adaptive,u=n.roundOffsets,d=n.isFixed,p=s.x,y=p===void 0?0:p,m=s.y,v=m===void 0?0:m,w=typeof u=="function"?u({x:y,y:v}):{x:y,y:v};y=w.x,v=w.y;var T=s.hasOwnProperty("x"),_=s.hasOwnProperty("y"),S=he,A=de,K=window;if(c){var z=et(t),L="clientHeight",H="clientWidth";if(z===ce(t)&&(z=Ae(t),Me(z).position!=="static"&&a==="absolute"&&(L="scrollHeight",H="scrollWidth")),z=z,r===de||(r===he||r===pe)&&o===bt){A=me;var N=d&&z===K&&K.visualViewport?K.visualViewport.height:z[L];v-=N-i.height,v*=l?1:-1}if(r===he||(r===de||r===me)&&o===bt){S=pe;var Y=d&&z===K&&K.visualViewport?K.visualViewport.width:z[H];y-=Y-i.width,y*=l?1:-1}}var $=Object.assign({position:a},c&&vu),ie=u===!0?yu({x:y,y:v},ce(t)):{x:y,y:v};if(y=ie.x,v=ie.y,l){var J;return Object.assign({},$,(J={},J[A]=_?"0":"",J[S]=T?"0":"",J.transform=(K.devicePixelRatio||1)<=1?"translate("+y+"px, "+v+"px)":"translate3d("+y+"px, "+v+"px, 0)",J))}return Object.assign({},$,(e={},e[A]=_?v+"px":"",e[S]=T?y+"px":"",e.transform="",e))}function Eu(n){var e=n.state,t=n.options,i=t.gpuAcceleration,r=i===void 0?!0:i,o=t.adaptive,s=o===void 0?!0:o,a=t.roundOffsets,l=a===void 0?!0:a,c={placement:xe(e.placement),variation:Ge(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:r,isFixed:e.options.strategy==="fixed"};e.modifiersData.popperOffsets!=null&&(e.styles.popper=Object.assign({},e.styles.popper,Ds(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:s,roundOffsets:l})))),e.modifiersData.arrow!=null&&(e.styles.arrow=Object.assign({},e.styles.arrow,Ds(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})}var mn={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:Eu,data:{}};var Ii={passive:!0};function bu(n){var e=n.state,t=n.instance,i=n.options,r=i.scroll,o=r===void 0?!0:r,s=i.resize,a=s===void 0?!0:s,l=ce(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach(function(u){u.addEventListener("scroll",t.update,Ii)}),a&&l.addEventListener("resize",t.update,Ii),function(){o&&c.forEach(function(u){u.removeEventListener("scroll",t.update,Ii)}),a&&l.removeEventListener("resize",t.update,Ii)}}var gn={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:bu,data:{}};var _u={left:"right",right:"left",bottom:"top",top:"bottom"};function vn(n){return n.replace(/left|right|bottom|top/g,function(e){return _u[e]})}var wu={start:"end",end:"start"};function Ri(n){return n.replace(/start|end/g,function(e){return wu[e]})}function Wt(n){var e=ce(n),t=e.pageXOffset,i=e.pageYOffset;return{scrollLeft:t,scrollTop:i}}function qt(n){return Ye(Ae(n)).left+Wt(n).scrollLeft}function Xr(n,e){var t=ce(n),i=Ae(n),r=t.visualViewport,o=i.clientWidth,s=i.clientHeight,a=0,l=0;if(r){o=r.width,s=r.height;var c=Un();(c||!c&&e==="fixed")&&(a=r.offsetLeft,l=r.offsetTop)}return{width:o,height:s,x:a+qt(n),y:l}}function Qr(n){var e,t=Ae(n),i=Wt(n),r=(e=n.ownerDocument)==null?void 0:e.body,o=Ze(t.scrollWidth,t.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),s=Ze(t.scrollHeight,t.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),a=-i.scrollLeft+qt(n),l=-i.scrollTop;return Me(r||t).direction==="rtl"&&(a+=Ze(t.clientWidth,r?r.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}function Ut(n){var e=Me(n),t=e.overflow,i=e.overflowX,r=e.overflowY;return/auto|scroll|overlay|hidden/.test(t+r+i)}function Hi(n){return["html","body","#document"].indexOf(we(n))>=0?n.ownerDocument.body:be(n)&&Ut(n)?n:Hi(ut(n))}function _t(n,e){var t;e===void 0&&(e=[]);var i=Hi(n),r=i===((t=n.ownerDocument)==null?void 0:t.body),o=ce(i),s=r?[o].concat(o.visualViewport||[],Ut(i)?i:[]):i,a=e.concat(s);return r?a:a.concat(_t(ut(s)))}function yn(n){return Object.assign({},n,{left:n.x,top:n.y,right:n.x+n.width,bottom:n.y+n.height})}function xu(n,e){var t=Ye(n,!1,e==="fixed");return t.top=t.top+n.clientTop,t.left=t.left+n.clientLeft,t.bottom=t.top+n.clientHeight,t.right=t.left+n.clientWidth,t.width=n.clientWidth,t.height=n.clientHeight,t.x=t.left,t.y=t.top,t}function Os(n,e,t){return e===Wn?yn(Xr(n,t)):Ue(e)?xu(e,t):yn(Qr(Ae(n)))}function Tu(n){var e=_t(ut(n)),t=["absolute","fixed"].indexOf(Me(n).position)>=0,i=t&&be(n)?et(n):n;return Ue(i)?e.filter(function(r){return Ue(r)&&Yn(r,i)&&we(r)!=="body"}):[]}function Jr(n,e,t,i){var r=e==="clippingParents"?Tu(n):[].concat(e),o=[].concat(r,[t]),s=o[0],a=o.reduce(function(l,c){var u=Os(n,c,i);return l.top=Ze(u.top,l.top),l.right=Bt(u.right,l.right),l.bottom=Bt(u.bottom,l.bottom),l.left=Ze(u.left,l.left),l},Os(n,s,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function Qn(n){var e=n.reference,t=n.element,i=n.placement,r=i?xe(i):null,o=i?Ge(i):null,s=e.x+e.width/2-t.width/2,a=e.y+e.height/2-t.height/2,l;switch(r){case de:l={x:s,y:e.y-t.height};break;case me:l={x:s,y:e.y+e.height};break;case pe:l={x:e.x+e.width,y:a};break;case he:l={x:e.x-t.width,y:a};break;default:l={x:e.x,y:e.y}}var c=r?zt(r):null;if(c!=null){var u=c==="y"?"height":"width";switch(o){case rt:l[c]=l[c]-(e[u]/2-t[u]/2);break;case bt:l[c]=l[c]+(e[u]/2-t[u]/2);break;default:}}return l}function ke(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=i===void 0?n.placement:i,o=t.strategy,s=o===void 0?n.strategy:o,a=t.boundary,l=a===void 0?Ur:a,c=t.rootBoundary,u=c===void 0?Wn:c,d=t.elementContext,p=d===void 0?$t:d,y=t.altBoundary,m=y===void 0?!1:y,v=t.padding,w=v===void 0?0:v,T=Kn(typeof w!="number"?w:Xn(w,lt)),_=p===$t?Yr:$t,S=n.rects.popper,A=n.elements[m?_:p],K=Jr(Ue(A)?A:A.contextElement||Ae(n.elements.popper),l,u,s),z=Ye(n.elements.reference),L=Qn({reference:z,element:S,strategy:"absolute",placement:r}),H=yn(Object.assign({},S,L)),N=p===$t?H:z,Y={top:K.top-N.top+T.top,bottom:N.bottom-K.bottom+T.bottom,left:K.left-N.left+T.left,right:N.right-K.right+T.right},$=n.modifiersData.offset;if(p===$t&&$){var ie=$[r];Object.keys(Y).forEach(function(J){var Te=[pe,me].indexOf(J)>=0?1:-1,Ce=[de,me].indexOf(J)>=0?"y":"x";Y[J]+=ie[Ce]*Te})}return Y}function Zr(n,e){e===void 0&&(e={});var t=e,i=t.placement,r=t.boundary,o=t.rootBoundary,s=t.padding,a=t.flipVariations,l=t.allowedAutoPlacements,c=l===void 0?qn:l,u=Ge(i),d=u?a?ki:ki.filter(function(m){return Ge(m)===u}):lt,p=d.filter(function(m){return c.indexOf(m)>=0});p.length===0&&(p=d);var y=p.reduce(function(m,v){return m[v]=ke(n,{placement:v,boundary:r,rootBoundary:o,padding:s})[xe(v)],m},{});return Object.keys(y).sort(function(m,v){return y[m]-y[v]})}function Cu(n){if(xe(n)===jn)return[];var e=vn(n);return[Ri(n),e,Ri(e)]}function Au(n){var e=n.state,t=n.options,i=n.name;if(!e.modifiersData[i]._skip){for(var r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!0:s,l=t.fallbackPlacements,c=t.padding,u=t.boundary,d=t.rootBoundary,p=t.altBoundary,y=t.flipVariations,m=y===void 0?!0:y,v=t.allowedAutoPlacements,w=e.options.placement,T=xe(w),_=T===w,S=l||(_||!m?[vn(w)]:Cu(w)),A=[w].concat(S).reduce(function(V,q){return V.concat(xe(q)===jn?Zr(e,{placement:q,boundary:u,rootBoundary:d,padding:c,flipVariations:m,allowedAutoPlacements:v}):q)},[]),K=e.rects.reference,z=e.rects.popper,L=new Map,H=!0,N=A[0],Y=0;Y=0,Ce=Te?"width":"height",se=ke(e,{placement:$,boundary:u,rootBoundary:d,altBoundary:p,padding:c}),ne=Te?J?pe:he:J?me:de;K[Ce]>z[Ce]&&(ne=vn(ne));var qe=vn(ne),Re=[];if(o&&Re.push(se[ie]<=0),a&&Re.push(se[ne]<=0,se[qe]<=0),Re.every(function(V){return V})){N=$,H=!1;break}L.set($,Re)}if(H)for(var W=m?3:1,M=function(q){var U=A.find(function(Z){var oe=L.get(Z);if(oe)return oe.slice(0,q).every(function(Et){return Et})});if(U)return N=U,"break"},D=W;D>0;D--){var B=M(D);if(B==="break")break}e.placement!==N&&(e.modifiersData[i]._skip=!0,e.placement=N,e.reset=!0)}}var Pi={name:"flip",enabled:!0,phase:"main",fn:Au,requiresIfExists:["offset"],data:{_skip:!1}};function Ls(n,e,t){return t===void 0&&(t={x:0,y:0}),{top:n.top-e.height-t.y,right:n.right-e.width+t.x,bottom:n.bottom-e.height+t.y,left:n.left-e.width-t.x}}function Ms(n){return[de,pe,me,he].some(function(e){return n[e]>=0})}function Su(n){var e=n.state,t=n.name,i=e.rects.reference,r=e.rects.popper,o=e.modifiersData.preventOverflow,s=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=Ls(s,i),c=Ls(a,r,o),u=Ms(l),d=Ms(c);e.modifiersData[t]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:u,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":d})}var Fi={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Su};function Du(n,e,t){var i=xe(n),r=[he,de].indexOf(i)>=0?-1:1,o=typeof t=="function"?t(Object.assign({},e,{placement:n})):t,s=o[0],a=o[1];return s=s||0,a=(a||0)*r,[he,pe].indexOf(i)>=0?{x:a,y:s}:{x:s,y:a}}function Ou(n){var e=n.state,t=n.options,i=n.name,r=t.offset,o=r===void 0?[0,0]:r,s=qn.reduce(function(u,d){return u[d]=Du(d,e.rects,o),u},{}),a=s[e.placement],l=a.x,c=a.y;e.modifiersData.popperOffsets!=null&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[i]=s}var $i={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Ou};function Lu(n){var e=n.state,t=n.name;e.modifiersData[t]=Qn({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})}var En={name:"popperOffsets",enabled:!0,phase:"read",fn:Lu,data:{}};function eo(n){return n==="x"?"y":"x"}function Mu(n){var e=n.state,t=n.options,i=n.name,r=t.mainAxis,o=r===void 0?!0:r,s=t.altAxis,a=s===void 0?!1:s,l=t.boundary,c=t.rootBoundary,u=t.altBoundary,d=t.padding,p=t.tether,y=p===void 0?!0:p,m=t.tetherOffset,v=m===void 0?0:m,w=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:u}),T=xe(e.placement),_=Ge(e.placement),S=!_,A=zt(T),K=eo(A),z=e.modifiersData.popperOffsets,L=e.rects.reference,H=e.rects.popper,N=typeof v=="function"?v(Object.assign({},e.rects,{placement:e.placement})):v,Y=typeof N=="number"?{mainAxis:N,altAxis:N}:Object.assign({mainAxis:0,altAxis:0},N),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,ie={x:0,y:0};if(z){if(o){var J,Te=A==="y"?de:he,Ce=A==="y"?me:pe,se=A==="y"?"height":"width",ne=z[A],qe=ne+w[Te],Re=ne-w[Ce],W=y?-H[se]/2:0,M=_===rt?L[se]:H[se],D=_===rt?-H[se]:-L[se],B=e.elements.arrow,V=y&&B?Vt(B):{width:0,height:0},q=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:Gn(),U=q[Te],Z=q[Ce],oe=jt(0,L[se],V[se]),Et=S?L[se]/2-W-oe-U-Y.mainAxis:M-oe-U-Y.mainAxis,$r=S?-L[se]/2+W+oe+Z+Y.mainAxis:D+oe+Z+Y.mainAxis,on=e.elements.arrow&&et(e.elements.arrow),sn=on?A==="y"?on.clientTop||0:on.clientLeft||0:0,Ti=(J=$==null?void 0:$[A])!=null?J:0,Br=ne+Et-Ti-sn,Ci=ne+$r-Ti,Ai=jt(y?Bt(qe,Br):qe,ne,y?Ze(Re,Ci):Re);z[A]=Ai,ie[A]=Ai-ne}if(a){var $n,Si=A==="x"?de:he,an=A==="x"?me:pe,ot=z[K],ln=K==="y"?"height":"width",Bn=ot+w[Si],cn=ot-w[an],un=[de,he].indexOf(T)!==-1,Ft=($n=$==null?void 0:$[K])!=null?$n:0,Di=un?Bn:ot-L[ln]-H[ln]-Ft+Y.altAxis,Vn=un?ot+L[ln]+H[ln]-Ft-Y.altAxis:cn,Oi=y&&un?Ss(Di,ot,Vn):jt(y?Di:Bn,ot,y?Vn:cn);z[K]=Oi,ie[K]=Oi-ot}e.modifiersData[i]=ie}}var Bi={name:"preventOverflow",enabled:!0,phase:"main",fn:Mu,requiresIfExists:["offset"]};function to(n){return{scrollLeft:n.scrollLeft,scrollTop:n.scrollTop}}function no(n){return n===ce(n)||!be(n)?Wt(n):to(n)}function ku(n){var e=n.getBoundingClientRect(),t=ct(e.width)/n.offsetWidth||1,i=ct(e.height)/n.offsetHeight||1;return t!==1||i!==1}function io(n,e,t){t===void 0&&(t=!1);var i=be(e),r=be(e)&&ku(e),o=Ae(e),s=Ye(n,r,t),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(i||!i&&!t)&&((we(e)!=="body"||Ut(o))&&(a=no(e)),be(e)?(l=Ye(e,!0),l.x+=e.clientLeft,l.y+=e.clientTop):o&&(l.x=qt(o))),{x:s.left+a.scrollLeft-l.x,y:s.top+a.scrollTop-l.y,width:s.width,height:s.height}}function Nu(n){var e=new Map,t=new Set,i=[];n.forEach(function(o){e.set(o.name,o)});function r(o){t.add(o.name);var s=[].concat(o.requires||[],o.requiresIfExists||[]);s.forEach(function(a){if(!t.has(a)){var l=e.get(a);l&&r(l)}}),i.push(o)}return n.forEach(function(o){t.has(o.name)||r(o)}),i}function ro(n){var e=Nu(n);return Gr.reduce(function(t,i){return t.concat(e.filter(function(r){return r.phase===i}))},[])}function oo(n){var e;return function(){return e||(e=new Promise(function(t){Promise.resolve().then(function(){e=void 0,t(n())})})),e}}function so(n){var e=n.reduce(function(t,i){var r=t[i.name];return t[i.name]=r?Object.assign({},r,i,{options:Object.assign({},r.options,i.options),data:Object.assign({},r.data,i.data)}):i,t},{});return Object.keys(e).map(function(t){return e[t]})}var ks={placement:"bottom",modifiers:[],strategy:"absolute"};function Ns(){for(var n=arguments.length,e=new Array(n),t=0;t(n&&window.CSS&&window.CSS.escape&&(n=n.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),n),Fu=n=>n==null?`${n}`:Object.prototype.toString.call(n).match(/\s([a-z]+)/i)[1].toLowerCase(),$u=n=>{do n+=Math.floor(Math.random()*Hu);while(document.getElementById(n));return n},Bu=n=>{if(!n)return 0;let{transitionDuration:e,transitionDelay:t}=window.getComputedStyle(n),i=Number.parseFloat(e),r=Number.parseFloat(t);return!i&&!r?0:(e=e.split(",")[0],t=t.split(",")[0],(Number.parseFloat(e)+Number.parseFloat(t))*Pu)},da=n=>{n.dispatchEvent(new Event(To))},dt=n=>!n||typeof n!="object"?!1:(typeof n.jquery!="undefined"&&(n=n[0]),typeof n.nodeType!="undefined"),xt=n=>dt(n)?n.jquery?n[0]:n:typeof n=="string"&&n.length>0?document.querySelector(ua(n)):null,An=n=>{if(!dt(n)||n.getClientRects().length===0)return!1;let e=getComputedStyle(n).getPropertyValue("visibility")==="visible",t=n.closest("details:not([open])");if(!t)return e;if(t!==n){let i=n.closest("summary");if(i&&i.parentNode!==t||i===null)return!1}return e},Tt=n=>!n||n.nodeType!==Node.ELEMENT_NODE||n.classList.contains("disabled")?!0:typeof n.disabled!="undefined"?n.disabled:n.hasAttribute("disabled")&&n.getAttribute("disabled")!=="false",fa=n=>{if(!document.documentElement.attachShadow)return null;if(typeof n.getRootNode=="function"){let e=n.getRootNode();return e instanceof ShadowRoot?e:null}return n instanceof ShadowRoot?n:n.parentNode?fa(n.parentNode):null},Xi=()=>{},ii=n=>{n.offsetHeight},ha=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,lo=[],Vu=n=>{document.readyState==="loading"?(lo.length||document.addEventListener("DOMContentLoaded",()=>{for(let e of lo)e()}),lo.push(n)):n()},Ke=()=>document.documentElement.dir==="rtl",Qe=n=>{Vu(()=>{let e=ha();if(e){let t=n.NAME,i=e.fn[t];e.fn[t]=n.jQueryInterface,e.fn[t].Constructor=n,e.fn[t].noConflict=()=>(e.fn[t]=i,n.jQueryInterface)}})},He=(n,e=[],t=n)=>typeof n=="function"?n.call(...e):t,pa=(n,e,t=!0)=>{if(!t){He(n);return}let r=Bu(e)+5,o=!1,s=({target:a})=>{a===e&&(o=!0,e.removeEventListener(To,s),He(n))};e.addEventListener(To,s),setTimeout(()=>{o||da(e)},r)},Do=(n,e,t,i)=>{let r=n.length,o=n.indexOf(e);return o===-1?!t&&i?n[r-1]:n[0]:(o+=t?1:-1,i&&(o=(o+r)%r),n[Math.max(0,Math.min(o,r-1))])},zu=/[^.]*(?=\..*)\.|.*/,ju=/\..*/,Wu=/::\d+$/,co={},Hs=1,ma={mouseenter:"mouseover",mouseleave:"mouseout"},qu=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function ga(n,e){return e&&`${e}::${Hs++}`||n.uidEvent||Hs++}function va(n){let e=ga(n);return n.uidEvent=e,co[e]=co[e]||{},co[e]}function Uu(n,e){return function t(i){return Oo(i,{delegateTarget:n}),t.oneOff&&x.off(n,i.type,e),e.apply(n,[i])}}function Yu(n,e,t){return function i(r){let o=n.querySelectorAll(e);for(let{target:s}=r;s&&s!==this;s=s.parentNode)for(let a of o)if(a===s)return Oo(r,{delegateTarget:s}),i.oneOff&&x.off(n,r.type,e,t),t.apply(s,[r])}}function ya(n,e,t=null){return Object.values(n).find(i=>i.callable===e&&i.delegationSelector===t)}function Ea(n,e,t){let i=typeof e=="string",r=i?t:e||t,o=ba(n);return qu.has(o)||(o=n),[i,r,o]}function Ps(n,e,t,i,r){if(typeof e!="string"||!n)return;let[o,s,a]=Ea(e,t,i);e in ma&&(s=(m=>function(v){if(!v.relatedTarget||v.relatedTarget!==v.delegateTarget&&!v.delegateTarget.contains(v.relatedTarget))return m.call(this,v)})(s));let l=va(n),c=l[a]||(l[a]={}),u=ya(c,s,o?t:null);if(u){u.oneOff=u.oneOff&&r;return}let d=ga(s,e.replace(zu,"")),p=o?Yu(n,t,s):Uu(n,s);p.delegationSelector=o?t:null,p.callable=s,p.oneOff=r,p.uidEvent=d,c[d]=p,n.addEventListener(a,p,o)}function Co(n,e,t,i,r){let o=ya(e[t],i,r);o&&(n.removeEventListener(t,o,!!r),delete e[t][o.uidEvent])}function Gu(n,e,t,i){let r=e[t]||{};for(let[o,s]of Object.entries(r))o.includes(i)&&Co(n,e,t,s.callable,s.delegationSelector)}function ba(n){return n=n.replace(ju,""),ma[n]||n}var x={on(n,e,t,i){Ps(n,e,t,i,!1)},one(n,e,t,i){Ps(n,e,t,i,!0)},off(n,e,t,i){if(typeof e!="string"||!n)return;let[r,o,s]=Ea(e,t,i),a=s!==e,l=va(n),c=l[s]||{},u=e.startsWith(".");if(typeof o!="undefined"){if(!Object.keys(c).length)return;Co(n,l,s,o,r?t:null);return}if(u)for(let d of Object.keys(l))Gu(n,l,d,e.slice(1));for(let[d,p]of Object.entries(c)){let y=d.replace(Wu,"");(!a||e.includes(y))&&Co(n,l,s,p.callable,p.delegationSelector)}},trigger(n,e,t){if(typeof e!="string"||!n)return null;let i=ha(),r=ba(e),o=e!==r,s=null,a=!0,l=!0,c=!1;o&&i&&(s=i.Event(e,t),i(n).trigger(s),a=!s.isPropagationStopped(),l=!s.isImmediatePropagationStopped(),c=s.isDefaultPrevented());let u=Oo(new Event(e,{bubbles:a,cancelable:!0}),t);return c&&u.preventDefault(),l&&n.dispatchEvent(u),u.defaultPrevented&&s&&s.preventDefault(),u}};function Oo(n,e={}){for(let[t,i]of Object.entries(e))try{n[t]=i}catch(r){Object.defineProperty(n,t,{configurable:!0,get(){return i}})}return n}function Fs(n){if(n==="true")return!0;if(n==="false")return!1;if(n===Number(n).toString())return Number(n);if(n===""||n==="null")return null;if(typeof n!="string")return n;try{return JSON.parse(decodeURIComponent(n))}catch(e){return n}}function uo(n){return n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var ft={setDataAttribute(n,e,t){n.setAttribute(`data-bs-${uo(e)}`,t)},removeDataAttribute(n,e){n.removeAttribute(`data-bs-${uo(e)}`)},getDataAttributes(n){if(!n)return{};let e={},t=Object.keys(n.dataset).filter(i=>i.startsWith("bs")&&!i.startsWith("bsConfig"));for(let i of t){let r=i.replace(/^bs/,"");r=r.charAt(0).toLowerCase()+r.slice(1),e[r]=Fs(n.dataset[i])}return e},getDataAttribute(n,e){return Fs(n.getAttribute(`data-bs-${uo(e)}`))}},Xt=class{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){let i=dt(t)?ft.getDataAttribute(t,"config"):{};return O(O(O(O({},this.constructor.Default),typeof i=="object"?i:{}),dt(t)?ft.getDataAttributes(t):{}),typeof e=="object"?e:{})}_typeCheckConfig(e,t=this.constructor.DefaultType){for(let[i,r]of Object.entries(t)){let o=e[i],s=dt(o)?"element":Fu(o);if(!new RegExp(r).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${s}" but expected type "${r}".`)}}},Ku="5.3.8",je=class extends Xt{constructor(e,t){super(),e=xt(e),e&&(this._element=e,this._config=this._getConfig(t),ao.set(this._element,this.constructor.DATA_KEY,this))}dispose(){ao.remove(this._element,this.constructor.DATA_KEY),x.off(this._element,this.constructor.EVENT_KEY);for(let e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,i=!0){pa(e,t,i)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return ao.get(xt(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t=="object"?t:null)}static get VERSION(){return Ku}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}},fo=n=>{let e=n.getAttribute("data-bs-target");if(!e||e==="#"){let t=n.getAttribute("href");if(!t||!t.includes("#")&&!t.startsWith("."))return null;t.includes("#")&&!t.startsWith("#")&&(t=`#${t.split("#")[1]}`),e=t&&t!=="#"?t.trim():null}return e?e.split(",").map(t=>ua(t)).join(","):null},j={find(n,e=document.documentElement){return[].concat(...Element.prototype.querySelectorAll.call(e,n))},findOne(n,e=document.documentElement){return Element.prototype.querySelector.call(e,n)},children(n,e){return[].concat(...n.children).filter(t=>t.matches(e))},parents(n,e){let t=[],i=n.parentNode.closest(e);for(;i;)t.push(i),i=i.parentNode.closest(e);return t},prev(n,e){let t=n.previousElementSibling;for(;t;){if(t.matches(e))return[t];t=t.previousElementSibling}return[]},next(n,e){let t=n.nextElementSibling;for(;t;){if(t.matches(e))return[t];t=t.nextElementSibling}return[]},focusableChildren(n){let e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,n).filter(t=>!Tt(t)&&An(t))},getSelectorFromElement(n){let e=fo(n);return e&&j.findOne(e)?e:null},getElementFromSelector(n){let e=fo(n);return e?j.findOne(e):null},getMultipleElementsFromSelector(n){let e=fo(n);return e?j.find(e):[]}},rr=(n,e="hide")=>{let t=`click.dismiss${n.EVENT_KEY}`,i=n.NAME;x.on(document,t,`[data-bs-dismiss="${i}"]`,function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Tt(this))return;let o=j.getElementFromSelector(this)||this.closest(`.${i}`);n.getOrCreateInstance(o)[e]()})},Xu="alert",Qu="bs.alert",_a=`.${Qu}`,Ju=`close${_a}`,Zu=`closed${_a}`,ed="fade",td="show",Qi=class n extends je{static get NAME(){return Xu}close(){if(x.trigger(this._element,Ju).defaultPrevented)return;this._element.classList.remove(td);let t=this._element.classList.contains(ed);this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),x.trigger(this._element,Zu),this.dispose()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};rr(Qi,"close");Qe(Qi);var nd="button",id="bs.button",rd=`.${id}`,od=".data-api",sd="active",$s='[data-bs-toggle="button"]',ad=`click${rd}${od}`,Ji=class n extends je{static get NAME(){return nd}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle(sd))}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);e==="toggle"&&t[e]()})}};x.on(document,ad,$s,n=>{n.preventDefault();let e=n.target.closest($s);Ji.getOrCreateInstance(e).toggle()});Qe(Ji);var ld="swipe",Sn=".bs.swipe",cd=`touchstart${Sn}`,ud=`touchmove${Sn}`,dd=`touchend${Sn}`,fd=`pointerdown${Sn}`,hd=`pointerup${Sn}`,pd="touch",md="pen",gd="pointer-event",vd=40,yd={endCallback:null,leftCallback:null,rightCallback:null},Ed={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"},Zi=class n extends Xt{constructor(e,t){super(),this._element=e,!(!e||!n.isSupported())&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=!!window.PointerEvent,this._initEvents())}static get Default(){return yd}static get DefaultType(){return Ed}static get NAME(){return ld}dispose(){x.off(this._element,Sn)}_start(e){if(!this._supportPointerEvents){this._deltaX=e.touches[0].clientX;return}this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX)}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),He(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){let e=Math.abs(this._deltaX);if(e<=vd)return;let t=e/this._deltaX;this._deltaX=0,t&&He(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(x.on(this._element,fd,e=>this._start(e)),x.on(this._element,hd,e=>this._end(e)),this._element.classList.add(gd)):(x.on(this._element,cd,e=>this._start(e)),x.on(this._element,ud,e=>this._move(e)),x.on(this._element,dd,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===md||e.pointerType===pd)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}},bd="carousel",_d="bs.carousel",Dt=`.${_d}`,wa=".data-api",wd="ArrowLeft",xd="ArrowRight",Td=500,Zn="next",bn="prev",wn="left",Gi="right",Cd=`slide${Dt}`,ho=`slid${Dt}`,Ad=`keydown${Dt}`,Sd=`mouseenter${Dt}`,Dd=`mouseleave${Dt}`,Od=`dragstart${Dt}`,Ld=`load${Dt}${wa}`,Md=`click${Dt}${wa}`,xa="carousel",zi="active",kd="slide",Nd="carousel-item-end",Id="carousel-item-start",Rd="carousel-item-next",Hd="carousel-item-prev",Ta=".active",Ca=".carousel-item",Pd=Ta+Ca,Fd=".carousel-item img",$d=".carousel-indicators",Bd="[data-bs-slide], [data-bs-slide-to]",Vd='[data-bs-ride="carousel"]',zd={[wd]:Gi,[xd]:wn},jd={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Wd={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"},ti=class n extends je{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=j.findOne($d,this._element),this._addEventListeners(),this._config.ride===xa&&this.cycle()}static get Default(){return jd}static get DefaultType(){return Wd}static get NAME(){return bd}next(){this._slide(Zn)}nextWhenVisible(){!document.hidden&&An(this._element)&&this.next()}prev(){this._slide(bn)}pause(){this._isSliding&&da(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){if(this._config.ride){if(this._isSliding){x.one(this._element,ho,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){x.one(this._element,ho,()=>this.to(e));return}let i=this._getItemIndex(this._getActive());if(i===e)return;let r=e>i?Zn:bn;this._slide(r,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&&x.on(this._element,Ad,e=>this._keydown(e)),this._config.pause==="hover"&&(x.on(this._element,Sd,()=>this.pause()),x.on(this._element,Dd,()=>this._maybeEnableCycle())),this._config.touch&&Zi.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let i of j.find(Fd,this._element))x.on(i,Od,r=>r.preventDefault());let t={leftCallback:()=>this._slide(this._directionToOrder(wn)),rightCallback:()=>this._slide(this._directionToOrder(Gi)),endCallback:()=>{this._config.pause==="hover"&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),Td+this._config.interval))}};this._swipeHelper=new Zi(this._element,t)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=zd[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;let t=j.findOne(Ta,this._indicatorsElement);t.classList.remove(zi),t.removeAttribute("aria-current");let i=j.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);i&&(i.classList.add(zi),i.setAttribute("aria-current","true"))}_updateInterval(){let e=this._activeElement||this._getActive();if(!e)return;let t=Number.parseInt(e.getAttribute("data-bs-interval"),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;let i=this._getActive(),r=e===Zn,o=t||Do(this._getItems(),i,r,this._config.wrap);if(o===i)return;let s=this._getItemIndex(o),a=y=>x.trigger(this._element,y,{relatedTarget:o,direction:this._orderToDirection(e),from:this._getItemIndex(i),to:s});if(a(Cd).defaultPrevented||!i||!o)return;let c=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(s),this._activeElement=o;let u=r?Id:Nd,d=r?Rd:Hd;o.classList.add(d),ii(o),i.classList.add(u),o.classList.add(u);let p=()=>{o.classList.remove(u,d),o.classList.add(zi),i.classList.remove(zi,d,u),this._isSliding=!1,a(ho)};this._queueCallback(p,i,this._isAnimated()),c&&this.cycle()}_isAnimated(){return this._element.classList.contains(kd)}_getActive(){return j.findOne(Pd,this._element)}_getItems(){return j.find(Ca,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Ke()?e===wn?bn:Zn:e===wn?Zn:bn}_orderToDirection(e){return Ke()?e===bn?wn:Gi:e===bn?Gi:wn}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="number"){t.to(e);return}if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(document,Md,Bd,function(n){let e=j.getElementFromSelector(this);if(!e||!e.classList.contains(xa))return;n.preventDefault();let t=ti.getOrCreateInstance(e),i=this.getAttribute("data-bs-slide-to");if(i){t.to(i),t._maybeEnableCycle();return}if(ft.getDataAttribute(this,"slide")==="next"){t.next(),t._maybeEnableCycle();return}t.prev(),t._maybeEnableCycle()});x.on(window,Ld,()=>{let n=j.find(Vd);for(let e of n)ti.getOrCreateInstance(e)});Qe(ti);var qd="collapse",Ud="bs.collapse",ri=`.${Ud}`,Yd=".data-api",Gd=`show${ri}`,Kd=`shown${ri}`,Xd=`hide${ri}`,Qd=`hidden${ri}`,Jd=`click${ri}${Yd}`,po="show",Tn="collapse",ji="collapsing",Zd="collapsed",ef=`:scope .${Tn} .${Tn}`,tf="collapse-horizontal",nf="width",rf="height",of=".collapse.show, .collapse.collapsing",Ao='[data-bs-toggle="collapse"]',sf={parent:null,toggle:!0},af={parent:"(null|element)",toggle:"boolean"},Ct=class n extends je{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let i=j.find(Ao);for(let r of i){let o=j.getSelectorFromElement(r),s=j.find(o).filter(a=>a===this._element);o!==null&&s.length&&this._triggerArray.push(r)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return sf}static get DefaultType(){return af}static get NAME(){return qd}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(of).filter(a=>a!==this._element).map(a=>n.getOrCreateInstance(a,{toggle:!1}))),e.length&&e[0]._isTransitioning||x.trigger(this._element,Gd).defaultPrevented)return;for(let a of e)a.hide();let i=this._getDimension();this._element.classList.remove(Tn),this._element.classList.add(ji),this._element.style[i]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let r=()=>{this._isTransitioning=!1,this._element.classList.remove(ji),this._element.classList.add(Tn,po),this._element.style[i]="",x.trigger(this._element,Kd)},s=`scroll${i[0].toUpperCase()+i.slice(1)}`;this._queueCallback(r,this._element,!0),this._element.style[i]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown()||x.trigger(this._element,Xd).defaultPrevented)return;let t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,ii(this._element),this._element.classList.add(ji),this._element.classList.remove(Tn,po);for(let r of this._triggerArray){let o=j.getElementFromSelector(r);o&&!this._isShown(o)&&this._addAriaAndCollapsedClass([r],!1)}this._isTransitioning=!0;let i=()=>{this._isTransitioning=!1,this._element.classList.remove(ji),this._element.classList.add(Tn),x.trigger(this._element,Qd)};this._element.style[t]="",this._queueCallback(i,this._element,!0)}_isShown(e=this._element){return e.classList.contains(po)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=xt(e.parent),e}_getDimension(){return this._element.classList.contains(tf)?nf:rf}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(Ao);for(let t of e){let i=j.getElementFromSelector(t);i&&this._addAriaAndCollapsedClass([t],this._isShown(i))}}_getFirstLevelChildren(e){let t=j.find(ef,this._config.parent);return j.find(e,this._config.parent).filter(i=>!t.includes(i))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let i of e)i.classList.toggle(Zd,!t),i.setAttribute("aria-expanded",t)}static jQueryInterface(e){let t={};return typeof e=="string"&&/show|hide/.test(e)&&(t.toggle=!1),this.each(function(){let i=n.getOrCreateInstance(this,t);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e]()}})}};x.on(document,Jd,Ao,function(n){(n.target.tagName==="A"||n.delegateTarget&&n.delegateTarget.tagName==="A")&&n.preventDefault();for(let e of j.getMultipleElementsFromSelector(this))Ct.getOrCreateInstance(e,{toggle:!1}).toggle()});Qe(Ct);var Bs="dropdown",lf="bs.dropdown",Jt=`.${lf}`,Lo=".data-api",cf="Escape",Vs="Tab",uf="ArrowUp",zs="ArrowDown",df=2,ff=`hide${Jt}`,hf=`hidden${Jt}`,pf=`show${Jt}`,mf=`shown${Jt}`,Aa=`click${Jt}${Lo}`,Sa=`keydown${Jt}${Lo}`,gf=`keyup${Jt}${Lo}`,xn="show",vf="dropup",yf="dropend",Ef="dropstart",bf="dropup-center",_f="dropdown-center",Gt='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',wf=`${Gt}.${xn}`,Ki=".dropdown-menu",xf=".navbar",Tf=".navbar-nav",Cf=".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",Af=Ke()?"top-end":"top-start",Sf=Ke()?"top-start":"top-end",Df=Ke()?"bottom-end":"bottom-start",Of=Ke()?"bottom-start":"bottom-end",Lf=Ke()?"left-start":"right-start",Mf=Ke()?"right-start":"left-start",kf="top",Nf="bottom",If={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Rf={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"},At=class n extends je{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=j.next(this._element,Ki)[0]||j.prev(this._element,Ki)[0]||j.findOne(Ki,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return If}static get DefaultType(){return Rf}static get NAME(){return Bs}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Tt(this._element)||this._isShown())return;let e={relatedTarget:this._element};if(!x.trigger(this._element,pf,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(Tf))for(let i of[].concat(...document.body.children))x.on(i,"mouseover",Xi);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(xn),this._element.classList.add(xn),x.trigger(this._element,mf,e)}}hide(){if(Tt(this._element)||!this._isShown())return;let e={relatedTarget:this._element};this._completeHide(e)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(e){if(!x.trigger(this._element,ff,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(let i of[].concat(...document.body.children))x.off(i,"mouseover",Xi);this._popper&&this._popper.destroy(),this._menu.classList.remove(xn),this._element.classList.remove(xn),this._element.setAttribute("aria-expanded","false"),ft.removeDataAttribute(this._menu,"popper"),x.trigger(this._element,hf,e)}}_getConfig(e){if(e=super._getConfig(e),typeof e.reference=="object"&&!dt(e.reference)&&typeof e.reference.getBoundingClientRect!="function")throw new TypeError(`${Bs.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(typeof Vi=="undefined")throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let e=this._element;this._config.reference==="parent"?e=this._parent:dt(this._config.reference)?e=xt(this._config.reference):typeof this._config.reference=="object"&&(e=this._config.reference);let t=this._getPopperConfig();this._popper=Jn(e,this._menu,t)}_isShown(){return this._menu.classList.contains(xn)}_getPlacement(){let e=this._parent;if(e.classList.contains(yf))return Lf;if(e.classList.contains(Ef))return Mf;if(e.classList.contains(bf))return kf;if(e.classList.contains(_f))return Nf;let t=getComputedStyle(this._menu).getPropertyValue("--bs-position").trim()==="end";return e.classList.contains(vf)?t?Sf:Af:t?Of:Df}_detectNavbar(){return this._element.closest(xf)!==null}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_getPopperConfig(){let e={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||this._config.display==="static")&&(ft.setDataAttribute(this._menu,"popper","static"),e.modifiers=[{name:"applyStyles",enabled:!1}]),O(O({},e),He(this._config.popperConfig,[void 0,e]))}_selectMenuItem({key:e,target:t}){let i=j.find(Cf,this._menu).filter(r=>An(r));i.length&&Do(i,t,e===zs,!i.includes(t)).focus()}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}static clearMenus(e){if(e.button===df||e.type==="keyup"&&e.key!==Vs)return;let t=j.find(wf);for(let i of t){let r=n.getInstance(i);if(!r||r._config.autoClose===!1)continue;let o=e.composedPath(),s=o.includes(r._menu);if(o.includes(r._element)||r._config.autoClose==="inside"&&!s||r._config.autoClose==="outside"&&s||r._menu.contains(e.target)&&(e.type==="keyup"&&e.key===Vs||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;let a={relatedTarget:r._element};e.type==="click"&&(a.clickEvent=e),r._completeHide(a)}}static dataApiKeydownHandler(e){let t=/input|textarea/i.test(e.target.tagName),i=e.key===cf,r=[uf,zs].includes(e.key);if(!r&&!i||t&&!i)return;e.preventDefault();let o=this.matches(Gt)?this:j.prev(this,Gt)[0]||j.next(this,Gt)[0]||j.findOne(Gt,e.delegateTarget.parentNode),s=n.getOrCreateInstance(o);if(r){e.stopPropagation(),s.show(),s._selectMenuItem(e);return}s._isShown()&&(e.stopPropagation(),s.hide(),o.focus())}};x.on(document,Sa,Gt,At.dataApiKeydownHandler);x.on(document,Sa,Ki,At.dataApiKeydownHandler);x.on(document,Aa,At.clearMenus);x.on(document,gf,At.clearMenus);x.on(document,Aa,Gt,function(n){n.preventDefault(),At.getOrCreateInstance(this).toggle()});Qe(At);var Da="backdrop",Hf="fade",js="show",Ws=`mousedown.bs.${Da}`,Pf={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ff={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"},er=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return Pf}static get DefaultType(){return Ff}static get NAME(){return Da}show(e){if(!this._config.isVisible){He(e);return}this._append();let t=this._getElement();this._config.isAnimated&&ii(t),t.classList.add(js),this._emulateAnimation(()=>{He(e)})}hide(e){if(!this._config.isVisible){He(e);return}this._getElement().classList.remove(js),this._emulateAnimation(()=>{this.dispose(),He(e)})}dispose(){this._isAppended&&(x.off(this._element,Ws),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){let e=document.createElement("div");e.className=this._config.className,this._config.isAnimated&&e.classList.add(Hf),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=xt(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),x.on(e,Ws,()=>{He(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){pa(e,this._getElement(),this._config.isAnimated)}},$f="focustrap",Bf="bs.focustrap",tr=`.${Bf}`,Vf=`focusin${tr}`,zf=`keydown.tab${tr}`,jf="Tab",Wf="forward",qs="backward",qf={autofocus:!0,trapElement:null},Uf={autofocus:"boolean",trapElement:"element"},nr=class extends Xt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return qf}static get DefaultType(){return Uf}static get NAME(){return $f}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),x.off(document,tr),x.on(document,Vf,e=>this._handleFocusin(e)),x.on(document,zf,e=>this._handleKeydown(e)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,x.off(document,tr))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let i=j.focusableChildren(t);i.length===0?t.focus():this._lastTabNavDirection===qs?i[i.length-1].focus():i[0].focus()}_handleKeydown(e){e.key===jf&&(this._lastTabNavDirection=e.shiftKey?qs:Wf)}},Us=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ys=".sticky-top",Wi="padding-right",Gs="margin-right",ni=class{constructor(){this._element=document.body}getWidth(){let e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){let e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Wi,t=>t+e),this._setElementAttributes(Us,Wi,t=>t+e),this._setElementAttributes(Ys,Gs,t=>t-e)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Wi),this._resetElementAttributes(Us,Wi),this._resetElementAttributes(Ys,Gs)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(e,t,i){let r=this.getWidth(),o=s=>{if(s!==this._element&&window.innerWidth>s.clientWidth+r)return;this._saveInitialAttribute(s,t);let a=window.getComputedStyle(s).getPropertyValue(t);s.style.setProperty(t,`${i(Number.parseFloat(a))}px`)};this._applyManipulationCallback(e,o)}_saveInitialAttribute(e,t){let i=e.style.getPropertyValue(t);i&&ft.setDataAttribute(e,t,i)}_resetElementAttributes(e,t){let i=r=>{let o=ft.getDataAttribute(r,t);if(o===null){r.style.removeProperty(t);return}ft.removeDataAttribute(r,t),r.style.setProperty(t,o)};this._applyManipulationCallback(e,i)}_applyManipulationCallback(e,t){if(dt(e)){t(e);return}for(let i of j.find(e,this._element))t(i)}},Yf="modal",Gf="bs.modal",Xe=`.${Gf}`,Kf=".data-api",Xf="Escape",Qf=`hide${Xe}`,Jf=`hidePrevented${Xe}`,Oa=`hidden${Xe}`,La=`show${Xe}`,Zf=`shown${Xe}`,eh=`resize${Xe}`,th=`click.dismiss${Xe}`,nh=`mousedown.dismiss${Xe}`,ih=`keydown.dismiss${Xe}`,rh=`click${Xe}${Kf}`,Ks="modal-open",oh="fade",Xs="show",mo="modal-static",sh=".modal.show",ah=".modal-dialog",lh=".modal-body",ch='[data-bs-toggle="modal"]',uh={backdrop:!0,focus:!0,keyboard:!0},dh={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"},tt=class n extends je{constructor(e,t){super(e,t),this._dialog=j.findOne(ah,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new ni,this._addEventListeners()}static get Default(){return uh}static get DefaultType(){return dh}static get NAME(){return Yf}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||x.trigger(this._element,La,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Ks),this._adjustDialog(),this._backdrop.show(()=>this._showElement(e)))}hide(){!this._isShown||this._isTransitioning||x.trigger(this._element,Qf).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Xs),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){x.off(window,Xe),x.off(this._dialog,Xe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new er({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new nr({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;let t=j.findOne(lh,this._dialog);t&&(t.scrollTop=0),ii(this._element),this._element.classList.add(Xs);let i=()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,x.trigger(this._element,Zf,{relatedTarget:e})};this._queueCallback(i,this._dialog,this._isAnimated())}_addEventListeners(){x.on(this._element,ih,e=>{if(e.key===Xf){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),x.on(window,eh,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),x.on(this._element,nh,e=>{x.one(this._element,th,t=>{if(!(this._element!==e.target||this._element!==t.target)){if(this._config.backdrop==="static"){this._triggerBackdropTransition();return}this._config.backdrop&&this.hide()}})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Ks),this._resetAdjustments(),this._scrollBar.reset(),x.trigger(this._element,Oa)})}_isAnimated(){return this._element.classList.contains(oh)}_triggerBackdropTransition(){if(x.trigger(this._element,Jf).defaultPrevented)return;let t=this._element.scrollHeight>document.documentElement.clientHeight,i=this._element.style.overflowY;i==="hidden"||this._element.classList.contains(mo)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(mo),this._queueCallback(()=>{this._element.classList.remove(mo),this._queueCallback(()=>{this._element.style.overflowY=i},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),i=t>0;if(i&&!e){let r=Ke()?"paddingLeft":"paddingRight";this._element.style[r]=`${t}px`}if(!i&&e){let r=Ke()?"paddingRight":"paddingLeft";this._element.style[r]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(e,t){return this.each(function(){let i=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof i[e]=="undefined")throw new TypeError(`No method named "${e}"`);i[e](t)}})}};x.on(document,rh,ch,function(n){let e=j.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&n.preventDefault(),x.one(e,La,r=>{r.defaultPrevented||x.one(e,Oa,()=>{An(this)&&this.focus()})});let t=j.findOne(sh);t&&tt.getInstance(t).hide(),tt.getOrCreateInstance(e).toggle(this)});rr(tt);Qe(tt);var fh="offcanvas",hh="bs.offcanvas",mt=`.${hh}`,Ma=".data-api",ph=`load${mt}${Ma}`,mh="Escape",Qs="show",Js="showing",Zs="hiding",gh="offcanvas-backdrop",ka=".offcanvas.show",vh=`show${mt}`,yh=`shown${mt}`,Eh=`hide${mt}`,ea=`hidePrevented${mt}`,Na=`hidden${mt}`,bh=`resize${mt}`,_h=`click${mt}${Ma}`,wh=`keydown.dismiss${mt}`,xh='[data-bs-toggle="offcanvas"]',Th={backdrop:!0,keyboard:!0,scroll:!1},Ch={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"},St=class n extends je{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Th}static get DefaultType(){return Ch}static get NAME(){return fh}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||x.trigger(this._element,vh,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||new ni().hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Js);let i=()=>{(!this._config.scroll||this._config.backdrop)&&this._focustrap.activate(),this._element.classList.add(Qs),this._element.classList.remove(Js),x.trigger(this._element,yh,{relatedTarget:e})};this._queueCallback(i,this._element,!0)}hide(){if(!this._isShown||x.trigger(this._element,Eh).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Zs),this._backdrop.hide();let t=()=>{this._element.classList.remove(Qs,Zs),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||new ni().reset(),x.trigger(this._element,Na)};this._queueCallback(t,this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop==="static"){x.trigger(this._element,ea);return}this.hide()},t=!!this._config.backdrop;return new er({className:gh,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new nr({trapElement:this._element})}_addEventListeners(){x.on(this._element,wh,e=>{if(e.key===mh){if(this._config.keyboard){this.hide();return}x.trigger(this._element,ea)}})}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};x.on(document,_h,xh,function(n){let e=j.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&n.preventDefault(),Tt(this))return;x.one(e,Na,()=>{An(this)&&this.focus()});let t=j.findOne(ka);t&&t!==e&&St.getInstance(t).hide(),St.getOrCreateInstance(e).toggle(this)});x.on(window,ph,()=>{for(let n of j.find(ka))St.getOrCreateInstance(n).show()});x.on(window,bh,()=>{for(let n of j.find("[aria-modal][class*=show][class*=offcanvas-]"))getComputedStyle(n).position!=="fixed"&&St.getOrCreateInstance(n).hide()});rr(St);Qe(St);var Ah=/^aria-[\w-]*$/i,Ia={"*":["class","dir","id","lang","role",Ah],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Sh=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Dh=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Oh=(n,e)=>{let t=n.nodeName.toLowerCase();return e.includes(t)?Sh.has(t)?!!Dh.test(n.nodeValue):!0:e.filter(i=>i instanceof RegExp).some(i=>i.test(t))};function Lh(n,e,t){if(!n.length)return n;if(t&&typeof t=="function")return t(n);let r=new window.DOMParser().parseFromString(n,"text/html"),o=[].concat(...r.body.querySelectorAll("*"));for(let s of o){let a=s.nodeName.toLowerCase();if(!Object.keys(e).includes(a)){s.remove();continue}let l=[].concat(...s.attributes),c=[].concat(e["*"]||[],e[a]||[]);for(let u of l)Oh(u,c)||s.removeAttribute(u.nodeName)}return r.body.innerHTML}var Mh="TemplateFactory",kh={allowList:Ia,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Nh={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ih={entry:"(string|element|function|null)",selector:"(string|element)"},So=class extends Xt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return kh}static get DefaultType(){return Nh}static get NAME(){return Mh}getContent(){return Object.values(this._config.content).map(e=>this._resolvePossibleFunction(e)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content=O(O({},this._config.content),e),this}toHtml(){let e=document.createElement("div");e.innerHTML=this._maybeSanitize(this._config.template);for(let[r,o]of Object.entries(this._config.content))this._setContent(e,o,r);let t=e.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&t.classList.add(...i.split(" ")),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(let[t,i]of Object.entries(e))super._typeCheckConfig({selector:t,entry:i},Ih)}_setContent(e,t,i){let r=j.findOne(i,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(dt(t)){this._putElementInTemplate(xt(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Lh(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return He(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML="",t.append(e);return}t.textContent=e.textContent}},Rh="tooltip",Hh=new Set(["sanitize","allowList","sanitizeFn"]),go="fade",Ph="modal",qi="show",Fh=".tooltip-inner",ta=`.${Ph}`,na="hide.bs.modal",ei="hover",vo="focus",yo="click",$h="manual",Bh="hide",Vh="hidden",zh="show",jh="shown",Wh="inserted",qh="click",Uh="focusin",Yh="focusout",Gh="mouseenter",Kh="mouseleave",Xh={AUTO:"auto",TOP:"top",RIGHT:Ke()?"left":"right",BOTTOM:"bottom",LEFT:Ke()?"right":"left"},Qh={allowList:Ia,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},Jh={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"},ht=class n extends je{constructor(e,t){if(typeof Vi=="undefined")throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Qh}static get DefaultType(){return Jh}static get NAME(){return Rh}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){if(this._isEnabled){if(this._isShown()){this._leave();return}this._enter()}}dispose(){clearTimeout(this._timeout),x.off(this._element.closest(ta),na,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if(this._element.style.display==="none")throw new Error("Please use show on visible elements");if(!(this._isWithContent()&&this._isEnabled))return;let e=x.trigger(this._element,this.constructor.eventName(zh)),i=(fa(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!i)return;this._disposePopper();let r=this._getTipElement();this._element.setAttribute("aria-describedby",r.getAttribute("id"));let{container:o}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(o.append(r),x.trigger(this._element,this.constructor.eventName(Wh))),this._popper=this._createPopper(r),r.classList.add(qi),"ontouchstart"in document.documentElement)for(let a of[].concat(...document.body.children))x.on(a,"mouseover",Xi);let s=()=>{x.trigger(this._element,this.constructor.eventName(jh)),this._isHovered===!1&&this._leave(),this._isHovered=!1};this._queueCallback(s,this.tip,this._isAnimated())}hide(){if(!this._isShown()||x.trigger(this._element,this.constructor.eventName(Bh)).defaultPrevented)return;if(this._getTipElement().classList.remove(qi),"ontouchstart"in document.documentElement)for(let r of[].concat(...document.body.children))x.off(r,"mouseover",Xi);this._activeTrigger[yo]=!1,this._activeTrigger[vo]=!1,this._activeTrigger[ei]=!1,this._isHovered=null;let i=()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),x.trigger(this._element,this.constructor.eventName(Vh)))};this._queueCallback(i,this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return!!this._getTitle()}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(e){let t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(go,qi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let i=$u(this.constructor.NAME).toString();return t.setAttribute("id",i),this._isAnimated()&&t.classList.add(go),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new So(ae(O({},this._config),{content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)})),this._templateFactory}_getContentForTemplate(){return{[Fh]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(go)}_isShown(){return this.tip&&this.tip.classList.contains(qi)}_createPopper(e){let t=He(this._config.placement,[this,e,this._element]),i=Xh[t.toUpperCase()];return Jn(this._element,e,this._getPopperConfig(i))}_getOffset(){let{offset:e}=this._config;return typeof e=="string"?e.split(",").map(t=>Number.parseInt(t,10)):typeof e=="function"?t=>e(t,this._element):e}_resolvePossibleFunction(e){return He(e,[this._element,this._element])}_getPopperConfig(e){let t={placement:e,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:i=>{this._getTipElement().setAttribute("data-popper-placement",i.state.placement)}}]};return O(O({},t),He(this._config.popperConfig,[void 0,t]))}_setListeners(){let e=this._config.trigger.split(" ");for(let t of e)if(t==="click")x.on(this._element,this.constructor.eventName(qh),this._config.selector,i=>{let r=this._initializeOnDelegatedTarget(i);r._activeTrigger[yo]=!(r._isShown()&&r._activeTrigger[yo]),r.toggle()});else if(t!==$h){let i=t===ei?this.constructor.eventName(Gh):this.constructor.eventName(Uh),r=t===ei?this.constructor.eventName(Kh):this.constructor.eventName(Yh);x.on(this._element,i,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusin"?vo:ei]=!0,s._enter()}),x.on(this._element,r,this._config.selector,o=>{let s=this._initializeOnDelegatedTarget(o);s._activeTrigger[o.type==="focusout"?vo:ei]=s._element.contains(o.relatedTarget),s._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},x.on(this._element.closest(ta),na,this._hideModalHandler)}_fixTitle(){let e=this._element.getAttribute("title");e&&(!this._element.getAttribute("aria-label")&&!this._element.textContent.trim()&&this._element.setAttribute("aria-label",e),this._element.setAttribute("data-bs-original-title",e),this._element.removeAttribute("title"))}_enter(){if(this._isShown()||this._isHovered){this._isHovered=!0;return}this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show)}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){let t=ft.getDataAttributes(this._element);for(let i of Object.keys(t))Hh.has(i)&&delete t[i];return e=O(O({},t),typeof e=="object"&&e?e:{}),e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=e.container===!1?document.body:xt(e.container),typeof e.delay=="number"&&(e.delay={show:e.delay,hide:e.delay}),typeof e.title=="number"&&(e.title=e.title.toString()),typeof e.content=="number"&&(e.content=e.content.toString()),e}_getDelegateConfig(){let e={};for(let[t,i]of Object.entries(this._config))this.constructor.Default[t]!==i&&(e[t]=i);return e.selector=!1,e.trigger="manual",e}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}};Qe(ht);var Zh="popover",ep=".popover-header",tp=".popover-body",np=ae(O({},ht.Default),{content:"",offset:[0,8],placement:"right",template:'',trigger:"click"}),ip=ae(O({},ht.DefaultType),{content:"(null|string|element|function)"}),Cn=class n extends ht{static get Default(){return np}static get DefaultType(){return ip}static get NAME(){return Zh}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ep]:this._getTitle(),[tp]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e]()}})}};Qe(Cn);var rp="scrollspy",op="bs.scrollspy",Mo=`.${op}`,sp=".data-api",ap=`activate${Mo}`,ia=`click${Mo}`,lp=`load${Mo}${sp}`,cp="dropdown-item",_n="active",up='[data-bs-spy="scroll"]',Eo="[href]",dp=".nav, .list-group",ra=".nav-link",fp=".nav-item",hp=".list-group-item",pp=`${ra}, ${fp} > ${ra}, ${hp}`,mp=".dropdown",gp=".dropdown-toggle",vp={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},yp={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"},ir=class n extends je{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement=getComputedStyle(this._element).overflowY==="visible"?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return vp}static get DefaultType(){return yp}static get NAME(){return rp}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(let e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=xt(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,typeof e.threshold=="string"&&(e.threshold=e.threshold.split(",").map(t=>Number.parseFloat(t))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(x.off(this._config.target,ia),x.on(this._config.target,ia,Eo,e=>{let t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();let i=this._rootElement||window,r=t.offsetTop-this._element.offsetTop;if(i.scrollTo){i.scrollTo({top:r,behavior:"smooth"});return}i.scrollTop=r}}))}_getNewObserver(){let e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),e)}_observerCallback(e){let t=s=>this._targetLinks.get(`#${s.target.id}`),i=s=>{this._previousScrollData.visibleEntryTop=s.target.offsetTop,this._process(t(s))},r=(this._rootElement||document.documentElement).scrollTop,o=r>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=r;for(let s of e){if(!s.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(s));continue}let a=s.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(o&&a){if(i(s),!r)return;continue}!o&&!a&&i(s)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;let e=j.find(Eo,this._config.target);for(let t of e){if(!t.hash||Tt(t))continue;let i=j.findOne(decodeURI(t.hash),this._element);An(i)&&(this._targetLinks.set(decodeURI(t.hash),t),this._observableSections.set(t.hash,i))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(_n),this._activateParents(e),x.trigger(this._element,ap,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains(cp)){j.findOne(gp,e.closest(mp)).classList.add(_n);return}for(let t of j.parents(e,dp))for(let i of j.prev(t,pp))i.classList.add(_n)}_clearActiveClass(e){e.classList.remove(_n);let t=j.find(`${Eo}.${_n}`,e);for(let i of t)i.classList.remove(_n)}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(window,lp,()=>{for(let n of j.find(up))ir.getOrCreateInstance(n)});Qe(ir);var Ep="tab",bp="bs.tab",Zt=`.${bp}`,_p=`hide${Zt}`,wp=`hidden${Zt}`,xp=`show${Zt}`,Tp=`shown${Zt}`,Cp=`click${Zt}`,Ap=`keydown${Zt}`,Sp=`load${Zt}`,Dp="ArrowLeft",oa="ArrowRight",Op="ArrowUp",sa="ArrowDown",bo="Home",aa="End",Kt="active",la="fade",_o="show",Lp="dropdown",Ra=".dropdown-toggle",Mp=".dropdown-menu",wo=`:not(${Ra})`,kp='.list-group, .nav, [role="tablist"]',Np=".nav-item, .list-group-item",Ip=`.nav-link${wo}, .list-group-item${wo}, [role="tab"]${wo}`,Ha='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',xo=`${Ip}, ${Ha}`,Rp=`.${Kt}[data-bs-toggle="tab"], .${Kt}[data-bs-toggle="pill"], .${Kt}[data-bs-toggle="list"]`,Qt=class n extends je{constructor(e){super(e),this._parent=this._element.closest(kp),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),x.on(this._element,Ap,t=>this._keydown(t)))}static get NAME(){return Ep}show(){let e=this._element;if(this._elemIsActive(e))return;let t=this._getActiveElem(),i=t?x.trigger(t,_p,{relatedTarget:e}):null;x.trigger(e,xp,{relatedTarget:t}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){if(!e)return;e.classList.add(Kt),this._activate(j.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.add(_o);return}e.removeAttribute("tabindex"),e.setAttribute("aria-selected",!0),this._toggleDropDown(e,!0),x.trigger(e,Tp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_deactivate(e,t){if(!e)return;e.classList.remove(Kt),e.blur(),this._deactivate(j.getElementFromSelector(e));let i=()=>{if(e.getAttribute("role")!=="tab"){e.classList.remove(_o);return}e.setAttribute("aria-selected",!1),e.setAttribute("tabindex","-1"),this._toggleDropDown(e,!1),x.trigger(e,wp,{relatedTarget:t})};this._queueCallback(i,e,e.classList.contains(la))}_keydown(e){if(![Dp,oa,Op,sa,bo,aa].includes(e.key))return;e.stopPropagation(),e.preventDefault();let t=this._getChildren().filter(r=>!Tt(r)),i;if([bo,aa].includes(e.key))i=t[e.key===bo?0:t.length-1];else{let r=[oa,sa].includes(e.key);i=Do(t,e.target,r,!0)}i&&(i.focus({preventScroll:!0}),n.getOrCreateInstance(i).show())}_getChildren(){return j.find(xo,this._parent)}_getActiveElem(){return this._getChildren().find(e=>this._elemIsActive(e))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,"role","tablist");for(let i of t)this._setInitialAttributesOnChild(i)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);let t=this._elemIsActive(e),i=this._getOuterElement(e);e.setAttribute("aria-selected",t),i!==e&&this._setAttributeIfNotExists(i,"role","presentation"),t||e.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(e,"role","tab"),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){let t=j.getElementFromSelector(e);t&&(this._setAttributeIfNotExists(t,"role","tabpanel"),e.id&&this._setAttributeIfNotExists(t,"aria-labelledby",`${e.id}`))}_toggleDropDown(e,t){let i=this._getOuterElement(e);if(!i.classList.contains(Lp))return;let r=(o,s)=>{let a=j.findOne(o,i);a&&a.classList.toggle(s,t)};r(Ra,Kt),r(Mp,_o),i.setAttribute("aria-expanded",t)}_setAttributeIfNotExists(e,t,i){e.hasAttribute(t)||e.setAttribute(t,i)}_elemIsActive(e){return e.classList.contains(Kt)}_getInnerElement(e){return e.matches(xo)?e:j.findOne(xo,e)}_getOuterElement(e){return e.closest(Np)||e}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this);if(typeof e=="string"){if(t[e]===void 0||e.startsWith("_")||e==="constructor")throw new TypeError(`No method named "${e}"`);t[e]()}})}};x.on(document,Cp,Ha,function(n){["A","AREA"].includes(this.tagName)&&n.preventDefault(),!Tt(this)&&Qt.getOrCreateInstance(this).show()});x.on(window,Sp,()=>{for(let n of j.find(Rp))Qt.getOrCreateInstance(n)});Qe(Qt);var Hp="toast",Pp="bs.toast",Ot=`.${Pp}`,Fp=`mouseover${Ot}`,$p=`mouseout${Ot}`,Bp=`focusin${Ot}`,Vp=`focusout${Ot}`,zp=`hide${Ot}`,jp=`hidden${Ot}`,Wp=`show${Ot}`,qp=`shown${Ot}`,Up="fade",ca="hide",Ui="show",Yi="showing",Yp={animation:"boolean",autohide:"boolean",delay:"number"},Gp={animation:!0,autohide:!0,delay:5e3},pt=class n extends je{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Gp}static get DefaultType(){return Yp}static get NAME(){return Hp}show(){if(x.trigger(this._element,Wp).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add(Up);let t=()=>{this._element.classList.remove(Yi),x.trigger(this._element,qp),this._maybeScheduleHide()};this._element.classList.remove(ca),ii(this._element),this._element.classList.add(Ui,Yi),this._queueCallback(t,this._element,this._config.animation)}hide(){if(!this.isShown()||x.trigger(this._element,zp).defaultPrevented)return;let t=()=>{this._element.classList.add(ca),this._element.classList.remove(Yi,Ui),x.trigger(this._element,jp)};this._element.classList.add(Yi),this._queueCallback(t,this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Ui),super.dispose()}isShown(){return this._element.classList.contains(Ui)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(e,t){switch(e.type){case"mouseover":case"mouseout":{this._hasMouseInteraction=t;break}case"focusin":case"focusout":{this._hasKeyboardInteraction=t;break}}if(t){this._clearTimeout();return}let i=e.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){x.on(this._element,Fp,e=>this._onInteraction(e,!0)),x.on(this._element,$p,e=>this._onInteraction(e,!1)),x.on(this._element,Bp,e=>this._onInteraction(e,!0)),x.on(this._element,Vp,e=>this._onInteraction(e,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each(function(){let t=n.getOrCreateInstance(this,e);if(typeof e=="string"){if(typeof t[e]=="undefined")throw new TypeError(`No method named "${e}"`);t[e](this)}})}};rr(pt);Qe(pt);var Kp=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(n,e){return getInputValues(n,e||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(n){return"[hx-"+n+"], [data-hx-"+n+"]"}).join(", ");function parseInterval(n){if(n==null)return;let e=NaN;return n.slice(-2)=="ms"?e=parseFloat(n.slice(0,-2)):n.slice(-1)=="s"?e=parseFloat(n.slice(0,-1))*1e3:n.slice(-1)=="m"?e=parseFloat(n.slice(0,-1))*1e3*60:e=parseFloat(n),isNaN(e)?void 0:e}function getRawAttribute(n,e){return n instanceof Element&&n.getAttribute(e)}function hasAttribute(n,e){return!!n.hasAttribute&&(n.hasAttribute(e)||n.hasAttribute("data-"+e))}function getAttributeValue(n,e){return getRawAttribute(n,e)||getRawAttribute(n,"data-"+e)}function parentElt(n){let e=n.parentElement;return!e&&n.parentNode instanceof ShadowRoot?n.parentNode:e}function getDocument(){return document}function getRootNode(n,e){return n.getRootNode?n.getRootNode({composed:e}):getDocument()}function getClosestMatch(n,e){for(;n&&!e(n);)n=parentElt(n);return n||null}function getAttributeValueWithDisinheritance(n,e,t){let i=getAttributeValue(e,t),r=getAttributeValue(e,"hx-disinherit");var o=getAttributeValue(e,"hx-inherit");if(n!==e){if(htmx.config.disableInheritance)return o&&(o==="*"||o.split(" ").indexOf(t)>=0)?i:null;if(r&&(r==="*"||r.split(" ").indexOf(t)>=0))return"unset"}return i}function getClosestAttributeValue(n,e){let t=null;if(getClosestMatch(n,function(i){return!!(t=getAttributeValueWithDisinheritance(n,asElement(i),e))}),t!=="unset")return t}function matches(n,e){return n instanceof Element&&n.matches(e)}function getStartTag(n){let t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(n);return t?t[1].toLowerCase():""}function parseHTML(n){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(n):new DOMParser().parseFromString(n,"text/html")}function takeChildrenFor(n,e){for(;e.childNodes.length>0;)n.append(e.childNodes[0])}function duplicateScript(n){let e=getDocument().createElement("script");return forEach(n.attributes,function(t){e.setAttribute(t.name,t.value)}),e.textContent=n.textContent,e.async=!1,htmx.config.inlineScriptNonce&&(e.nonce=htmx.config.inlineScriptNonce),e}function isJavaScriptScriptNode(n){return n.matches("script")&&(n.type==="text/javascript"||n.type==="module"||n.type==="")}function normalizeScriptTags(n){Array.from(n.querySelectorAll("script")).forEach(e=>{if(isJavaScriptScriptNode(e)){let t=duplicateScript(e),i=e.parentNode;try{i.insertBefore(t,e)}catch(r){logError(r)}finally{e.remove()}}})}function makeFragment(n){let e=n.replace(/]*)?>[\s\S]*?<\/head>/i,""),t=getStartTag(e),i;if(t==="html"){i=new DocumentFragment;let o=parseHTML(n);takeChildrenFor(i,o.body),i.title=o.title}else if(t==="body"){i=new DocumentFragment;let o=parseHTML(e);takeChildrenFor(i,o.body),i.title=o.title}else{let o=parseHTML('");i=o.querySelector("template").content,i.title=o.title;var r=i.querySelector("title");r&&r.parentNode===i&&(r.remove(),i.title=r.innerText)}return i&&(htmx.config.allowScriptTags?normalizeScriptTags(i):i.querySelectorAll("script").forEach(o=>o.remove())),i}function maybeCall(n){n&&n()}function isType(n,e){return Object.prototype.toString.call(n)==="[object "+e+"]"}function isFunction(n){return typeof n=="function"}function isRawObject(n){return isType(n,"Object")}function getInternalData(n){let e="htmx-internal-data",t=n[e];return t||(t=n[e]={}),t}function toArray(n){let e=[];if(n)for(let t=0;t=0}function bodyContains(n){return n.getRootNode({composed:!0})===document}function splitOnWhitespace(n){return n.trim().split(/\s+/)}function mergeObjects(n,e){for(let t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);return n}function parseJSON(n){try{return JSON.parse(n)}catch(e){return logError(e),null}}function canAccessLocalStorage(){let n="htmx:sessionStorageTest";try{return sessionStorage.setItem(n,n),sessionStorage.removeItem(n),!0}catch(e){return!1}}function normalizePath(n){let e=new URL(n,"http://x");return e&&(n=e.pathname+e.search),n!="/"&&(n=n.replace(/\/+$/,"")),n}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(n){return htmx.on("htmx:load",function(t){n(t.detail.elt)})}function logAll(){htmx.logger=function(n,e,t){console&&console.log(e,n,t)}}function logNone(){htmx.logger=null}function find(n,e){return typeof n!="string"?n.querySelector(e):find(getDocument(),n)}function findAll(n,e){return typeof n!="string"?n.querySelectorAll(e):findAll(getDocument(),n)}function getWindow(){return window}function removeElement(n,e){n=resolveTarget(n),e?getWindow().setTimeout(function(){removeElement(n),n=null},e):parentElt(n).removeChild(n)}function asElement(n){return n instanceof Element?n:null}function asHtmlElement(n){return n instanceof HTMLElement?n:null}function asString(n){return typeof n=="string"?n:null}function asParentNode(n){return n instanceof Element||n instanceof Document||n instanceof DocumentFragment?n:null}function addClassToElement(n,e,t){n=asElement(resolveTarget(n)),n&&(t?getWindow().setTimeout(function(){addClassToElement(n,e),n=null},t):n.classList&&n.classList.add(e))}function removeClassFromElement(n,e,t){let i=asElement(resolveTarget(n));i&&(t?getWindow().setTimeout(function(){removeClassFromElement(i,e),i=null},t):i.classList&&(i.classList.remove(e),i.classList.length===0&&i.removeAttribute("class")))}function toggleClassOnElement(n,e){n=resolveTarget(n),n.classList.toggle(e)}function takeClassForElement(n,e){n=resolveTarget(n),forEach(n.parentElement.children,function(t){removeClassFromElement(t,e)}),addClassToElement(asElement(n),e)}function closest(n,e){return n=asElement(resolveTarget(n)),n?n.closest(e):null}function startsWith(n,e){return n.substring(0,e.length)===e}function endsWith(n,e){return n.substring(n.length-e.length)===e}function normalizeSelector(n){let e=n.trim();return startsWith(e,"<")&&endsWith(e,"/>")?e.substring(1,e.length-2):e}function querySelectorAllExt(n,e,t){if(e.indexOf("global ")===0)return querySelectorAllExt(n,e.slice(7),!0);n=resolveTarget(n);let i=[];{let s=0,a=0;for(let l=0;l"&&s--}a0;){let s=normalizeSelector(i.shift()),a;s.indexOf("closest ")===0?a=closest(asElement(n),normalizeSelector(s.slice(8))):s.indexOf("find ")===0?a=find(asParentNode(n),normalizeSelector(s.slice(5))):s==="next"||s==="nextElementSibling"?a=asElement(n).nextElementSibling:s.indexOf("next ")===0?a=scanForwardQuery(n,normalizeSelector(s.slice(5)),!!t):s==="previous"||s==="previousElementSibling"?a=asElement(n).previousElementSibling:s.indexOf("previous ")===0?a=scanBackwardsQuery(n,normalizeSelector(s.slice(9)),!!t):s==="document"?a=document:s==="window"?a=window:s==="body"?a=document.body:s==="root"?a=getRootNode(n,!!t):s==="host"?a=n.getRootNode().host:o.push(s),a&&r.push(a)}if(o.length>0){let s=o.join(","),a=asParentNode(getRootNode(n,!!t));r.push(...toArray(a.querySelectorAll(s)))}return r}var scanForwardQuery=function(n,e,t){let i=asParentNode(getRootNode(n,t)).querySelectorAll(e);for(let r=0;r=0;r--){let o=i[r];if(o.compareDocumentPosition(n)===Node.DOCUMENT_POSITION_FOLLOWING)return o}};function querySelectorExt(n,e){return typeof n!="string"?querySelectorAllExt(n,e)[0]:querySelectorAllExt(getDocument().body,n)[0]}function resolveTarget(n,e){return typeof n=="string"?find(asParentNode(e)||document,n):n}function processEventArgs(n,e,t,i){return isFunction(e)?{target:getDocument().body,event:asString(n),listener:e,options:t}:{target:resolveTarget(n),event:asString(e),listener:t,options:i}}function addEventListenerImpl(n,e,t,i){return ready(function(){let o=processEventArgs(n,e,t,i);o.target.addEventListener(o.event,o.listener,o.options)}),isFunction(e)?e:t}function removeEventListenerImpl(n,e,t){return ready(function(){let i=processEventArgs(n,e,t);i.target.removeEventListener(i.event,i.listener)}),isFunction(e)?e:t}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(n,e){let t=getClosestAttributeValue(n,e);if(t){if(t==="this")return[findThisElement(n,e)];{let i=querySelectorAllExt(n,t);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(t)){let o=asElement(getClosestMatch(n,function(s){return s!==n&&hasAttribute(asElement(s),e)}));o&&i.push(...findAttributeTargets(o,e))}return i.length===0?(logError('The selector "'+t+'" on '+e+" returned no matches!"),[DUMMY_ELT]):i}}}function findThisElement(n,e){return asElement(getClosestMatch(n,function(t){return getAttributeValue(asElement(t),e)!=null}))}function getTarget(n){let e=getClosestAttributeValue(n,"hx-target");return e?e==="this"?findThisElement(n,"hx-target"):querySelectorExt(n,e):getInternalData(n).boosted?getDocument().body:n}function shouldSettleAttribute(n){return htmx.config.attributesToSettle.includes(n)}function cloneAttributes(n,e){forEach(Array.from(n.attributes),function(t){!e.hasAttribute(t.name)&&shouldSettleAttribute(t.name)&&n.removeAttribute(t.name)}),forEach(e.attributes,function(t){shouldSettleAttribute(t.name)&&n.setAttribute(t.name,t.value)})}function isInlineSwap(n,e){let t=getExtensions(e);for(let i=0;i0?(o=n.substring(0,n.indexOf(":")),r=n.substring(n.indexOf(":")+1)):o=n),e.removeAttribute("hx-swap-oob"),e.removeAttribute("data-hx-swap-oob");let s=querySelectorAllExt(i,r,!1);return s.length?(forEach(s,function(a){let l,c=e.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(o,a)||(l=asParentNode(c));let u={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",u)&&(a=u.target,u.shouldSwap&&(handlePreservedElements(l),swapWithStyle(o,a,a,l,t),restorePreservedElements()),forEach(t.elts,function(d){triggerEvent(d,"htmx:oobAfterSwap",u)}))}),e.parentNode.removeChild(e)):(e.parentNode.removeChild(e),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:e})),n}function restorePreservedElements(){let n=find("#--htmx-preserve-pantry--");if(n){for(let e of[...n.children]){let t=find("#"+e.id);t.parentNode.moveBefore(e,t),t.remove()}n.remove()}}function handlePreservedElements(n){forEach(findAll(n,"[hx-preserve], [data-hx-preserve]"),function(e){let t=getAttributeValue(e,"id"),i=getDocument().getElementById(t);if(i!=null)if(e.moveBefore){let r=find("#--htmx-preserve-pantry--");r==null&&(getDocument().body.insertAdjacentHTML("afterend","
"),r=find("#--htmx-preserve-pantry--")),r.moveBefore(i,null)}else e.parentNode.replaceChild(i,e)})}function handleAttributes(n,e,t){forEach(e.querySelectorAll("[id]"),function(i){let r=getRawAttribute(i,"id");if(r&&r.length>0){let o=r.replace("'","\\'"),s=i.tagName.replace(":","\\:"),a=asParentNode(n),l=a&&a.querySelector(s+"[id='"+o+"']");if(l&&l!==a){let c=i.cloneNode();cloneAttributes(i,l),t.tasks.push(function(){cloneAttributes(i,c)})}}})}function makeAjaxLoadTask(n){return function(){removeClassFromElement(n,htmx.config.addedClass),processNode(asElement(n)),processFocus(asParentNode(n)),triggerEvent(n,"htmx:load")}}function processFocus(n){let e="[autofocus]",t=asHtmlElement(matches(n,e)?n:n.querySelector(e));t!=null&&t.focus()}function insertNodesBefore(n,e,t,i){for(handleAttributes(n,t,i);t.childNodes.length>0;){let r=t.firstChild;addClassToElement(asElement(r),htmx.config.addedClass),n.insertBefore(r,e),r.nodeType!==Node.TEXT_NODE&&r.nodeType!==Node.COMMENT_NODE&&i.tasks.push(makeAjaxLoadTask(r))}}function stringHash(n,e){let t=0;for(;t0}function swap(n,e,t,i){i||(i={});let r=null,o=null,s=function(){maybeCall(i.beforeSwapCallback),n=resolveTarget(n);let c=i.contextElement?getRootNode(i.contextElement,!1):getDocument(),u=document.activeElement,d={};d={elt:u,start:u?u.selectionStart:null,end:u?u.selectionEnd:null};let p=makeSettleInfo(n);if(t.swapStyle==="textContent")n.textContent=e;else{let m=makeFragment(e);if(p.title=i.title||m.title,i.historyRequest&&(m=m.querySelector("[hx-history-elt],[data-hx-history-elt]")||m),i.selectOOB){let v=i.selectOOB.split(",");for(let w=0;w0?getWindow().setTimeout(y,t.settleDelay):y()},a=htmx.config.globalViewTransitions;t.hasOwnProperty("transition")&&(a=t.transition);let l=i.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",i.eventInfo)&&typeof Promise!="undefined"&&document.startViewTransition){let c=new Promise(function(d,p){r=d,o=p}),u=s;s=function(){document.startViewTransition(function(){return u(),c})}}try{t!=null&&t.swapDelay&&t.swapDelay>0?getWindow().setTimeout(s,t.swapDelay):s()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",i.eventInfo),maybeCall(o),c}}function handleTriggerHeader(n,e,t){let i=n.getResponseHeader(e);if(i.indexOf("{")===0){let r=parseJSON(i);for(let o in r)if(r.hasOwnProperty(o)){let s=r[o];isRawObject(s)?t=s.target!==void 0?s.target:t:s={value:s},triggerEvent(t,o,s)}}else{let r=i.split(",");for(let o=0;o0;){let s=e[0];if(s==="]"){if(i--,i===0){o===null&&(r=r+"true"),e.shift(),r+=")})";try{let a=maybeEval(n,function(){return Function(r)()},function(){return!0});return a.source=r,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:r}),null}}}else s==="["&&i++;isPossibleRelativeReference(s,o,t)?r+="(("+t+"."+s+") ? ("+t+"."+s+") : (window."+s+"))":r=r+s,o=e.shift()}}}function consumeUntil(n,e){let t="";for(;n.length>0&&!e.test(n[0]);)t+=n.shift();return t}function consumeCSSSelector(n){let e;return n.length>0&&COMBINED_SELECTOR_START.test(n[0])?(n.shift(),e=consumeUntil(n,COMBINED_SELECTOR_END).trim(),n.shift()):e=consumeUntil(n,WHITESPACE_OR_COMMA),e}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(n,e,t){let i=[],r=tokenizeString(e);do{consumeUntil(r,NOT_WHITESPACE);let a=r.length,l=consumeUntil(r,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(r,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(r,/[,\[\s]/)),consumeUntil(r,NOT_WHITESPACE);var o=maybeGenerateConditional(n,r,"event");o&&(c.eventFilter=o),i.push(c)}else{let c={trigger:l};var o=maybeGenerateConditional(n,r,"event");for(o&&(c.eventFilter=o),consumeUntil(r,NOT_WHITESPACE);r.length>0&&r[0]!==",";){let d=r.shift();if(d==="changed")c.changed=!0;else if(d==="once")c.once=!0;else if(d==="consume")c.consume=!0;else if(d==="delay"&&r[0]===":")r.shift(),c.delay=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA));else if(d==="from"&&r[0]===":"){if(r.shift(),COMBINED_SELECTOR_START.test(r[0]))var s=consumeCSSSelector(r);else{var s=consumeUntil(r,WHITESPACE_OR_COMMA);if(s==="closest"||s==="find"||s==="next"||s==="previous"){r.shift();let y=consumeCSSSelector(r);y.length>0&&(s+=" "+y)}}c.from=s}else d==="target"&&r[0]===":"?(r.shift(),c.target=consumeCSSSelector(r)):d==="throttle"&&r[0]===":"?(r.shift(),c.throttle=parseInterval(consumeUntil(r,WHITESPACE_OR_COMMA))):d==="queue"&&r[0]===":"?(r.shift(),c.queue=consumeUntil(r,WHITESPACE_OR_COMMA)):d==="root"&&r[0]===":"?(r.shift(),c[d]=consumeCSSSelector(r)):d==="threshold"&&r[0]===":"?(r.shift(),c[d]=consumeUntil(r,WHITESPACE_OR_COMMA)):triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()});consumeUntil(r,NOT_WHITESPACE)}i.push(c)}r.length===a&&triggerErrorEvent(n,"htmx:syntax:error",{token:r.shift()}),consumeUntil(r,NOT_WHITESPACE)}while(r[0]===","&&r.shift());return t&&(t[e]=i),i}function getTriggerSpecs(n){let e=getAttributeValue(n,"hx-trigger"),t=[];if(e){let i=htmx.config.triggerSpecsCache;t=i&&i[e]||parseAndCacheTrigger(n,e,i)}return t.length>0?t:matches(n,"form")?[{trigger:"submit"}]:matches(n,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(n,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(n){getInternalData(n).cancelled=!0}function processPolling(n,e,t){let i=getInternalData(n);i.timeout=getWindow().setTimeout(function(){bodyContains(n)&&i.cancelled!==!0&&(maybeFilterEvent(t,n,makeEvent("hx:poll:trigger",{triggerSpec:t,target:n}))||e(n),processPolling(n,e,t))},t.pollInterval)}function isLocalLink(n){return location.hostname===n.hostname&&getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")!==0}function eltIsDisabled(n){return closest(n,htmx.config.disableSelector)}function boostElement(n,e,t){if(n instanceof HTMLAnchorElement&&isLocalLink(n)&&(n.target===""||n.target==="_self")||n.tagName==="FORM"&&String(getRawAttribute(n,"method")).toLowerCase()!=="dialog"){e.boosted=!0;let i,r;if(n.tagName==="A")i="get",r=getRawAttribute(n,"href");else{let o=getRawAttribute(n,"method");i=o?o.toLowerCase():"get",r=getRawAttribute(n,"action"),(r==null||r==="")&&(r=location.href),i==="get"&&r.includes("?")&&(r=r.replace(/\?[^#]+/,""))}t.forEach(function(o){addEventListener(n,function(s,a){let l=asElement(s);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(i,r,l,a)},e,o,!0)})}}function shouldCancel(n,e){if(n.type==="submit"&&e.tagName==="FORM")return!0;if(n.type==="click"){let t=e.closest('input[type="submit"], button');if(t&&t.form&&t.type==="submit")return!0;let i=e.closest("a"),r=/^#.+/;if(i&&i.href&&!r.test(i.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(n,e){return getInternalData(n).boosted&&n instanceof HTMLAnchorElement&&e.type==="click"&&(e.ctrlKey||e.metaKey)}function maybeFilterEvent(n,e,t){let i=n.eventFilter;if(i)try{return i.call(e,t)!==!0}catch(r){let o=i.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:r,source:o}),!0}return!1}function addEventListener(n,e,t,i,r){let o=getInternalData(n),s;i.from?s=querySelectorAllExt(n,i.from):s=[n],i.changed&&("lastValue"in o||(o.lastValue=new WeakMap),s.forEach(function(a){o.lastValue.has(i)||o.lastValue.set(i,new WeakMap),o.lastValue.get(i).set(a,a.value)})),forEach(s,function(a){let l=function(c){if(!bodyContains(n)){a.removeEventListener(i.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(n,c)||((r||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(i,n,c)))return;let u=getInternalData(c);if(u.triggerSpec=i,u.handledFor==null&&(u.handledFor=[]),u.handledFor.indexOf(n)<0){if(u.handledFor.push(n),i.consume&&c.stopPropagation(),i.target&&c.target&&!matches(asElement(c.target),i.target))return;if(i.once){if(o.triggeredOnce)return;o.triggeredOnce=!0}if(i.changed){let d=c.target,p=d.value,y=o.lastValue.get(i);if(y.has(d)&&y.get(d)===p)return;y.set(d,p)}if(o.delayed&&clearTimeout(o.delayed),o.throttle)return;i.throttle>0?o.throttle||(triggerEvent(n,"htmx:trigger"),e(n,c),o.throttle=getWindow().setTimeout(function(){o.throttle=null},i.throttle)):i.delay>0?o.delayed=getWindow().setTimeout(function(){triggerEvent(n,"htmx:trigger"),e(n,c)},i.delay):(triggerEvent(n,"htmx:trigger"),e(n,c))}};t.listenerInfos==null&&(t.listenerInfos=[]),t.listenerInfos.push({trigger:i.trigger,listener:l,on:a}),a.addEventListener(i.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(n){maybeReveal(n)}))},200))}function maybeReveal(n){!hasAttribute(n,"data-hx-revealed")&&isScrolledIntoView(n)&&(n.setAttribute("data-hx-revealed","true"),getInternalData(n).initHash?triggerEvent(n,"revealed"):n.addEventListener("htmx:afterProcessNode",function(){triggerEvent(n,"revealed")},{once:!0}))}function loadImmediately(n,e,t,i){let r=function(){t.loaded||(t.loaded=!0,triggerEvent(n,"htmx:trigger"),e(n))};i>0?getWindow().setTimeout(r,i):r()}function processVerbs(n,e,t){let i=!1;return forEach(VERBS,function(r){if(hasAttribute(n,"hx-"+r)){let o=getAttributeValue(n,"hx-"+r);i=!0,e.path=o,e.verb=r,t.forEach(function(s){addTriggerHandler(n,s,e,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(r,o,c,l)})})}}),i}function addTriggerHandler(n,e,t,i){if(e.trigger==="revealed")initScrollHandler(),addEventListener(n,i,t,e),maybeReveal(asElement(n));else if(e.trigger==="intersect"){let r={};e.root&&(r.root=querySelectorExt(n,e.root)),e.threshold&&(r.threshold=parseFloat(e.threshold)),new IntersectionObserver(function(s){for(let a=0;a0?(t.polling=!0,processPolling(asElement(n),i,e)):addEventListener(n,i,t,e)}function shouldProcessHxOn(n){let e=asElement(n);if(!e)return!1;let t=e.attributes;for(let i=0;i", "+o).join(""))}else return[]}function maybeSetLastButtonClicked(n){let e=getTargetButton(n.target),t=getRelatedFormData(n);t&&(t.lastButtonClicked=e)}function maybeUnsetLastButtonClicked(n){let e=getRelatedFormData(n);e&&(e.lastButtonClicked=null)}function getTargetButton(n){return closest(asElement(n),"button, input[type='submit']")}function getRelatedForm(n){return n.form||closest(n,"form")}function getRelatedFormData(n){let e=getTargetButton(n.target);if(!e)return;let t=getRelatedForm(e);if(t)return getInternalData(t)}function initButtonTracking(n){n.addEventListener("click",maybeSetLastButtonClicked),n.addEventListener("focusin",maybeSetLastButtonClicked),n.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(n,e,t){let i=getInternalData(n);Array.isArray(i.onHandlers)||(i.onHandlers=[]);let r,o=function(s){maybeEval(n,function(){eltIsDisabled(n)||(r||(r=new Function("event",t)),r.call(n,s))})};n.addEventListener(e,o),i.onHandlers.push({event:e,listener:o})}function processHxOnWildcard(n){deInitOnHandlers(n);for(let e=0;ehtmx.config.historyCacheSize;)o.shift();for(;o.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(o));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:o}),o.shift()}}function getCachedHistory(n){if(!canAccessLocalStorage())return null;n=normalizePath(n);let e=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let t=0;t=200&&this.status<400?(i.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",i),swap(i.historyElt,i.response,t,{contextElement:i.historyElt,historyRequest:!0}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:n,cacheMiss:!0,serverResponse:i.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",i)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",i)&&e.send()}function restoreHistory(n){saveCurrentPageToHistory(),n=n||location.pathname+location.search;let e=getCachedHistory(n);if(e){let t={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:e.scroll},i={path:n,item:e,historyElt:getHistoryElement(),swapSpec:t};triggerEvent(getDocument().body,"htmx:historyCacheHit",i)&&(swap(i.historyElt,e.content,t,{contextElement:i.historyElt,title:e.title}),setCurrentPathForHistory(i.path),triggerEvent(getDocument().body,"htmx:historyRestore",i))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(n)}function addRequestIndicatorClasses(n){let e=findAttributeTargets(n,"hx-indicator");return e==null&&(e=[n]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,t.classList.add.call(t.classList,htmx.config.requestClass)}),e}function disableElements(n){let e=findAttributeTargets(n,"hx-disabled-elt");return e==null&&(e=[]),forEach(e,function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||0)+1,t.setAttribute("disabled",""),t.setAttribute("data-disabled-by-htmx","")}),e}function removeRequestIndicators(n,e){forEach(n.concat(e),function(t){let i=getInternalData(t);i.requestCount=(i.requestCount||1)-1}),forEach(n,function(t){getInternalData(t).requestCount===0&&t.classList.remove.call(t.classList,htmx.config.requestClass)}),forEach(e,function(t){getInternalData(t).requestCount===0&&(t.removeAttribute("disabled"),t.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(n,e){for(let t=0;te.indexOf(r)<0):i=i.filter(r=>r!==e),t.delete(n),forEach(i,r=>t.append(n,r))}}function getValueFromInput(n){return n instanceof HTMLSelectElement&&n.multiple?toArray(n.querySelectorAll("option:checked")).map(function(e){return e.value}):n instanceof HTMLInputElement&&n.files?toArray(n.files):n.value}function processInputValue(n,e,t,i,r){if(!(i==null||haveSeenNode(n,i))){if(n.push(i),shouldInclude(i)){let o=getRawAttribute(i,"name");addValueToFormData(o,getValueFromInput(i),e),r&&validateElement(i,t)}i instanceof HTMLFormElement&&(forEach(i.elements,function(o){n.indexOf(o)>=0?removeValueFromFormData(o.name,getValueFromInput(o),e):n.push(o),r&&validateElement(o,t)}),new FormData(i).forEach(function(o,s){o instanceof File&&o.name===""||addValueToFormData(s,o,e)}))}}function validateElement(n,e){let t=n;t.willValidate&&(triggerEvent(t,"htmx:validation:validate"),t.checkValidity()||(triggerEvent(t,"htmx:validation:failed",{message:t.validationMessage,validity:t.validity})&&!e.length&&htmx.config.reportValidityOfForms&&t.reportValidity(),e.push({elt:t,message:t.validationMessage,validity:t.validity})))}function overrideFormData(n,e){for(let t of e.keys())n.delete(t);return e.forEach(function(t,i){n.append(i,t)}),n}function getInputValues(n,e){let t=[],i=new FormData,r=new FormData,o=[],s=getInternalData(n);s.lastButtonClicked&&!bodyContains(s.lastButtonClicked)&&(s.lastButtonClicked=null);let a=n instanceof HTMLFormElement&&n.noValidate!==!0||getAttributeValue(n,"hx-validate")==="true";if(s.lastButtonClicked&&(a=a&&s.lastButtonClicked.formNoValidate!==!0),e!=="get"&&processInputValue(t,r,o,getRelatedForm(n),a),processInputValue(t,i,o,n,a),s.lastButtonClicked||n.tagName==="BUTTON"||n.tagName==="INPUT"&&getRawAttribute(n,"type")==="submit"){let c=s.lastButtonClicked||n,u=getRawAttribute(c,"name");addValueToFormData(u,c.value,r)}let l=findAttributeTargets(n,"hx-include");return forEach(l,function(c){processInputValue(t,i,o,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(u){processInputValue(t,i,o,u,a)})}),overrideFormData(i,r),{errors:o,formData:i,values:formDataProxy(i)}}function appendParam(n,e,t){n!==""&&(n+="&"),String(t)==="[object Object]"&&(t=JSON.stringify(t));let i=encodeURIComponent(t);return n+=encodeURIComponent(e)+"="+i,n}function urlEncode(n){n=formDataFromObject(n);let e="";return n.forEach(function(t,i){e=appendParam(e,i,t)}),e}function getHeaders(n,e,t){let i={"HX-Request":"true","HX-Trigger":getRawAttribute(n,"id"),"HX-Trigger-Name":getRawAttribute(n,"name"),"HX-Target":getAttributeValue(e,"id"),"HX-Current-URL":location.href};return getValuesForElement(n,"hx-headers",!1,i),t!==void 0&&(i["HX-Prompt"]=t),getInternalData(n).boosted&&(i["HX-Boosted"]="true"),i}function filterValues(n,e){let t=getClosestAttributeValue(e,"hx-params");if(t){if(t==="none")return new FormData;if(t==="*")return n;if(t.indexOf("not ")===0)return forEach(t.slice(4).split(","),function(i){i=i.trim(),n.delete(i)}),n;{let i=new FormData;return forEach(t.split(","),function(r){r=r.trim(),n.has(r)&&n.getAll(r).forEach(function(o){i.append(r,o)})}),i}}else return n}function isAnchorLink(n){return!!getRawAttribute(n,"href")&&getRawAttribute(n,"href").indexOf("#")>=0}function getSwapSpecification(n,e){let t=e||getClosestAttributeValue(n,"hx-swap"),i={swapStyle:getInternalData(n).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(n).boosted&&!isAnchorLink(n)&&(i.show="top"),t){let s=splitOnWhitespace(t);if(s.length>0)for(let a=0;a0?r.join(":"):null;i.scroll=u,i.scrollTarget=o}else if(l.indexOf("show:")===0){var r=l.slice(5).split(":");let d=r.pop();var o=r.length>0?r.join(":"):null;i.show=d,i.showTarget=o}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);i.focusScroll=c=="true"}else a==0?i.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return i}function usesFormData(n){return getClosestAttributeValue(n,"hx-encoding")==="multipart/form-data"||matches(n,"form")&&getRawAttribute(n,"enctype")==="multipart/form-data"}function encodeParamsForBody(n,e,t){let i=null;return withExtensions(e,function(r){i==null&&(i=r.encodeParameters(n,t,e))}),i!=null?i:usesFormData(e)?overrideFormData(new FormData,formDataFromObject(t)):urlEncode(t)}function makeSettleInfo(n){return{tasks:[],elts:[n]}}function updateScrollState(n,e){let t=n[0],i=n[n.length-1];if(e.scroll){var r=null;e.scrollTarget&&(r=asElement(querySelectorExt(t,e.scrollTarget))),e.scroll==="top"&&(t||r)&&(r=r||t,r.scrollTop=0),e.scroll==="bottom"&&(i||r)&&(r=r||i,r.scrollTop=r.scrollHeight),typeof e.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,e.scroll)},0)}if(e.show){var r=null;if(e.showTarget){let s=e.showTarget;e.showTarget==="window"&&(s="body"),r=asElement(querySelectorExt(t,s))}e.show==="top"&&(t||r)&&(r=r||t,r.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),e.show==="bottom"&&(i||r)&&(r=r||i,r.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(n,e,t,i,r){if(i==null&&(i={}),n==null)return i;let o=getAttributeValue(n,e);if(o){let s=o.trim(),a=t;if(s==="unset")return null;s.indexOf("javascript:")===0?(s=s.slice(11),a=!0):s.indexOf("js:")===0&&(s=s.slice(3),a=!0),s.indexOf("{")!==0&&(s="{"+s+"}");let l;a?l=maybeEval(n,function(){return r?Function("event","return ("+s+")").call(n,r):Function("return ("+s+")").call(n)},{}):l=parseJSON(s);for(let c in l)l.hasOwnProperty(c)&&i[c]==null&&(i[c]=l[c])}return getValuesForElement(asElement(parentElt(n)),e,t,i,r)}function maybeEval(n,e,t){return htmx.config.allowEval?e():(triggerErrorEvent(n,"htmx:evalDisallowedError"),t)}function getHXVarsForElement(n,e,t){return getValuesForElement(n,"hx-vars",!0,t,e)}function getHXValsForElement(n,e,t){return getValuesForElement(n,"hx-vals",!1,t,e)}function getExpressionVars(n,e){return mergeObjects(getHXVarsForElement(n,e),getHXValsForElement(n,e))}function safelySetHeaderValue(n,e,t){if(t!==null)try{n.setRequestHeader(e,t)}catch(i){n.setRequestHeader(e,encodeURIComponent(t)),n.setRequestHeader(e+"-URI-AutoEncoded","true")}}function getPathFromResponse(n){if(n.responseURL)try{let e=new URL(n.responseURL);return e.pathname+e.search}catch(e){triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:n.responseURL})}}function hasHeader(n,e){return e.test(n.getAllResponseHeaders())}function ajaxHelper(n,e,t){if(n=n.toLowerCase(),t){if(t instanceof Element||typeof t=="string")return issueAjaxRequest(n,e,null,null,{targetOverride:resolveTarget(t)||DUMMY_ELT,returnPromise:!0});{let i=resolveTarget(t.target);return(t.target&&!i||t.source&&!i&&!resolveTarget(t.source))&&(i=DUMMY_ELT),issueAjaxRequest(n,e,resolveTarget(t.source),t.event,{handler:t.handler,headers:t.headers,values:t.values,targetOverride:i,swapOverride:t.swap,select:t.select,returnPromise:!0,push:t.push,replace:t.replace,selectOOB:t.selectOOB})}}else return issueAjaxRequest(n,e,null,null,{returnPromise:!0})}function hierarchyForElt(n){let e=[];for(;n;)e.push(n),n=n.parentElement;return e}function verifyPath(n,e,t){let i=new URL(e,location.protocol!=="about:"?location.href:window.origin),o=(location.protocol!=="about:"?location.origin:window.origin)===i.origin;return htmx.config.selfRequestsOnly&&!o?!1:triggerEvent(n,"htmx:validateUrl",mergeObjects({url:i,sameHost:o},t))}function formDataFromObject(n){if(n instanceof FormData)return n;let e=new FormData;for(let t in n)n.hasOwnProperty(t)&&(n[t]&&typeof n[t].forEach=="function"?n[t].forEach(function(i){e.append(t,i)}):typeof n[t]=="object"&&!(n[t]instanceof Blob)?e.append(t,JSON.stringify(n[t])):e.append(t,n[t]));return e}function formDataArrayProxy(n,e,t){return new Proxy(t,{get:function(i,r){return typeof r=="number"?i[r]:r==="length"?i.length:r==="push"?function(o){i.push(o),n.append(e,o)}:typeof i[r]=="function"?function(){i[r].apply(i,arguments),n.delete(e),i.forEach(function(o){n.append(e,o)})}:i[r]&&i[r].length===1?i[r][0]:i[r]},set:function(i,r,o){return i[r]=o,n.delete(e),i.forEach(function(s){n.append(e,s)}),!0}})}function formDataProxy(n){return new Proxy(n,{get:function(e,t){if(typeof t=="symbol"){let r=Reflect.get(e,t);return typeof r=="function"?function(){return r.apply(n,arguments)}:r}if(t==="toJSON")return()=>Object.fromEntries(n);if(t in e&&typeof e[t]=="function")return function(){return n[t].apply(n,arguments)};let i=n.getAll(t);if(i.length!==0)return i.length===1?i[0]:formDataArrayProxy(e,t,i)},set:function(e,t,i){return typeof t!="string"?!1:(e.delete(t),i&&typeof i.forEach=="function"?i.forEach(function(r){e.append(t,r)}):typeof i=="object"&&!(i instanceof Blob)?e.append(t,JSON.stringify(i)):e.append(t,i),!0)},deleteProperty:function(e,t){return typeof t=="string"&&e.delete(t),!0},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function issueAjaxRequest(n,e,t,i,r,o){let s=null,a=null;if(r=r!=null?r:{},r.returnPromise&&typeof Promise!="undefined")var l=new Promise(function(U,Z){s=U,a=Z});t==null&&(t=getDocument().body);let c=r.handler||handleAjaxResponse,u=r.select||null;if(!bodyContains(t))return maybeCall(s),l;let d=r.targetOverride||asElement(getTarget(t));if(d==null||d==DUMMY_ELT)return triggerErrorEvent(t,"htmx:targetError",{target:getClosestAttributeValue(t,"hx-target")}),maybeCall(a),l;let p=getInternalData(t),y=p.lastButtonClicked;if(y){let U=getRawAttribute(y,"formaction");U!=null&&(e=U);let Z=getRawAttribute(y,"formmethod");if(Z!=null)if(VERBS.includes(Z.toLowerCase()))n=Z;else return maybeCall(s),l}let m=getClosestAttributeValue(t,"hx-confirm");if(o===void 0&&triggerEvent(t,"htmx:confirm",{target:d,elt:t,path:e,verb:n,triggeringEvent:i,etc:r,issueRequest:function(oe){return issueAjaxRequest(n,e,t,i,r,!!oe)},question:m})===!1)return maybeCall(s),l;let v=t,w=getClosestAttributeValue(t,"hx-sync"),T=null,_=!1;if(w){let U=w.split(":"),Z=U[0].trim();if(Z==="this"?v=findThisElement(t,"hx-sync"):v=asElement(querySelectorExt(t,Z)),w=(U[1]||"drop").trim(),p=getInternalData(v),w==="drop"&&p.xhr&&p.abortable!==!0)return maybeCall(s),l;if(w==="abort"){if(p.xhr)return maybeCall(s),l;_=!0}else w==="replace"?triggerEvent(v,"htmx:abort"):w.indexOf("queue")===0&&(T=(w.split(" ")[1]||"last").trim())}if(p.xhr)if(p.abortable)triggerEvent(v,"htmx:abort");else{if(T==null){if(i){let U=getInternalData(i);U&&U.triggerSpec&&U.triggerSpec.queue&&(T=U.triggerSpec.queue)}T==null&&(T="last")}return p.queuedRequests==null&&(p.queuedRequests=[]),T==="first"&&p.queuedRequests.length===0?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):T==="all"?p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)}):T==="last"&&(p.queuedRequests=[],p.queuedRequests.push(function(){issueAjaxRequest(n,e,t,i,r)})),maybeCall(s),l}let S=new XMLHttpRequest;p.xhr=S,p.abortable=_;let A=function(){p.xhr=null,p.abortable=!1,p.queuedRequests!=null&&p.queuedRequests.length>0&&p.queuedRequests.shift()()},K=getClosestAttributeValue(t,"hx-prompt");if(K){var z=prompt(K);if(z===null||!triggerEvent(t,"htmx:prompt",{prompt:z,target:d}))return maybeCall(s),A(),l}if(m&&!o&&!confirm(m))return maybeCall(s),A(),l;let L=getHeaders(t,d,z);n!=="get"&&!usesFormData(t)&&(L["Content-Type"]="application/x-www-form-urlencoded"),r.headers&&(L=mergeObjects(L,r.headers));let H=getInputValues(t,n),N=H.errors,Y=H.formData;r.values&&overrideFormData(Y,formDataFromObject(r.values));let $=formDataFromObject(getExpressionVars(t,i)),ie=overrideFormData(Y,$),J=filterValues(ie,t);htmx.config.getCacheBusterParam&&n==="get"&&J.set("org.htmx.cache-buster",getRawAttribute(d,"id")||"true"),(e==null||e==="")&&(e=location.href);let Te=getValuesForElement(t,"hx-request"),Ce=getInternalData(t).boosted,se=htmx.config.methodsThatUseUrlParams.indexOf(n)>=0,ne={boosted:Ce,useUrlParams:se,formData:J,parameters:formDataProxy(J),unfilteredFormData:ie,unfilteredParameters:formDataProxy(ie),headers:L,elt:t,target:d,verb:n,errors:N,withCredentials:r.credentials||Te.credentials||htmx.config.withCredentials,timeout:r.timeout||Te.timeout||htmx.config.timeout,path:e,triggeringEvent:i};if(!triggerEvent(t,"htmx:configRequest",ne))return maybeCall(s),A(),l;if(e=ne.path,n=ne.verb,L=ne.headers,J=formDataFromObject(ne.parameters),N=ne.errors,se=ne.useUrlParams,N&&N.length>0)return triggerEvent(t,"htmx:validation:halted",ne),maybeCall(s),A(),l;let qe=e.split("#"),Re=qe[0],W=qe[1],M=e;if(se&&(M=Re,!J.keys().next().done&&(M.indexOf("?")<0?M+="?":M+="&",M+=urlEncode(J),W&&(M+="#"+W))),!verifyPath(t,M,ne))return triggerErrorEvent(t,"htmx:invalidPath",ne),maybeCall(a),A(),l;if(S.open(n.toUpperCase(),M,!0),S.overrideMimeType("text/html"),S.withCredentials=ne.withCredentials,S.timeout=ne.timeout,!Te.noHeaders){for(let U in L)if(L.hasOwnProperty(U)){let Z=L[U];safelySetHeaderValue(S,U,Z)}}let D={xhr:S,target:d,requestConfig:ne,etc:r,boosted:Ce,select:u,pathInfo:{requestPath:e,finalRequestPath:M,responsePath:null,anchor:W}};if(S.onload=function(){try{let U=hierarchyForElt(t);if(D.pathInfo.responsePath=getPathFromResponse(S),c(t,D),D.keepIndicators!==!0&&removeRequestIndicators(B,V),triggerEvent(t,"htmx:afterRequest",D),triggerEvent(t,"htmx:afterOnLoad",D),!bodyContains(t)){let Z=null;for(;U.length>0&&Z==null;){let oe=U.shift();bodyContains(oe)&&(Z=oe)}Z&&(triggerEvent(Z,"htmx:afterRequest",D),triggerEvent(Z,"htmx:afterOnLoad",D))}maybeCall(s)}catch(U){throw triggerErrorEvent(t,"htmx:onLoadError",mergeObjects({error:U},D)),U}finally{A()}},S.onerror=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:sendError",D),maybeCall(a),A()},S.onabort=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:sendAbort",D),maybeCall(a),A()},S.ontimeout=function(){removeRequestIndicators(B,V),triggerErrorEvent(t,"htmx:afterRequest",D),triggerErrorEvent(t,"htmx:timeout",D),maybeCall(a),A()},!triggerEvent(t,"htmx:beforeRequest",D))return maybeCall(s),A(),l;var B=addRequestIndicatorClasses(t),V=disableElements(t);forEach(["loadstart","loadend","progress","abort"],function(U){forEach([S,S.upload],function(Z){Z.addEventListener(U,function(oe){triggerEvent(t,"htmx:xhr:"+U,{lengthComputable:oe.lengthComputable,loaded:oe.loaded,total:oe.total})})})}),triggerEvent(t,"htmx:beforeSend",D);let q=se?null:encodeParamsForBody(S,t,J);return S.send(q),l}function determineHistoryUpdates(n,e){let t=e.xhr,i=null,r=null;if(hasHeader(t,/HX-Push:/i)?(i=t.getResponseHeader("HX-Push"),r="push"):hasHeader(t,/HX-Push-Url:/i)?(i=t.getResponseHeader("HX-Push-Url"),r="push"):hasHeader(t,/HX-Replace-Url:/i)&&(i=t.getResponseHeader("HX-Replace-Url"),r="replace"),i)return i==="false"?{}:{type:r,path:i};let o=e.pathInfo.finalRequestPath,s=e.pathInfo.responsePath,a=e.etc.push||getClosestAttributeValue(n,"hx-push-url"),l=e.etc.replace||getClosestAttributeValue(n,"hx-replace-url"),c=getInternalData(n).boosted,u=null,d=null;return a?(u="push",d=a):l?(u="replace",d=l):c&&(u="push",d=s||o),d?d==="false"?{}:(d==="true"&&(d=s||o),e.pathInfo.anchor&&d.indexOf("#")===-1&&(d=d+"#"+e.pathInfo.anchor),{type:u,path:d}):{}}function codeMatches(n,e){var t=new RegExp(n.code);return t.test(e.toString(10))}function resolveResponseHandling(n){for(var e=0;e.${e}{opacity:0;visibility: hidden} .${t} .${e}, .${t}.${e}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`)}}function getMetaConfig(){let n=getDocument().querySelector('meta[name="htmx-config"]');return n?parseJSON(n.content):null}function mergeMetaConfig(){let n=getMetaConfig();n&&(htmx.config=mergeObjects(htmx.config,n))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let n=getDocument().body;processNode(n);let e=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");n.addEventListener("htmx:abort",function(i){let r=i.detail.elt||i.target,o=getInternalData(r);o&&o.xhr&&o.xhr.abort()});let t=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(i){i.state&&i.state.htmx?(restoreHistory(),forEach(e,function(r){triggerEvent(r,"htmx:restored",{document:getDocument(),triggerEvent})})):t&&t(i)},getWindow().setTimeout(function(){triggerEvent(n,"htmx:load",{}),n=null},0)}),htmx})(),x_=Kp;function ko(n,e){n.split(/\s+/).forEach(t=>{e(t)})}var oi=class{constructor(){this._events={}}on(e,t){ko(e,i=>{let r=this._events[i]||[];r.push(t),this._events[i]=r})}off(e,t){var i=arguments.length;if(i===0){this._events={};return}ko(e,r=>{if(i===1){delete this._events[r];return}let o=this._events[r];o!==void 0&&(o.splice(o.indexOf(t),1),this._events[r]=o)})}trigger(e,...t){var i=this;ko(e,r=>{let o=i._events[r];o!==void 0&&o.forEach(s=>{s.apply(i,t)})})}};function No(n){return n.plugins={},class extends n{constructor(){super(...arguments),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(e,t){n.plugins[e]={name:e,fn:t}}initializePlugins(e){var t,i;let r=this,o=[];if(Array.isArray(e))e.forEach(s=>{typeof s=="string"?o.push(s):(r.plugins.settings[s.name]=s.options,o.push(s.name))});else if(e)for(t in e)e.hasOwnProperty(t)&&(r.plugins.settings[t]=e[t],o.push(t));for(;i=o.shift();)r.require(i)}loadPlugin(e){var t=this,i=t.plugins,r=n.plugins[e];if(!n.plugins.hasOwnProperty(e))throw new Error('Unable to find "'+e+'" plugin');i.requested[e]=!0,i.loaded[e]=r.fn.apply(t,[t.plugins.settings[e]||{}]),i.names.push(e)}require(e){var t=this,i=t.plugins;if(!t.plugins.loaded.hasOwnProperty(e)){if(i.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")');t.loadPlugin(e)}return i.loaded[e]}}}var si=n=>(n=n.filter(Boolean),n.length<2?n[0]||"":Qp(n)==1?"["+n.join("")+"]":"(?:"+n.join("|")+")"),Io=n=>{if(!Xp(n))return n.join("");let e="",t=0,i=()=>{t>1&&(e+="{"+t+"}")};return n.forEach((r,o)=>{if(r===n[o-1]){t++;return}i(),e+=r,t=1}),i(),e},Ro=n=>{let e=Array.from(n);return si(e)},Xp=n=>new Set(n).size!==n.length,Lt=n=>(n+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),Qp=n=>n.reduce((e,t)=>Math.max(e,Jp(t)),0),Jp=n=>Array.from(n).length;var Ho=n=>{if(n.length===1)return[[n]];let e=[],t=n.substring(1);return Ho(t).forEach(function(r){let o=r.slice(0);o[0]=n.charAt(0)+o[0],e.push(o),o=r.slice(0),o.unshift(n.charAt(0)),e.push(o)}),e};var Zp=[[0,65535]],em="[\u0300-\u036F\xB7\u02BE\u02BC]",or,Ba,tm=3,Po={},Pa={"/":"\u2044\u2215",0:"\u07C0",a:"\u2C65\u0250\u0251",aa:"\uA733",ae:"\xE6\u01FD\u01E3",ao:"\uA735",au:"\uA737",av:"\uA739\uA73B",ay:"\uA73D",b:"\u0180\u0253\u0183",c:"\uA73F\u0188\u023C\u2184",d:"\u0111\u0257\u0256\u1D05\u018C\uABB7\u0501\u0266",e:"\u025B\u01DD\u1D07\u0247",f:"\uA77C\u0192",g:"\u01E5\u0260\uA7A1\u1D79\uA77F\u0262",h:"\u0127\u2C68\u2C76\u0265",i:"\u0268\u0131",j:"\u0249\u0237",k:"\u0199\u2C6A\uA741\uA743\uA745\uA7A3",l:"\u0142\u019A\u026B\u2C61\uA749\uA747\uA781\u026D",m:"\u0271\u026F\u03FB",n:"\uA7A5\u019E\u0272\uA791\u1D0E\u043B\u0509",o:"\xF8\u01FF\u0254\u0275\uA74B\uA74D\u1D11",oe:"\u0153",oi:"\u01A3",oo:"\uA74F",ou:"\u0223",p:"\u01A5\u1D7D\uA751\uA753\uA755\u03C1",q:"\uA757\uA759\u024B",r:"\u024D\u027D\uA75B\uA7A7\uA783",s:"\xDF\u023F\uA7A9\uA785\u0282",t:"\u0167\u01AD\u0288\u2C66\uA787",th:"\xFE",tz:"\uA729",u:"\u0289",v:"\u028B\uA75F\u028C",vy:"\uA761",w:"\u2C73",y:"\u01B4\u024F\u1EFF",z:"\u01B6\u0225\u0240\u2C6C\uA763",hv:"\u0195"};for(let n in Pa){let e=Pa[n]||"";for(let t=0;t{or===void 0&&(or=am(n||Zp))},Fa=(n,e="NFKD")=>n.normalize(e),ai=n=>Array.from(n).reduce((e,t)=>e+rm(t),""),rm=n=>(n=Fa(n).toLowerCase().replace(nm,e=>Po[e]||""),Fa(n,"NFC"));function*om(n){for(let[e,t]of n)for(let i=e;i<=t;i++){let r=String.fromCharCode(i),o=ai(r);o!=r.toLowerCase()&&(o.length>tm||o.length!=0&&(yield{folded:o,composed:r,code_point:i}))}}var sm=n=>{let e={},t=(i,r)=>{let o=e[i]||new Set,s=new RegExp("^"+Ro(o)+"$","iu");r.match(s)||(o.add(Lt(r)),e[i]=o)};for(let i of om(n))t(i.folded,i.folded),t(i.folded,i.composed);return e},am=n=>{let e=sm(n),t={},i=[];for(let o in e){let s=e[o];s&&(t[o]=Ro(s)),o.length>1&&i.push(Lt(o))}i.sort((o,s)=>s.length-o.length);let r=si(i);return Ba=new RegExp("^"+r,"u"),t},lm=(n,e=1)=>{let t=0;return n=n.map(i=>(or[i]&&(t+=i.length),or[i]||i)),t>=e?Io(n):""},cm=(n,e=1)=>(e=Math.max(e,n.length-1),si(Ho(n).map(t=>lm(t,e)))),$a=(n,e=!0)=>{let t=n.length>1?1:0;return si(n.map(i=>{let r=[],o=e?i.length():i.length()-1;for(let s=0;s{for(let t of e){if(t.start!=n.start||t.end!=n.end||t.substrs.join("")!==n.substrs.join(""))continue;let i=n.parts,r=s=>{for(let a of i){if(a.start===s.start&&a.substr===s.substr)return!1;if(!(s.length==1||a.length==1)&&(s.starta.start||a.starts.start))return!0}return!1};if(!(t.parts.filter(r).length>0))return!0}return!1},sr=class n{constructor(){ee(this,"parts");ee(this,"substrs");ee(this,"start");ee(this,"end");this.parts=[],this.substrs=[],this.start=0,this.end=0}add(e){e&&(this.parts.push(e),this.substrs.push(e.substr),this.start=Math.min(e.start,this.start),this.end=Math.max(e.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(e,t){let i=new n,r=JSON.parse(JSON.stringify(this.parts)),o=r.pop();for(let l of r)i.add(l);let s=t.substr.substring(0,e-o.start),a=s.length;return i.add({start:o.start,end:o.start+a,length:a,substr:s}),i}},Va=n=>{im(),n=ai(n);let e="",t=[new sr];for(let i=0;i0){l=l.sort((u,d)=>u.length()-d.length());for(let u of l)um(u,t)||t.push(u);continue}if(i>0&&c.size==1&&!c.has("3")){e+=$a(t,!1);let u=new sr,d=t[0];d&&u.add(d.last()),t=[u]}}return e+=$a(t,!0),e};var za=(n,e)=>{if(n)return n[e]},ja=(n,e)=>{if(n){for(var t,i=e.split(".");(t=i.shift())&&(n=n[t]););return n}},ar=(n,e,t)=>{var i,r;return!n||(n=n+"",e.regex==null)||(r=n.search(e.regex),r===-1)?0:(i=e.string.length/n.length,r===0&&(i+=.5),i*t)},lr=(n,e)=>{var t=n[e];if(typeof t=="function")return t;t&&!Array.isArray(t)&&(n[e]=[t])},li=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Wa=(n,e)=>typeof n=="number"&&typeof e=="number"?n>e?1:ne?1:e>n?-1:0);var cr=class{constructor(e,t){ee(this,"items");ee(this,"settings");this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,i){if(!e||!e.length)return[];let r=[],o=e.split(/\s+/);var s;return i&&(s=new RegExp("^("+Object.keys(i).map(Lt).join("|")+"):(.*)$")),o.forEach(a=>{let l,c=null,u=null;s&&(l=a.match(s))&&(c=l[1],a=l[2]),a.length>0&&(this.settings.diacritics?u=Va(a)||null:u=Lt(a),u&&t&&(u="\\b"+u)),r.push({string:a,regex:u?new RegExp(u,"iu"):null,field:c})}),r}getScoreFunction(e,t){var i=this.prepareSearch(e,t);return this._getScoreFunction(i)}_getScoreFunction(e){let t=e.tokens,i=t.length;if(!i)return function(){return 0};let r=e.options.fields,o=e.weights,s=r.length,a=e.getAttrFn;if(!s)return function(){return 1};let l=(function(){return s===1?function(c,u){let d=r[0].field;return ar(a(u,d),c,o[d]||1)}:function(c,u){var d=0;if(c.field){let p=a(u,c.field);!c.regex&&p?d+=1/s:d+=ar(p,c,1)}else li(o,(p,y)=>{d+=ar(a(u,y),c,p)});return d/s}})();return i===1?function(c){return l(t[0],c)}:e.options.conjunction==="and"?function(c){var u,d=0;for(let p of t){if(u=l(p,c),u<=0)return 0;d+=u}return d/i}:function(c){var u=0;return li(t,d=>{u+=l(d,c)}),u/i}}getSortFunction(e,t){var i=this.prepareSearch(e,t);return this._getSortFunction(i)}_getSortFunction(e){var t,i=[];let r=this,o=e.options,s=!e.query&&o.sort_empty?o.sort_empty:o.sort;if(typeof s=="function")return s.bind(this);let a=function(c,u){return c==="$score"?u.score:e.getAttrFn(r.items[u.id],c)};if(s)for(let c of s)(e.query||c.field!=="$score")&&i.push(c);if(e.query){t=!0;for(let c of i)if(c.field==="$score"){t=!1;break}t&&i.unshift({field:"$score",direction:"desc"})}else i=i.filter(c=>c.field!=="$score");return i.length?function(c,u){var d,p;for(let y of i)if(p=y.field,d=(y.direction==="desc"?-1:1)*Wa(a(p,c),a(p,u)),d)return d;return 0}:null}prepareSearch(e,t){let i={};var r=Object.assign({},t);if(lr(r,"sort"),lr(r,"sort_empty"),r.fields){lr(r,"fields");let o=[];r.fields.forEach(s=>{typeof s=="string"&&(s={field:s,weight:1}),o.push(s),i[s.field]="weight"in s?s.weight:1}),r.fields=o}return{options:r,query:e.toLowerCase().trim(),tokens:this.tokenize(e,r.respect_word_boundaries,i),total:0,items:[],weights:i,getAttrFn:r.nesting?ja:za}}search(e,t){var i=this,r,o;o=this.prepareSearch(e,t),t=o.options,e=o.query;let s=t.score||i._getScoreFunction(o);e.length?li(i.items,(l,c)=>{r=s(l),(t.filter===!1||r>0)&&o.items.push({score:r,id:c})}):li(i.items,(l,c)=>{o.items.push({score:1,id:c})});let a=i._getSortFunction(o);return a&&o.items.sort(a),o.total=o.items.length,typeof t.limit=="number"&&(o.items=o.items.slice(0,t.limit)),o}};var We=n=>typeof n=="undefined"||n===null?null:ci(n),ci=n=>typeof n=="boolean"?n?"1":"0":n+"",ur=n=>(n+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),qa=(n,e)=>e>0?window.setTimeout(n,e):(n.call(null),null),Ua=(n,e)=>{var t;return function(i,r){var o=this;t&&(o.loading=Math.max(o.loading-1,0),clearTimeout(t)),t=setTimeout(function(){t=null,o.loadedSearches[i]=!0,n.call(o,i,r)},e)}},Fo=(n,e,t)=>{var i,r=n.trigger,o={};n.trigger=function(){var s=arguments[0];if(e.indexOf(s)!==-1)o[s]=arguments;else return r.apply(n,arguments)},t.apply(n,[]),n.trigger=r;for(i of e)i in o&&r.apply(n,o[i])},Ya=n=>({start:n.selectionStart||0,length:(n.selectionEnd||0)-(n.selectionStart||0)}),fe=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Se=(n,e,t,i)=>{n.addEventListener(e,t,i)},Mt=(n,e)=>{if(!e||!e[n])return!1;var t=(e.altKey?1:0)+(e.ctrlKey?1:0)+(e.shiftKey?1:0)+(e.metaKey?1:0);return t===1},dr=(n,e)=>{let t=n.getAttribute("id");return t||(n.setAttribute("id",e),e)},$o=n=>n.replace(/[\\"']/g,"\\$&"),kt=(n,e)=>{e&&n.append(e)},ge=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)};var nt=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Bo(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Bo=n=>typeof n=="string"&&n.indexOf("<")>-1,Ga=n=>n.replace(/['"\\]/g,"\\$&"),fr=(n,e)=>{var t=document.createEvent("HTMLEvents");t.initEvent(e,!0,!1),n.dispatchEvent(t)},ui=(n,e)=>{Object.assign(n.style,e)},Pe=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},gt=(n,...e)=>{var t=Ka(e);n=Xa(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Ka=n=>{var e=[];return ge(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Xa=n=>(Array.isArray(n)||(n=[n]),n),hr=(n,e,t)=>{if(!(t&&!t.contains(n)))for(;n&&n.matches;){if(n.matches(e))return n;n=n.parentNode}},Vo=(n,e=0)=>e>0?n[n.length-1]:n[0],Qa=n=>Object.keys(n).length===0,zo=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t},le=(n,e)=>{ge(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},di=(n,e)=>{n.parentNode&&n.parentNode.replaceChild(e,n)};var Ja=(n,e)=>{if(e===null)return;if(typeof e=="string"){if(!e.length)return;e=new RegExp(e,"i")}let t=o=>{var s=o.data.match(e);if(s&&o.data.length>0){var a=document.createElement("span");a.className="highlight";var l=o.splitText(s.index);l.splitText(s[0].length);var c=l.cloneNode(!0);return a.appendChild(c),di(l,a),1}return 0},i=o=>{o.nodeType===1&&o.childNodes&&!/(script|style)/i.test(o.tagName)&&(o.className!=="highlight"||o.tagName!=="SPAN")&&Array.from(o.childNodes).forEach(s=>{r(s)})},r=o=>o.nodeType===3?t(o):(i(o),0);r(n)},Za=n=>{var e=n.querySelectorAll("span.highlight");Array.prototype.forEach.call(e,function(t){var i=t.parentNode;i.replaceChild(t.firstChild,t),i.normalize()})};var dm=typeof navigator=="undefined"?!1:/Mac/.test(navigator.userAgent),fi=dm?"metaKey":"ctrlKey";var jo={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,refreshThrottle:300,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(n){return n.length>0},render:{}};function pr(n,e){var t=Object.assign({},jo,e),i=t.dataAttr,r=t.labelField,o=t.valueField,s=t.disabledField,a=t.optgroupField,l=t.optgroupLabelField,c=t.optgroupValueField,u=n.tagName.toLowerCase(),d=n.getAttribute("placeholder")||n.getAttribute("data-placeholder");if(!d&&!t.allowEmptyOption){let v=n.querySelector('option[value=""]');v&&(d=v.textContent)}var p={placeholder:d,options:[],optgroups:[],items:[],maxItems:null},y=()=>{var v,w=p.options,T={},_=1;let S=0;var A=L=>{var H=Object.assign({},L.dataset),N=i&&H[i];return typeof N=="string"&&N.length&&(H=Object.assign(H,JSON.parse(N))),H},K=(L,H)=>{var N=We(L.value);if(N!=null&&!(!N&&!t.allowEmptyOption)){if(T.hasOwnProperty(N)){if(H){var Y=T[N][a];Y?Array.isArray(Y)?Y.push(H):T[N][a]=[Y,H]:T[N][a]=H}}else{var $=A(L);$[r]=$[r]||L.textContent,$[o]=$[o]||N,$[s]=$[s]||L.disabled,$[a]=$[a]||H,$.$option=L,$.$order=$.$order||++S,T[N]=$,w.push($)}L.selected&&p.items.push(N)}},z=L=>{var H,N;N=A(L),N[l]=N[l]||L.getAttribute("label")||"",N[c]=N[c]||_++,N[s]=N[s]||L.disabled,N.$order=N.$order||++S,p.optgroups.push(N),H=N[c],ge(L.children,Y=>{K(Y,H)})};p.maxItems=n.hasAttribute("multiple")?null:1,ge(n.children,L=>{v=L.tagName.toLowerCase(),v==="optgroup"?z(L):v==="option"&&K(L)})},m=()=>{let v=n.getAttribute(i);if(v)p.options=JSON.parse(v),ge(p.options,T=>{p.items.push(T[o])});else{var w=n.value.trim()||"";if(!t.allowEmptyOption&&!w.length)return;let T=w.split(t.delimiter);ge(T,_=>{let S={};S[r]=_,S[o]=_,p.options.push(S)}),p.items=T}};return u==="select"?y():m(),Object.assign({},jo,p,e)}var nl=0,_e=class extends No(oi){constructor(e,t){super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isReadOnly=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],this.refreshTimeout=null,nl++;var i,r=nt(e);if(r.tomselect)throw new Error("Tom Select already initialized on this element");r.tomselect=this;var o=window.getComputedStyle&&window.getComputedStyle(r,null);i=o.getPropertyValue("direction");let s=pr(r,t);this.settings=s,this.input=r,this.tabIndex=r.tabIndex||0,this.is_select_tag=r.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(i),this.inputId=dr(r,"tomselect-"+nl),this.isRequired=r.required,this.sifter=new cr(this.options,{diacritics:s.diacritics}),s.mode=s.mode||(s.maxItems===1?"single":"multi"),typeof s.hideSelected!="boolean"&&(s.hideSelected=s.mode==="multi"),typeof s.hidePlaceholder!="boolean"&&(s.hidePlaceholder=s.mode!=="multi");var a=s.createFilter;typeof a!="function"&&(typeof a=="string"&&(a=new RegExp(a)),a instanceof RegExp?s.createFilter=w=>a.test(w):s.createFilter=w=>this.settings.duplicates||!this.options[w]),this.initializePlugins(s.plugins),this.setupCallbacks(),this.setupTemplates();let l=nt("
"),c=nt("
"),u=this._render("dropdown"),d=nt('
'),p=this.input.getAttribute("class")||"",y=s.mode;var m;if(Pe(l,s.wrapperClass,p,y),Pe(c,s.controlClass),kt(l,c),Pe(u,s.dropdownClass,y),s.copyClassesToDropdown&&Pe(u,p),Pe(d,s.dropdownContentClass),kt(u,d),nt(s.dropdownParent||l).appendChild(u),Bo(s.controlInput)){m=nt(s.controlInput);var v=["autocorrect","autocapitalize","autocomplete","spellcheck"];ge(v,w=>{r.getAttribute(w)&&le(m,{[w]:r.getAttribute(w)})}),m.tabIndex=-1,c.appendChild(m),this.focus_node=m}else s.controlInput?(m=nt(s.controlInput),this.focus_node=m):(m=nt(""),this.focus_node=c);this.wrapper=l,this.dropdown=u,this.dropdown_content=d,this.control=c,this.control_input=m,this.setup()}setup(){let e=this,t=e.settings,i=e.control_input,r=e.dropdown,o=e.dropdown_content,s=e.wrapper,a=e.control,l=e.input,c=e.focus_node,u={passive:!0},d=e.inputId+"-ts-dropdown";le(o,{id:d}),le(c,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":d});let p=dr(c,e.inputId+"-ts-control"),y="label[for='"+Ga(e.inputId)+"']",m=document.querySelector(y),v=e.focus.bind(e);if(m){Se(m,"click",v),le(m,{for:p});let _=dr(m,e.inputId+"-ts-label");le(c,{"aria-labelledby":_}),le(o,{"aria-labelledby":_})}if(s.style.width=l.style.width,e.plugins.names.length){let _="plugin-"+e.plugins.names.join(" plugin-");Pe([s,r],_)}(t.maxItems===null||t.maxItems>1)&&e.is_select_tag&&le(l,{multiple:"multiple"}),t.placeholder&&le(i,{placeholder:t.placeholder}),!t.splitOn&&t.delimiter&&(t.splitOn=new RegExp("\\s*"+Lt(t.delimiter)+"+\\s*")),t.load&&t.loadThrottle&&(t.load=Ua(t.load,t.loadThrottle)),Se(r,"mousemove",()=>{e.ignoreHover=!1}),Se(r,"mouseenter",_=>{var S=hr(_.target,"[data-selectable]",r);S&&e.onOptionHover(_,S)},{capture:!0}),Se(r,"click",_=>{let S=hr(_.target,"[data-selectable]");S&&(e.onOptionSelect(_,S),fe(_,!0))}),Se(a,"click",_=>{var S=hr(_.target,"[data-ts-item]",a);if(S&&e.onItemSelect(_,S)){fe(_,!0);return}i.value==""&&(e.onClick(),fe(_,!0))}),Se(c,"keydown",_=>e.onKeyDown(_)),Se(i,"keypress",_=>e.onKeyPress(_)),Se(i,"input",_=>e.onInput(_)),Se(c,"blur",_=>e.onBlur(_)),Se(c,"focus",_=>e.onFocus(_)),Se(i,"paste",_=>e.onPaste(_));let w=_=>{let S=_.composedPath()[0];if(!s.contains(S)&&!r.contains(S)){e.isFocused&&e.blur(),e.inputState();return}S==i&&e.isOpen?_.stopPropagation():fe(_,!0)},T=()=>{e.isOpen&&e.positionDropdown()};Se(document,"mousedown",w),Se(window,"scroll",T,u),Se(window,"resize",T,u),this._destroy=()=>{document.removeEventListener("mousedown",w),window.removeEventListener("scroll",T),window.removeEventListener("resize",T),m&&m.removeEventListener("click",v)},this.revertSettings={innerHTML:l.innerHTML,tabIndex:l.tabIndex},l.tabIndex=-1,l.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),t.items=[],delete t.optgroups,delete t.options,Se(l,"invalid",()=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())}),e.updateOriginalInput(),e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,l.disabled?e.disable():l.readOnly?e.setReadOnly(!0):e.enable(),e.on("change",this.onChange),Pe(l,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),t.preload===!0&&e.preload()}setupOptions(e=[],t=[]){this.addOptions(e),ge(t,i=>{this.registerOptionGroup(i)})}setupTemplates(){var e=this,t=e.settings.labelField,i=e.settings.optgroupLabelField,r={optgroup:o=>{let s=document.createElement("div");return s.className="optgroup",s.appendChild(o.options),s},optgroup_header:(o,s)=>'
'+s(o[i])+"
",option:(o,s)=>"
"+s(o[t])+"
",item:(o,s)=>"
"+s(o[t])+"
",option_create:(o,s)=>'
Add '+s(o.input)+"
",no_results:()=>'
No results found
',loading:()=>'
',not_loading:()=>{},dropdown:()=>"
"};e.settings.render=Object.assign({},r,e.settings.render)}setupCallbacks(){var e,t,i={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in i)t=this.settings[i[e]],t&&this.on(e,t)}sync(e=!0){let t=this,i=e?pr(t.input,{delimiter:t.settings.delimiter}):t.settings;t.setupOptions(i.options,i.optgroups),t.setValue(i.items||[],!0),t.lastQuery=null}onClick(){var e=this;if(e.activeItems.length>0){e.clearActiveItems(),e.focus();return}e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){fr(this.input,"input"),fr(this.input,"change")}onPaste(e){var t=this;if(t.isInputHidden||t.isLocked){fe(e);return}t.settings.splitOn&&setTimeout(()=>{var i=t.inputValue();if(i.match(t.settings.splitOn)){var r=i.trim().split(t.settings.splitOn);ge(r,o=>{We(o)&&(this.options[o]?t.addItem(o):t.createItem(o))})}},0)}onKeyPress(e){var t=this;if(t.isLocked){fe(e);return}var i=String.fromCharCode(e.keyCode||e.which);if(t.settings.create&&t.settings.mode==="multi"&&i===t.settings.delimiter){t.createItem(),fe(e);return}}onKeyDown(e){var t=this;if(t.ignoreHover=!0,t.isLocked){e.keyCode!==9&&fe(e);return}switch(e.keyCode){case 65:if(Mt(fi,e)&&t.control_input.value==""){fe(e),t.selectAll();return}break;case 27:t.isOpen&&(fe(e,!0),t.close()),t.clearActiveItems();return;case 40:if(!t.isOpen&&t.hasOptions)t.open();else if(t.activeOption){let i=t.getAdjacent(t.activeOption,1);i&&t.setActiveOption(i)}fe(e);return;case 38:if(t.activeOption){let i=t.getAdjacent(t.activeOption,-1);i&&t.setActiveOption(i)}fe(e);return;case 13:t.canSelect(t.activeOption)?(t.onOptionSelect(e,t.activeOption),fe(e)):t.settings.create&&t.createItem()?fe(e):document.activeElement==t.control_input&&t.isOpen&&fe(e);return;case 37:t.advanceSelection(-1,e);return;case 39:t.advanceSelection(1,e);return;case 9:t.settings.selectOnTab&&(t.canSelect(t.activeOption)&&(t.onOptionSelect(e,t.activeOption),fe(e)),t.settings.create&&t.createItem()&&fe(e));return;case 8:case 46:t.deleteSelection(e);return}t.isInputHidden&&!Mt(fi,e)&&fe(e)}onInput(e){if(this.isLocked)return;let t=this.inputValue();if(this.lastValue!==t){if(this.lastValue=t,t==""){this._onInput();return}this.refreshTimeout&&window.clearTimeout(this.refreshTimeout),this.refreshTimeout=qa(()=>{this.refreshTimeout=null,this._onInput()},this.settings.refreshThrottle)}}_onInput(){let e=this.lastValue;this.settings.shouldLoad.call(this,e)&&this.load(e),this.refreshOptions(),this.trigger("type",e)}onOptionHover(e,t){this.ignoreHover||this.setActiveOption(t,!1)}onFocus(e){var t=this,i=t.isFocused;if(t.isDisabled||t.isReadOnly){t.blur(),fe(e);return}t.ignoreFocus||(t.isFocused=!0,t.settings.preload==="focus"&&t.preload(),i||t.trigger("focus"),t.activeItems.length||(t.inputState(),t.refreshOptions(!!t.settings.openOnFocus)),t.refreshState())}onBlur(e){if(document.hasFocus()!==!1){var t=this;if(t.isFocused){t.isFocused=!1,t.ignoreFocus=!1;var i=()=>{t.close(),t.setActiveItem(),t.setCaret(t.items.length),t.trigger("blur")};t.settings.create&&t.settings.createOnBlur?t.createItem(null,i):i()}}}onOptionSelect(e,t){var i,r=this;t.parentElement&&t.parentElement.matches("[data-disabled]")||(t.classList.contains("create")?r.createItem(null,()=>{r.settings.closeAfterSelect&&r.close()}):(i=t.dataset.value,typeof i!="undefined"&&(r.lastQuery=null,r.addItem(i),r.settings.closeAfterSelect&&r.close(),!r.settings.hideSelected&&e.type&&/click/.test(e.type)&&r.setActiveOption(t))))}canSelect(e){return!!(this.isOpen&&e&&this.dropdown_content.contains(e))}onItemSelect(e,t){var i=this;return!i.isLocked&&i.settings.mode==="multi"?(fe(e),i.setActiveItem(t,e),!0):!1}canLoad(e){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(e))}load(e){let t=this;if(!t.canLoad(e))return;Pe(t.wrapper,t.settings.loadingClass),t.loading++;let i=t.loadCallback.bind(t);t.settings.load.call(t,e,i)}loadCallback(e,t){let i=this;i.loading=Math.max(i.loading-1,0),i.lastQuery=null,i.clearActiveOption(),i.setupOptions(e,t),i.refreshOptions(i.isFocused&&!i.isInputHidden),i.loading||gt(i.wrapper,i.settings.loadingClass),i.trigger("load",e,t)}preload(){var e=this.wrapper.classList;e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var t=this.control_input,i=t.value!==e;i&&(t.value=e,fr(t,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,t){var i=t?[]:["change"];Fo(this,i,()=>{this.clear(t),this.addItems(e,t)})}setMaxItems(e){e===0&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,t){var i=this,r,o,s,a,l,c;if(i.settings.mode!=="single"){if(!e){i.clearActiveItems(),i.isFocused&&i.inputState();return}if(r=t&&t.type.toLowerCase(),r==="click"&&Mt("shiftKey",t)&&i.activeItems.length){for(c=i.getLastActive(),s=Array.prototype.indexOf.call(i.control.children,c),a=Array.prototype.indexOf.call(i.control.children,e),s>a&&(l=s,s=a,a=l),o=s;o<=a;o++)e=i.control.children[o],i.activeItems.indexOf(e)===-1&&i.setActiveItemClass(e);fe(t)}else r==="click"&&Mt(fi,t)||r==="keydown"&&Mt("shiftKey",t)?e.classList.contains("active")?i.removeActiveItem(e):i.setActiveItemClass(e):(i.clearActiveItems(),i.setActiveItemClass(e));i.inputState(),i.isFocused||i.focus()}}setActiveItemClass(e){let t=this,i=t.control.querySelector(".last-active");i&>(i,"last-active"),Pe(e,"active last-active"),t.trigger("item_select",e),t.activeItems.indexOf(e)==-1&&t.activeItems.push(e)}removeActiveItem(e){var t=this.activeItems.indexOf(e);this.activeItems.splice(t,1),gt(e,"active")}clearActiveItems(){gt(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e,t=!0){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,le(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),le(e,{"aria-selected":"true"}),Pe(e,"active"),t&&this.scrollToOption(e)))}scrollToOption(e,t){if(!e)return;let i=this.dropdown_content,r=i.clientHeight,o=i.scrollTop||0,s=e.offsetHeight,a=e.getBoundingClientRect().top-i.getBoundingClientRect().top+o;a+s>r+o?this.scroll(a-r+s,t):a{e.setActiveItemClass(i)}))}inputState(){var e=this;e.control.contains(e.control_input)&&(le(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&le(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}inputValue(){return this.control_input.value.trim()}focus(){var e=this;e.isDisabled||e.isReadOnly||(e.ignoreFocus=!0,e.control_input.offsetWidth?e.control_input.focus():e.focus_node.focus(),setTimeout(()=>{e.ignoreFocus=!1,e.onFocus()},0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,t=e.sortField;return typeof e.sortField=="string"&&(t=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:t,nesting:e.nesting}}search(e){var t,i,r=this,o=this.getSearchOptions();if(r.settings.score&&(i=r.settings.score.call(r,e),typeof i!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return e!==r.lastQuery?(r.lastQuery=e,t=r.sifter.search(e,Object.assign(o,{score:i})),r.currentResults=t):t=Object.assign({},r.currentResults),r.settings.hideSelected&&(t.items=t.items.filter(s=>{let a=We(s.id);return!(a&&r.items.indexOf(a)!==-1)})),t}refreshOptions(e=!0){var t,i,r,o,s,a,l,c,u,d;let p={},y=[];var m=this,v=m.inputValue();let w=v===m.lastQuery||v==""&&m.lastQuery==null;var T=m.search(v),_=null,S=m.settings.shouldOpen||!1,A=m.dropdown_content;w&&(_=m.activeOption,_&&(u=_.closest("[data-group]"))),o=T.items.length,typeof m.settings.maxOptions=="number"&&(o=Math.min(o,m.settings.maxOptions)),o>0&&(S=!0);let K=(L,H)=>{let N=p[L];if(N!==void 0){let $=y[N];if($!==void 0)return[N,$.fragment]}let Y=document.createDocumentFragment();return N=y.length,y.push({fragment:Y,order:H,optgroup:L}),[N,Y]};for(t=0;t0&&($=$.cloneNode(!0),le($,{id:N.$id+"-clone-"+i,"aria-selected":null}),$.classList.add("ts-cloned"),gt($,"active"),m.activeOption&&m.activeOption.dataset.value==H&&u&&u.dataset.group===s.toString()&&(_=$)),Ce.appendChild($),s!=""&&(p[s]=Te)}}m.settings.lockOptgroupOrder&&y.sort((L,H)=>L.order-H.order),l=document.createDocumentFragment(),ge(y,L=>{let H=L.fragment,N=L.optgroup;if(!H||!H.children.length)return;let Y=m.optgroups[N];if(Y!==void 0){let $=document.createDocumentFragment(),ie=m.render("optgroup_header",Y);kt($,ie),kt($,H);let J=m.render("optgroup",{group:Y,options:$});kt(l,J)}else kt(l,H)}),A.innerHTML="",kt(A,l),m.settings.highlight&&(Za(A),T.query.length&&T.tokens.length&&ge(T.tokens,L=>{Ja(A,L.regex)}));var z=L=>{let H=m.render(L,{input:v});return H&&(S=!0,A.insertBefore(H,A.firstChild)),H};if(m.loading?z("loading"):m.settings.shouldLoad.call(m,v)?T.items.length===0&&z("no_results"):z("not_loading"),c=m.canCreate(v),c&&(d=z("option_create")),m.hasOptions=T.items.length>0||c,S){if(T.items.length>0){if(!_&&m.settings.mode==="single"&&m.items[0]!=null&&(_=m.getOption(m.items[0])),!A.contains(_)){let L=0;d&&!m.settings.addPrecedence&&(L=1),_=m.selectable()[L]}}else d&&(_=d);e&&!m.isOpen&&(m.open(),m.scrollToOption(_,"auto")),m.setActiveOption(_)}else m.clearActiveOption(),e&&m.isOpen&&m.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,t=!1){let i=this;if(Array.isArray(e))return i.addOptions(e,t),!1;let r=We(e[i.settings.valueField]);return r===null||i.options.hasOwnProperty(r)?!1:(e.$order=e.$order||++i.order,e.$id=i.inputId+"-opt-"+e.$order,i.options[r]=e,i.lastQuery=null,t&&(i.userOptions[r]=t,i.trigger("option_add",r,e)),r)}addOptions(e,t=!1){ge(e,i=>{this.addOption(i,t)})}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var t=We(e[this.settings.optgroupValueField]);return t===null?!1:(e.$order=e.$order||++this.order,this.optgroups[t]=e,t)}addOptionGroup(e,t){var i;t[this.settings.optgroupValueField]=e,(i=this.registerOptionGroup(t))&&this.trigger("optgroup_add",i,t)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,t){let i=this;var r,o;let s=We(e),a=We(t[i.settings.valueField]);if(s===null)return;let l=i.options[s];if(l==null)return;if(typeof a!="string")throw new Error("Value must be set in option data");let c=i.getOption(s),u=i.getItem(s);if(t.$order=t.$order||l.$order,delete i.options[s],i.uncacheValue(a),i.options[a]=t,c){if(i.dropdown_content.contains(c)){let d=i._render("option",t);di(c,d),i.activeOption===c&&i.setActiveOption(d)}c.remove()}u&&(o=i.items.indexOf(s),o!==-1&&i.items.splice(o,1,a),r=i._render("item",t),u.classList.contains("active")&&Pe(r,"active"),di(u,r)),i.lastQuery=null}removeOption(e,t){let i=this;e=ci(e),i.uncacheValue(e),delete i.userOptions[e],delete i.options[e],i.lastQuery=null,i.trigger("option_remove",e),i.removeItem(e,t)}clearOptions(e){let t=(e||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();let i={};ge(this.options,(r,o)=>{t(r,o)&&(i[o]=r)}),this.options=this.sifter.items=i,this.lastQuery=null,this.trigger("option_clear")}clearFilter(e,t){return this.items.indexOf(t)>=0}getOption(e,t=!1){let i=We(e);if(i===null)return null;let r=this.options[i];if(r!=null){if(r.$div)return r.$div;if(t)return this._render("option",r)}return null}getAdjacent(e,t,i="option"){var r=this,o;if(!e)return null;i=="item"?o=r.controlChildren():o=r.dropdown_content.querySelectorAll("[data-selectable]");for(let s=0;s0?o[s+1]:o[s-1];return null}getItem(e){if(typeof e=="object")return e;var t=We(e);return t!==null?this.control.querySelector(`[data-value="${$o(t)}"]`):null}addItems(e,t){var i=this,r=Array.isArray(e)?e:[e];r=r.filter(s=>i.items.indexOf(s)===-1);let o=r[r.length-1];r.forEach(s=>{i.isPending=s!==o,i.addItem(s,t)})}addItem(e,t){var i=t?[]:["change","dropdown_close"];Fo(this,i,()=>{var r,o;let s=this,a=s.settings.mode,l=We(e);if(!(l&&s.items.indexOf(l)!==-1&&(a==="single"&&s.close(),a==="single"||!s.settings.duplicates))&&!(l===null||!s.options.hasOwnProperty(l))&&(a==="single"&&s.clear(t),!(a==="multi"&&s.isFull()))){if(r=s._render("item",s.options[l]),s.control.contains(r)&&(r=r.cloneNode(!0)),o=s.isFull(),s.items.splice(s.caretPos,0,l),s.insertAtCaret(r),s.isSetup){if(!s.isPending&&s.settings.hideSelected){let c=s.getOption(l),u=s.getAdjacent(c,1);u&&s.setActiveOption(u)}!s.isPending&&!s.settings.closeAfterSelect&&s.refreshOptions(s.isFocused&&a!=="single"),s.settings.closeAfterSelect!=!1&&s.isFull()?s.close():s.isPending||s.positionDropdown(),s.trigger("item_add",l,r),s.isPending||s.updateOriginalInput({silent:t})}(!s.isPending||!o&&s.isFull())&&(s.inputState(),s.refreshState())}})}removeItem(e=null,t){let i=this;if(e=i.getItem(e),!e)return;var r,o;let s=e.dataset.value;r=zo(e),e.remove(),e.classList.contains("active")&&(o=i.activeItems.indexOf(e),i.activeItems.splice(o,1),gt(e,"active")),i.items.splice(r,1),i.lastQuery=null,!i.settings.persist&&i.userOptions.hasOwnProperty(s)&&i.removeOption(s,t),r{}){arguments.length===3&&(t=arguments[2]),typeof t!="function"&&(t=()=>{});var i=this,r=i.caretPos,o;if(e=e||i.inputValue(),!i.canCreate(e))return t(),!1;i.lock();var s=!1,a=l=>{if(i.unlock(),!l||typeof l!="object")return t();var c=We(l[i.settings.valueField]);if(typeof c!="string")return t();i.setTextboxValue(),i.addOption(l,!0),i.setCaret(r),i.addItem(c),t(l),s=!0};return typeof i.settings.create=="function"?o=i.settings.create.call(this,e,a):o={[i.settings.labelField]:e,[i.settings.valueField]:e},s||a(o),!0}refreshItems(){var e=this;e.lastQuery=null,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){let e=this;e.refreshValidityState();let t=e.isFull(),i=e.isLocked;e.wrapper.classList.toggle("rtl",e.rtl);let r=e.wrapper.classList;r.toggle("focus",e.isFocused),r.toggle("disabled",e.isDisabled),r.toggle("readonly",e.isReadOnly),r.toggle("required",e.isRequired),r.toggle("invalid",!e.isValid),r.toggle("locked",i),r.toggle("full",t),r.toggle("input-active",e.isFocused&&!e.isInputHidden),r.toggle("dropdown-active",e.isOpen),r.toggle("has-options",Qa(e.options)),r.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this;e.input.validity&&(e.isValid=e.input.validity.valid,e.isInvalid=!e.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){let t=this;var i,r;let o=t.input.querySelector('option[value=""]');if(t.is_select_tag){let l=function(c,u,d){return c||(c=nt('")),c!=o&&t.input.append(c),s.push(c),(c!=o||a>0)&&(c.selected=!0),c},s=[],a=t.input.querySelectorAll("option:checked").length;t.input.querySelectorAll("option:checked").forEach(c=>{c.selected=!1}),t.items.length==0&&t.settings.mode=="single"?l(o,"",""):t.items.forEach(c=>{if(i=t.options[c],r=i[t.settings.labelField]||"",s.includes(i.$option)){let u=t.input.querySelector(`option[value="${$o(c)}"]:not(:checked)`);l(u,c,r)}else i.$option=l(i.$option,c,r)})}else t.input.value=t.getValue();t.isSetup&&(e.silent||t.trigger("change",t.getValue()))}open(){var e=this;e.isLocked||e.isOpen||e.settings.mode==="multi"&&e.isFull()||(e.isOpen=!0,le(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),ui(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),ui(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var t=this,i=t.isOpen;e&&(t.setTextboxValue(),t.settings.mode==="single"&&t.items.length&&t.inputState()),t.isOpen=!1,le(t.focus_node,{"aria-expanded":"false"}),ui(t.dropdown,{display:"none"}),t.settings.hideSelected&&t.clearActiveOption(),t.refreshState(),i&&t.trigger("dropdown_close",t.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var e=this.control,t=e.getBoundingClientRect(),i=e.offsetHeight+t.top+window.scrollY,r=t.left+window.scrollX;ui(this.dropdown,{width:t.width+"px",top:i+"px",left:r+"px"})}}clear(e){var t=this;if(t.items.length){var i=t.controlChildren();ge(i,r=>{t.removeItem(r,!0)}),t.inputState(),e||t.updateOriginalInput(),t.trigger("clear")}}insertAtCaret(e){let t=this,i=t.caretPos,r=t.control;r.insertBefore(e,r.children[i]||null),t.setCaret(i+1)}deleteSelection(e){var t,i,r,o,s=this;t=e&&e.keyCode===8?-1:1,i=Ya(s.control_input);let a=[];if(s.activeItems.length)o=Vo(s.activeItems,t),r=zo(o),t>0&&r++,ge(s.activeItems,l=>a.push(l));else if((s.isFocused||s.settings.mode==="single")&&s.items.length){let l=s.controlChildren(),c;t<0&&i.start===0&&i.length===0?c=l[s.caretPos-1]:t>0&&i.start===s.inputValue().length&&(c=l[s.caretPos]),c!==void 0&&a.push(c)}if(!s.shouldDelete(a,e))return!1;for(fe(e,!0),typeof r!="undefined"&&s.setCaret(r);a.length;)s.removeItem(a.pop());return s.inputState(),s.positionDropdown(),s.refreshOptions(!1),!0}shouldDelete(e,t){let i=e.map(r=>r.dataset.value);return!(!i.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete(i,t)===!1)}advanceSelection(e,t){var i,r,o=this;o.rtl&&(e*=-1),!o.inputValue().length&&(Mt(fi,t)||Mt("shiftKey",t)?(i=o.getLastActive(e),i?i.classList.contains("active")?r=o.getAdjacent(i,e,"item"):r=i:e>0?r=o.control_input.nextElementSibling:r=o.control_input.previousElementSibling,r&&(r.classList.contains("active")&&o.removeActiveItem(i),o.setActiveItemClass(r))):o.moveCaret(e))}moveCaret(e){}getLastActive(e){let t=this.control.querySelector(".last-active");if(t)return t;var i=this.control.querySelectorAll(".active");if(i)return Vo(i,e)}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.setLocked(!0)}unlock(){this.setLocked(!1)}setLocked(e=this.isReadOnly||this.isDisabled){this.isLocked=e,this.refreshState()}disable(){this.setDisabled(!0),this.close()}enable(){this.setDisabled(!1)}setDisabled(e){this.focus_node.tabIndex=e?-1:this.tabIndex,this.isDisabled=e,this.input.disabled=e,this.control_input.disabled=e,this.setLocked()}setReadOnly(e){this.isReadOnly=e,this.input.readOnly=e,this.control_input.readOnly=e,this.setLocked()}destroy(){var e=this,t=e.revertSettings;e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=t.innerHTML,e.input.tabIndex=t.tabIndex,gt(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,t){var i,r;let o=this;if(typeof this.settings.render[e]!="function"||(r=o.settings.render[e].call(this,t,ur),!r))return null;if(r=nt(r),e==="option"||e==="option_create"?t[o.settings.disabledField]?le(r,{"aria-disabled":"true"}):le(r,{"data-selectable":""}):e==="optgroup"&&(i=t.group[o.settings.optgroupValueField],le(r,{"data-group":i}),t.group[o.settings.disabledField]&&le(r,{"data-disabled":""})),e==="option"||e==="item"){let s=ci(t[o.settings.valueField]);le(r,{"data-value":s}),e==="item"?(Pe(r,o.settings.itemClass),le(r,{"data-ts-item":""})):(Pe(r,o.settings.optionClass),le(r,{role:"option",id:t.$id}),t.$div=r,o.options[s]=t)}return r}_render(e,t){let i=this.render(e,t);if(i==null)throw"HTMLElement expected";return i}clearCache(){ge(this.options,e=>{e.$div&&(e.$div.remove(),delete e.$div)})}uncacheValue(e){let t=this.getOption(e);t&&t.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,t,i){var r=this,o=r[t];r[t]=function(){var s,a;return e==="after"&&(s=o.apply(r,arguments)),a=i.apply(r,arguments),e==="instead"?a:(e==="before"&&(s=o.apply(r,arguments)),s)}}};var _m=(n,e,t,i)=>{n.addEventListener(e,t,i)};function il(){_m(this.input,"change",()=>{this.sync()})}var wm=n=>typeof n=="undefined"||n===null?null:xm(n),xm=n=>typeof n=="boolean"?n?"1":"0":n+"",rl=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Tm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Cm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Cm=n=>typeof n=="string"&&n.indexOf("<")>-1;function ol(n){var e=this,t=e.onOptionSelect;e.settings.hideSelected=!1;let i=Object.assign({className:"tomselect-checkbox",checkedClassNames:void 0,uncheckedClassNames:void 0},n);var r=function(a,l){l?(a.checked=!0,i.uncheckedClassNames&&a.classList.remove(...i.uncheckedClassNames),i.checkedClassNames&&a.classList.add(...i.checkedClassNames)):(a.checked=!1,i.checkedClassNames&&a.classList.remove(...i.checkedClassNames),i.uncheckedClassNames&&a.classList.add(...i.uncheckedClassNames))},o=function(a){setTimeout(()=>{var l=a.querySelector("input."+i.className);l instanceof HTMLInputElement&&r(l,a.classList.contains("selected"))},1)};e.hook("after","setupTemplates",()=>{var s=e.settings.render.option;e.settings.render.option=(a,l)=>{var c=Tm(s.call(e,a,l)),u=document.createElement("input");i.className&&u.classList.add(i.className),u.addEventListener("click",function(p){rl(p)}),u.type="checkbox";let d=wm(a[e.settings.valueField]);return r(u,!!(d&&e.items.indexOf(d)>-1)),c.prepend(u),c}}),e.on("item_remove",s=>{var a=e.getOption(s);a&&(a.classList.remove("selected"),o(a))}),e.on("item_add",s=>{var a=e.getOption(s);a&&o(a)}),e.hook("instead","onOptionSelect",(s,a)=>{if(a.classList.contains("selected")){a.classList.remove("selected"),e.removeItem(a.dataset.value),e.refreshOptions(),rl(s,!0);return}t.call(e,s,a),o(a)})}var Am=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Sm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Sm=n=>typeof n=="string"&&n.indexOf("<")>-1;function sl(n){let e=this,t=Object.assign({className:"clear-button",title:"Clear All",html:i=>`
`},n);e.on("initialize",()=>{var i=Am(t.html(t));i.addEventListener("click",r=>{e.isLocked||(e.clear(),e.settings.mode==="single"&&e.settings.allowEmptyOption&&e.addItem(""),r.preventDefault(),r.stopPropagation())}),e.control.appendChild(i)})}var Dm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Dn=(n,e,t,i)=>{n.addEventListener(e,t,i)},Om=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Lm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Mm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Mm=n=>typeof n=="string"&&n.indexOf("<")>-1,km=(n,e)=>{Om(e,(t,i)=>{t==null?n.removeAttribute(i):n.setAttribute(i,""+t)})},Nm=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n.nextSibling)},Im=(n,e)=>{var t;(t=n.parentNode)==null||t.insertBefore(e,n)},Rm=(n,e)=>{do{var t;if(e=(t=e)==null?void 0:t.previousElementSibling,n==e)return!0}while(e&&e.previousElementSibling);return!1};function al(){var n=this;if(n.settings.mode!=="multi")return;var e=n.lock,t=n.unlock;let i=!0,r;n.hook("after","setupTemplates",()=>{var o=n.settings.render.item;n.settings.render.item=(s,a)=>{let l=Lm(o.call(n,s,a));km(l,{draggable:"true"});let c=v=>{i||Dm(v),v.stopPropagation()},u=v=>{r=l,setTimeout(()=>{l.classList.add("ts-dragging")},0)},d=v=>{v.preventDefault(),l.classList.add("ts-drag-over"),y(l,r)},p=()=>{l.classList.remove("ts-drag-over")},y=(v,w)=>{w!==void 0&&(Rm(w,l)?Nm(v,w):Im(v,w))},m=()=>{var v;document.querySelectorAll(".ts-drag-over").forEach(T=>T.classList.remove("ts-drag-over")),(v=r)==null||v.classList.remove("ts-dragging"),r=void 0;var w=[];n.control.querySelectorAll("[data-value]").forEach(T=>{if(T.dataset.value){let _=T.dataset.value;_&&w.push(_)}}),n.setValue(w)};return Dn(l,"mousedown",c),Dn(l,"dragstart",u),Dn(l,"dragenter",d),Dn(l,"dragover",d),Dn(l,"dragleave",p),Dn(l,"dragend",m),l}}),n.hook("instead","lock",()=>(i=!1,e.call(n))),n.hook("instead","unlock",()=>(i=!0,t.call(n)))}var Hm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},Pm=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Fm(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Fm=n=>typeof n=="string"&&n.indexOf("<")>-1;function ll(n){let e=this,t=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:i=>'
'+i.title+'×
'},n);e.on("initialize",()=>{var i=Pm(t.html(t)),r=i.querySelector("."+t.closeClass);r&&r.addEventListener("click",o=>{Hm(o,!0),e.close()}),e.dropdown.insertBefore(i,e.dropdown.firstChild)})}var $m=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},Bm=(n,...e)=>{var t=Vm(e);n=zm(n),n.map(i=>{t.map(r=>{i.classList.remove(r)})})},Vm=n=>{var e=[];return $m(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},zm=n=>(Array.isArray(n)||(n=[n]),n),jm=(n,e)=>{if(!n)return-1;e=e||n.nodeName;for(var t=0;n=n.previousElementSibling;)n.matches(e)&&t++;return t};function cl(){var n=this;n.hook("instead","setCaret",e=>{n.settings.mode==="single"||!n.control.contains(n.control_input)?e=n.items.length:(e=Math.max(0,Math.min(n.items.length,e)),e!=n.caretPos&&!n.isPending&&n.controlChildren().forEach((t,i)=>{i{if(!n.isFocused)return;let t=n.getLastActive(e);if(t){let i=jm(t);n.setCaret(e>0?i+1:i),n.setActiveItem(),Bm(t,"last-active")}else n.setCaret(n.caretPos+e)})}var Wm=(n,e=!1)=>{n&&(n.preventDefault(),e&&n.stopPropagation())},qm=(n,e,t,i)=>{n.addEventListener(e,t,i)},Um=(n,e)=>{if(Array.isArray(n))n.forEach(e);else for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},ul=n=>{if(n.jquery)return n[0];if(n instanceof HTMLElement)return n;if(Ym(n)){var e=document.createElement("template");return e.innerHTML=n.trim(),e.content.firstChild}return document.querySelector(n)},Ym=n=>typeof n=="string"&&n.indexOf("<")>-1,Gm=(n,...e)=>{var t=Km(e);n=Xm(n),n.map(i=>{t.map(r=>{i.classList.add(r)})})},Km=n=>{var e=[];return Um(n,t=>{typeof t=="string"&&(t=t.trim().split(/[\t\n\f\r\s]/)),Array.isArray(t)&&(e=e.concat(t))}),e.filter(Boolean)},Xm=n=>(Array.isArray(n)||(n=[n]),n);function dl(){let n=this;n.settings.shouldOpen=!0,n.hook("before","setup",()=>{n.focus_node=n.control,Gm(n.control_input,"dropdown-input");let e=ul('