mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-13 16:47:34 -06:00
9856 Replace graphene with Strawberry (#15141)
* 9856 base strawberry integration * 9856 user and group * 9856 user and circuits base * 9856 extras and mixins * 9856 fk * 9856 update strawberry version * 9856 update imports * 9856 compatability fixes * 9856 compatability fixes * 9856 update strawberry types * 9856 update strawberry types * 9856 core schema * 9856 dcim schema * 9856 extras schema * 9856 ipam and tenant schema * 9856 virtualization, vpn, wireless schema * 9856 fix old decorator * 9856 cleanup * 9856 cleanup * 9856 fixes to circuits type specifiers * 9856 fixes to circuits type specifiers * 9856 update types * 9856 GFK working * 9856 GFK working * 9856 _name * 9856 misc fixes * 9856 type updates * 9856 _name to types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 update types * 9856 GraphQLView * 9856 GraphQLView * 9856 fix OrganizationalObjectType * 9856 single item query for schema * 9856 circuits graphql tests working * 9856 test fixes * 9856 test fixes * 9856 test fixes * 9856 test fix vpn * 9856 test fixes * 9856 test fixes * 9856 test fixes * 9856 circuits test sans DjangoModelType * 9856 core test sans DjangoModelType * 9856 temp checkin * 9856 fix extas FK * 9856 fix tenancy FK * 9856 fix virtualization FK * 9856 fix vpn FK * 9856 fix wireless FK * 9856 fix ipam FK * 9856 fix partial dcim FK * 9856 fix dcim FK * 9856 fix virtualization FK * 9856 fix tests / remove debug code * 9856 fix test imagefield * 9856 cleanup graphene * 9856 fix plugin schema * 9856 fix requirements * 9856 fix requirements * 9856 fix docs * 9856 fix docs * 9856 temp fix tests * 9856 first filterset * 9856 first filterset * 9856 fix tests * 9856 fix tests * 9856 working auto filter generation * 9856 filter types * 9856 filter types * 9856 filter types * 9856 fix graphiql test * 9856 fix counter fields and merge feature * 9856 temp fix tests * 9856 fix tests * 9856 fix tenancy, ipam filter definitions * 9856 cleanup * 9856 cleanup * 9856 cleanup * 9856 review changes * 9856 review changes * 9856 review changes * 9856 fix base-requirements * 9856 add wrapper to graphiql * 9856 remove old graphiql debug toolbar * 9856 review changes * 9856 update strawberry * 9856 remove superfluous check --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
parent
423c9813a2
commit
45c99e4477
@ -14,10 +14,6 @@ django-debug-toolbar
|
||||
# https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
|
||||
django-filter
|
||||
|
||||
# Django debug toolbar extension with support for GraphiQL
|
||||
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
|
||||
django-graphiql-debug-toolbar
|
||||
|
||||
# HTMX utilities for Django
|
||||
# https://django-htmx.readthedocs.io/en/latest/changelog.html
|
||||
django-htmx
|
||||
@ -75,11 +71,6 @@ drf-spectacular-sidecar
|
||||
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
|
||||
feedparser
|
||||
|
||||
# Django wrapper for Graphene (GraphQL support)
|
||||
# https://github.com/graphql-python/graphene-django/releases
|
||||
# Pinned to v3.0.0 for GraphiQL UI issue (see #12762)
|
||||
graphene_django==3.0.0
|
||||
|
||||
# WSGI HTTP server
|
||||
# https://docs.gunicorn.org/en/latest/news.html
|
||||
gunicorn
|
||||
@ -136,8 +127,16 @@ social-auth-core
|
||||
# https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
|
||||
social-auth-app-django
|
||||
|
||||
# Strawberry GraphQL
|
||||
# https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md
|
||||
strawberry-graphql
|
||||
|
||||
# Strawberry GraphQL Django extension
|
||||
# https://github.com/strawberry-graphql/strawberry-django/blob/main/CHANGELOG.md
|
||||
strawberry-graphql-django
|
||||
|
||||
# SVG image rendering (used for rack elevations)
|
||||
# hhttps://github.com/mozman/svgwrite/blob/master/NEWS.rst
|
||||
# https://github.com/mozman/svgwrite/blob/master/NEWS.rst
|
||||
svgwrite
|
||||
|
||||
# Tabular dataset library (for table-based exports)
|
||||
|
@ -8,23 +8,32 @@ A plugin can extend NetBox's GraphQL API by registering its own schema class. By
|
||||
|
||||
```python
|
||||
# graphql.py
|
||||
import graphene
|
||||
from netbox.graphql.types import NetBoxObjectType
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from . import filtersets, models
|
||||
from typing import List
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
class MyModelType(NetBoxObjectType):
|
||||
from . import models
|
||||
|
||||
class Meta:
|
||||
model = models.MyModel
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.MyModelFilterSet
|
||||
|
||||
class MyQuery(graphene.ObjectType):
|
||||
mymodel = ObjectField(MyModelType)
|
||||
mymodel_list = ObjectListField(MyModelType)
|
||||
@strawberry_django.type(
|
||||
models.MyModel,
|
||||
fields='__all__',
|
||||
)
|
||||
class MyModelType:
|
||||
pass
|
||||
|
||||
schema = MyQuery
|
||||
|
||||
@strawberry.type
|
||||
class MyQuery:
|
||||
@strawberry.field
|
||||
def dummymodel(self, id: int) -> DummyModelType:
|
||||
return None
|
||||
dummymodel_list: List[DummyModelType] = strawberry_django.field()
|
||||
|
||||
|
||||
schema = [
|
||||
MyQuery,
|
||||
]
|
||||
```
|
||||
|
||||
## GraphQL Objects
|
||||
@ -38,15 +47,3 @@ NetBox provides two object type classes for use by plugins.
|
||||
::: netbox.graphql.types.NetBoxObjectType
|
||||
options:
|
||||
members: false
|
||||
|
||||
## GraphQL Fields
|
||||
|
||||
NetBox provides two field classes for use by plugins.
|
||||
|
||||
::: netbox.graphql.fields.ObjectField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: netbox.graphql.fields.ObjectListField
|
||||
options:
|
||||
members: false
|
||||
|
50
netbox/circuits/graphql/filters.py
Normal file
50
netbox/circuits/graphql/filters.py
Normal file
@ -0,0 +1,50 @@
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from circuits import filtersets, models
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationFilter',
|
||||
'CircuitFilter',
|
||||
'CircuitTypeFilter',
|
||||
'ProviderFilter',
|
||||
'ProviderAccountFilter',
|
||||
'ProviderNetworkFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CircuitTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitTerminationFilterSet)
|
||||
class CircuitTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Circuit, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitFilterSet)
|
||||
class CircuitFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CircuitType, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitTypeFilterSet)
|
||||
class CircuitTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Provider, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderFilterSet)
|
||||
class ProviderFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ProviderAccount, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderAccountFilterSet)
|
||||
class ProviderAccountFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ProviderNetwork, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
|
||||
class ProviderNetworkFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,41 +1,40 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class CircuitsQuery(graphene.ObjectType):
|
||||
circuit = ObjectField(CircuitType)
|
||||
circuit_list = ObjectListField(CircuitType)
|
||||
@strawberry.type
|
||||
class CircuitsQuery:
|
||||
@strawberry.field
|
||||
def circuit(self, id: int) -> CircuitType:
|
||||
return models.Circuit.objects.get(pk=id)
|
||||
circuit_list: List[CircuitType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Circuit.objects.all(), info)
|
||||
@strawberry.field
|
||||
def circuit_termination(self, id: int) -> CircuitTerminationType:
|
||||
return models.CircuitTermination.objects.get(pk=id)
|
||||
circuit_termination_list: List[CircuitTerminationType] = strawberry_django.field()
|
||||
|
||||
circuit_termination = ObjectField(CircuitTerminationType)
|
||||
circuit_termination_list = ObjectListField(CircuitTerminationType)
|
||||
@strawberry.field
|
||||
def circuit_type(self, id: int) -> CircuitTypeType:
|
||||
return models.CircuitType.objects.get(pk=id)
|
||||
circuit_type_list: List[CircuitTypeType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_termination_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CircuitTermination.objects.all(), info)
|
||||
@strawberry.field
|
||||
def provider(self, id: int) -> ProviderType:
|
||||
return models.Provider.objects.get(pk=id)
|
||||
provider_list: List[ProviderType] = strawberry_django.field()
|
||||
|
||||
circuit_type = ObjectField(CircuitTypeType)
|
||||
circuit_type_list = ObjectListField(CircuitTypeType)
|
||||
@strawberry.field
|
||||
def provider_account(self, id: int) -> ProviderAccountType:
|
||||
return models.ProviderAccount.objects.get(pk=id)
|
||||
provider_account_list: List[ProviderAccountType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CircuitType.objects.all(), info)
|
||||
|
||||
provider = ObjectField(ProviderType)
|
||||
provider_list = ObjectListField(ProviderType)
|
||||
|
||||
def resolve_provider_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Provider.objects.all(), info)
|
||||
|
||||
provider_account = ObjectField(ProviderAccountType)
|
||||
provider_account_list = ObjectListField(ProviderAccountType)
|
||||
|
||||
provider_network = ObjectField(ProviderNetworkType)
|
||||
provider_network_list = ObjectListField(ProviderNetworkType)
|
||||
|
||||
def resolve_provider_network_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ProviderNetwork.objects.all(), info)
|
||||
@strawberry.field
|
||||
def provider_network(self, id: int) -> ProviderNetworkType:
|
||||
return models.ProviderNetwork.objects.get(pk=id)
|
||||
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
|
||||
|
@ -1,9 +1,14 @@
|
||||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
from circuits import filtersets, models
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits import models
|
||||
from dcim.graphql.mixins import CabledObjectMixin
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin, ContactsMixin
|
||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import NetBoxObjectType, ObjectType, OrganizationalObjectType
|
||||
from tenancy.graphql.types import TenantType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationType',
|
||||
@ -15,48 +20,93 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitTermination
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitTerminationFilterSet
|
||||
|
||||
|
||||
class CircuitType(NetBoxObjectType, ContactsMixin):
|
||||
class Meta:
|
||||
model = models.Circuit
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitFilterSet
|
||||
|
||||
|
||||
class CircuitTypeType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitType
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitTypeFilterSet
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Provider,
|
||||
fields='__all__',
|
||||
filters=ProviderFilter
|
||||
)
|
||||
class ProviderType(NetBoxObjectType, ContactsMixin):
|
||||
|
||||
class Meta:
|
||||
model = models.Provider
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderFilterSet
|
||||
@strawberry_django.field
|
||||
def networks(self) -> List[Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.networks.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def accounts(self) -> List[Annotated["ProviderAccountType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.accounts.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ProviderAccount,
|
||||
fields='__all__',
|
||||
filters=ProviderAccountFilter
|
||||
)
|
||||
class ProviderAccountType(NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.ProviderAccount
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderAccountFilterSet
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ProviderNetwork,
|
||||
fields='__all__',
|
||||
filters=ProviderNetworkFilter
|
||||
)
|
||||
class ProviderNetworkType(NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.ProviderNetwork
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CircuitTermination,
|
||||
fields='__all__',
|
||||
filters=CircuitTerminationFilter
|
||||
)
|
||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||
provider_network: Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')] | None
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CircuitType,
|
||||
fields='__all__',
|
||||
filters=CircuitTypeFilter
|
||||
)
|
||||
class CircuitTypeType(OrganizationalObjectType):
|
||||
color: str
|
||||
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Circuit,
|
||||
fields='__all__',
|
||||
filters=CircuitFilter
|
||||
)
|
||||
class CircuitType(NetBoxObjectType, ContactsMixin):
|
||||
provider: ProviderType
|
||||
provider_account: ProviderAccountType | None
|
||||
termination_a: CircuitTerminationType | None
|
||||
termination_z: CircuitTerminationType | None
|
||||
type: CircuitTypeType
|
||||
tenant: TenantType | None
|
||||
|
||||
@strawberry_django.field
|
||||
def terminations(self) -> List[CircuitTerminationType]:
|
||||
return self.terminations.all()
|
||||
|
21
netbox/core/graphql/filters.py
Normal file
21
netbox/core/graphql/filters.py
Normal file
@ -0,0 +1,21 @@
|
||||
import strawberry_django
|
||||
|
||||
from core import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'DataFileFilter',
|
||||
'DataSourceFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DataFile, lookups=True)
|
||||
@autotype_decorator(filtersets.DataFileFilterSet)
|
||||
class DataFileFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DataSource, lookups=True)
|
||||
@autotype_decorator(filtersets.DataSourceFilterSet)
|
||||
class DataSourceFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,20 +1,20 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from core import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class CoreQuery(graphene.ObjectType):
|
||||
data_file = ObjectField(DataFileType)
|
||||
data_file_list = ObjectListField(DataFileType)
|
||||
@strawberry.type
|
||||
class CoreQuery:
|
||||
@strawberry.field
|
||||
def data_file(self, id: int) -> DataFileType:
|
||||
return models.DataFile.objects.get(pk=id)
|
||||
data_file_list: List[DataFileType] = strawberry_django.field()
|
||||
|
||||
def resolve_data_file_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DataFile.objects.all(), info)
|
||||
|
||||
data_source = ObjectField(DataSourceType)
|
||||
data_source_list = ObjectListField(DataSourceType)
|
||||
|
||||
def resolve_data_source_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DataSource.objects.all(), info)
|
||||
@strawberry.field
|
||||
def data_source(self, id: int) -> DataSourceType:
|
||||
return models.DataSource.objects.get(pk=id)
|
||||
data_source_list: List[DataSourceType] = strawberry_django.field()
|
||||
|
@ -1,5 +1,11 @@
|
||||
from core import filtersets, models
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from core import models
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'DataFileType',
|
||||
@ -7,15 +13,22 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.DataFile,
|
||||
exclude=['data',],
|
||||
filters=DataFileFilter
|
||||
)
|
||||
class DataFileType(BaseObjectType):
|
||||
class Meta:
|
||||
model = models.DataFile
|
||||
exclude = ('data',)
|
||||
filterset_class = filtersets.DataFileFilterSet
|
||||
source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.DataSource,
|
||||
fields='__all__',
|
||||
filters=DataSourceFilter
|
||||
)
|
||||
class DataSourceType(NetBoxObjectType):
|
||||
class Meta:
|
||||
model = models.DataSource
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.DataSourceFilterSet
|
||||
|
||||
@strawberry_django.field
|
||||
def datafiles(self) -> List[Annotated["DataFileType", strawberry.lazy('core.graphql.types')]]:
|
||||
return self.datafiles.all()
|
||||
|
294
netbox/dcim/graphql/filters.py
Normal file
294
netbox/dcim/graphql/filters.py
Normal file
@ -0,0 +1,294 @@
|
||||
import strawberry_django
|
||||
|
||||
from dcim import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'CableFilter',
|
||||
'CableTerminationFilter',
|
||||
'ConsolePortFilter',
|
||||
'ConsolePortTemplateFilter',
|
||||
'ConsoleServerPortFilter',
|
||||
'ConsoleServerPortTemplateFilter',
|
||||
'DeviceFilter',
|
||||
'DeviceBayFilter',
|
||||
'DeviceBayTemplateFilter',
|
||||
'InventoryItemTemplateFilter',
|
||||
'DeviceRoleFilter',
|
||||
'DeviceTypeFilter',
|
||||
'FrontPortFilter',
|
||||
'FrontPortTemplateFilter',
|
||||
'InterfaceFilter',
|
||||
'InterfaceTemplateFilter',
|
||||
'InventoryItemFilter',
|
||||
'InventoryItemRoleFilter',
|
||||
'LocationFilter',
|
||||
'ManufacturerFilter',
|
||||
'ModuleFilter',
|
||||
'ModuleBayFilter',
|
||||
'ModuleBayTemplateFilter',
|
||||
'ModuleTypeFilter',
|
||||
'PlatformFilter',
|
||||
'PowerFeedFilter',
|
||||
'PowerOutletFilter',
|
||||
'PowerOutletTemplateFilter',
|
||||
'PowerPanelFilter',
|
||||
'PowerPortFilter',
|
||||
'PowerPortTemplateFilter',
|
||||
'RackFilter',
|
||||
'RackReservationFilter',
|
||||
'RackRoleFilter',
|
||||
'RearPortFilter',
|
||||
'RearPortTemplateFilter',
|
||||
'RegionFilter',
|
||||
'SiteFilter',
|
||||
'SiteGroupFilter',
|
||||
'VirtualChassisFilter',
|
||||
'VirtualDeviceContextFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Cable, lookups=True)
|
||||
@autotype_decorator(filtersets.CableFilterSet)
|
||||
class CableFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CableTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.CableTerminationFilterSet)
|
||||
class CableTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsolePort, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsolePortFilterSet)
|
||||
class ConsolePortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsolePortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsolePortTemplateFilterSet)
|
||||
class ConsolePortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsoleServerPort, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsoleServerPortFilterSet)
|
||||
class ConsoleServerPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsoleServerPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsoleServerPortTemplateFilterSet)
|
||||
class ConsoleServerPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Device, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceFilterSet)
|
||||
class DeviceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceBay, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceBayFilterSet)
|
||||
class DeviceBayFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceBayTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceBayTemplateFilterSet)
|
||||
class DeviceBayTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItemTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemTemplateFilterSet)
|
||||
class InventoryItemTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceRole, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceRoleFilterSet)
|
||||
class DeviceRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceType, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceTypeFilterSet)
|
||||
class DeviceTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FrontPort, lookups=True)
|
||||
@autotype_decorator(filtersets.FrontPortFilterSet)
|
||||
class FrontPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FrontPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.FrontPortTemplateFilterSet)
|
||||
class FrontPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Interface, lookups=True)
|
||||
@autotype_decorator(filtersets.InterfaceFilterSet)
|
||||
class InterfaceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InterfaceTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.InterfaceTemplateFilterSet)
|
||||
class InterfaceTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItem, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemFilterSet)
|
||||
class InventoryItemFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItemRole, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemRoleFilterSet)
|
||||
class InventoryItemRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Location, lookups=True)
|
||||
@autotype_decorator(filtersets.LocationFilterSet)
|
||||
class LocationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Manufacturer, lookups=True)
|
||||
@autotype_decorator(filtersets.ManufacturerFilterSet)
|
||||
class ManufacturerFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Module, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleFilterSet)
|
||||
class ModuleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleBay, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleBayFilterSet)
|
||||
class ModuleBayFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleBayTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleBayTemplateFilterSet)
|
||||
class ModuleBayTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleType, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleTypeFilterSet)
|
||||
class ModuleTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Platform, lookups=True)
|
||||
@autotype_decorator(filtersets.PlatformFilterSet)
|
||||
class PlatformFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerFeed, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerFeedFilterSet)
|
||||
class PowerFeedFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerOutlet, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerOutletFilterSet)
|
||||
class PowerOutletFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerOutletTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerOutletTemplateFilterSet)
|
||||
class PowerOutletTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPanel, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPanelFilterSet)
|
||||
class PowerPanelFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPort, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPortFilterSet)
|
||||
class PowerPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPortTemplateFilterSet)
|
||||
class PowerPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Rack, lookups=True)
|
||||
@autotype_decorator(filtersets.RackFilterSet)
|
||||
class RackFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RackReservation, lookups=True)
|
||||
@autotype_decorator(filtersets.RackReservationFilterSet)
|
||||
class RackReservationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RackRole, lookups=True)
|
||||
@autotype_decorator(filtersets.RackRoleFilterSet)
|
||||
class RackRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RearPort, lookups=True)
|
||||
@autotype_decorator(filtersets.RearPortFilterSet)
|
||||
class RearPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RearPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.RearPortTemplateFilterSet)
|
||||
class RearPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Region, lookups=True)
|
||||
@autotype_decorator(filtersets.RegionFilterSet)
|
||||
class RegionFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Site, lookups=True)
|
||||
@autotype_decorator(filtersets.SiteFilterSet)
|
||||
class SiteFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.SiteGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.SiteGroupFilterSet)
|
||||
class SiteGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualChassis, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualChassisFilterSet)
|
||||
class VirtualChassisFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualDeviceContext, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualDeviceContextFilterSet)
|
||||
class VirtualDeviceContextFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,4 +1,3 @@
|
||||
import graphene
|
||||
from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
|
||||
from circuits.models import CircuitTermination, ProviderNetwork
|
||||
from dcim.graphql.types import (
|
||||
@ -37,79 +36,7 @@ from dcim.models import (
|
||||
)
|
||||
|
||||
|
||||
class LinkPeerType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
ConsolePortType,
|
||||
ConsoleServerPortType,
|
||||
FrontPortType,
|
||||
InterfaceType,
|
||||
PowerFeedType,
|
||||
PowerOutletType,
|
||||
PowerPortType,
|
||||
RearPortType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is CircuitTermination:
|
||||
return CircuitTerminationType
|
||||
if type(instance) is ConsolePortType:
|
||||
return ConsolePortType
|
||||
if type(instance) is ConsoleServerPort:
|
||||
return ConsoleServerPortType
|
||||
if type(instance) is FrontPort:
|
||||
return FrontPortType
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is PowerFeed:
|
||||
return PowerFeedType
|
||||
if type(instance) is PowerOutlet:
|
||||
return PowerOutletType
|
||||
if type(instance) is PowerPort:
|
||||
return PowerPortType
|
||||
if type(instance) is RearPort:
|
||||
return RearPortType
|
||||
|
||||
|
||||
class CableTerminationTerminationType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
ConsolePortType,
|
||||
ConsoleServerPortType,
|
||||
FrontPortType,
|
||||
InterfaceType,
|
||||
PowerFeedType,
|
||||
PowerOutletType,
|
||||
PowerPortType,
|
||||
RearPortType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is CircuitTermination:
|
||||
return CircuitTerminationType
|
||||
if type(instance) is ConsolePortType:
|
||||
return ConsolePortType
|
||||
if type(instance) is ConsoleServerPort:
|
||||
return ConsoleServerPortType
|
||||
if type(instance) is FrontPort:
|
||||
return FrontPortType
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is PowerFeed:
|
||||
return PowerFeedType
|
||||
if type(instance) is PowerOutlet:
|
||||
return PowerOutletType
|
||||
if type(instance) is PowerPort:
|
||||
return PowerPortType
|
||||
if type(instance) is RearPort:
|
||||
return RearPortType
|
||||
|
||||
|
||||
class InventoryItemTemplateComponentType(graphene.Union):
|
||||
class InventoryItemTemplateComponentType:
|
||||
class Meta:
|
||||
types = (
|
||||
ConsolePortTemplateType,
|
||||
@ -139,7 +66,7 @@ class InventoryItemTemplateComponentType(graphene.Union):
|
||||
return RearPortTemplateType
|
||||
|
||||
|
||||
class InventoryItemComponentType(graphene.Union):
|
||||
class InventoryItemComponentType:
|
||||
class Meta:
|
||||
types = (
|
||||
ConsolePortType,
|
||||
@ -169,7 +96,7 @@ class InventoryItemComponentType(graphene.Union):
|
||||
return RearPortType
|
||||
|
||||
|
||||
class ConnectedEndpointType(graphene.Union):
|
||||
class ConnectedEndpointType:
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
|
@ -1,20 +1,47 @@
|
||||
import graphene
|
||||
from typing import Annotated, List, Union
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
__all__ = (
|
||||
'CabledObjectMixin',
|
||||
'PathEndpointMixin',
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CabledObjectMixin:
|
||||
link_peers = graphene.List('dcim.graphql.gfk_mixins.LinkPeerType')
|
||||
cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
def resolve_cable_end(self, info):
|
||||
# Handle empty values
|
||||
return self.cable_end or None
|
||||
|
||||
def resolve_link_peers(self, info):
|
||||
@strawberry_django.field
|
||||
def link_peers(self) -> List[Annotated[Union[
|
||||
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("LinkPeerType")]]:
|
||||
return self.link_peers
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class PathEndpointMixin:
|
||||
connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.ConnectedEndpointType')
|
||||
|
||||
def resolve_connected_endpoints(self, info):
|
||||
# Handle empty values
|
||||
@strawberry_django.field
|
||||
def connected_endpoints(self) -> List[Annotated[Union[
|
||||
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("ConnectedEndpointType")]]:
|
||||
return self.connected_endpoints or None
|
||||
|
@ -1,249 +1,210 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from dcim import models
|
||||
from .types import VirtualDeviceContextType
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class DCIMQuery(graphene.ObjectType):
|
||||
cable = ObjectField(CableType)
|
||||
cable_list = ObjectListField(CableType)
|
||||
|
||||
def resolve_cable_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Cable.objects.all(), info)
|
||||
|
||||
console_port = ObjectField(ConsolePortType)
|
||||
console_port_list = ObjectListField(ConsolePortType)
|
||||
|
||||
def resolve_console_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsolePort.objects.all(), info)
|
||||
|
||||
console_port_template = ObjectField(ConsolePortTemplateType)
|
||||
console_port_template_list = ObjectListField(ConsolePortTemplateType)
|
||||
|
||||
def resolve_console_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsolePortTemplate.objects.all(), info)
|
||||
|
||||
console_server_port = ObjectField(ConsoleServerPortType)
|
||||
console_server_port_list = ObjectListField(ConsoleServerPortType)
|
||||
|
||||
def resolve_console_server_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsoleServerPort.objects.all(), info)
|
||||
|
||||
console_server_port_template = ObjectField(ConsoleServerPortTemplateType)
|
||||
console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType)
|
||||
|
||||
def resolve_console_server_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsoleServerPortTemplate.objects.all(), info)
|
||||
|
||||
device = ObjectField(DeviceType)
|
||||
device_list = ObjectListField(DeviceType)
|
||||
|
||||
def resolve_device_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Device.objects.all(), info)
|
||||
|
||||
device_bay = ObjectField(DeviceBayType)
|
||||
device_bay_list = ObjectListField(DeviceBayType)
|
||||
|
||||
def resolve_device_bay_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceBay.objects.all(), info)
|
||||
|
||||
device_bay_template = ObjectField(DeviceBayTemplateType)
|
||||
device_bay_template_list = ObjectListField(DeviceBayTemplateType)
|
||||
|
||||
def resolve_device_bay_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceBayTemplate.objects.all(), info)
|
||||
|
||||
device_role = ObjectField(DeviceRoleType)
|
||||
device_role_list = ObjectListField(DeviceRoleType)
|
||||
|
||||
def resolve_device_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceRole.objects.all(), info)
|
||||
|
||||
device_type = ObjectField(DeviceTypeType)
|
||||
device_type_list = ObjectListField(DeviceTypeType)
|
||||
|
||||
def resolve_device_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceType.objects.all(), info)
|
||||
|
||||
front_port = ObjectField(FrontPortType)
|
||||
front_port_list = ObjectListField(FrontPortType)
|
||||
|
||||
def resolve_front_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FrontPort.objects.all(), info)
|
||||
|
||||
front_port_template = ObjectField(FrontPortTemplateType)
|
||||
front_port_template_list = ObjectListField(FrontPortTemplateType)
|
||||
|
||||
def resolve_front_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FrontPortTemplate.objects.all(), info)
|
||||
|
||||
interface = ObjectField(InterfaceType)
|
||||
interface_list = ObjectListField(InterfaceType)
|
||||
|
||||
def resolve_interface_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Interface.objects.all(), info)
|
||||
|
||||
interface_template = ObjectField(InterfaceTemplateType)
|
||||
interface_template_list = ObjectListField(InterfaceTemplateType)
|
||||
|
||||
def resolve_interface_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InterfaceTemplate.objects.all(), info)
|
||||
|
||||
inventory_item = ObjectField(InventoryItemType)
|
||||
inventory_item_list = ObjectListField(InventoryItemType)
|
||||
|
||||
def resolve_inventory_item_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItem.objects.all(), info)
|
||||
|
||||
inventory_item_role = ObjectField(InventoryItemRoleType)
|
||||
inventory_item_role_list = ObjectListField(InventoryItemRoleType)
|
||||
|
||||
def resolve_inventory_item_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItemRole.objects.all(), info)
|
||||
|
||||
inventory_item_template = ObjectField(InventoryItemTemplateType)
|
||||
inventory_item_template_list = ObjectListField(InventoryItemTemplateType)
|
||||
|
||||
def resolve_inventory_item_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItemTemplate.objects.all(), info)
|
||||
|
||||
location = ObjectField(LocationType)
|
||||
location_list = ObjectListField(LocationType)
|
||||
|
||||
def resolve_location_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Location.objects.all(), info)
|
||||
|
||||
manufacturer = ObjectField(ManufacturerType)
|
||||
manufacturer_list = ObjectListField(ManufacturerType)
|
||||
|
||||
def resolve_manufacturer_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Manufacturer.objects.all(), info)
|
||||
|
||||
module = ObjectField(ModuleType)
|
||||
module_list = ObjectListField(ModuleType)
|
||||
|
||||
def resolve_module_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Module.objects.all(), info)
|
||||
|
||||
module_bay = ObjectField(ModuleBayType)
|
||||
module_bay_list = ObjectListField(ModuleBayType)
|
||||
|
||||
def resolve_module_bay_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleBay.objects.all(), info)
|
||||
|
||||
module_bay_template = ObjectField(ModuleBayTemplateType)
|
||||
module_bay_template_list = ObjectListField(ModuleBayTemplateType)
|
||||
|
||||
def resolve_module_bay_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleBayTemplate.objects.all(), info)
|
||||
|
||||
module_type = ObjectField(ModuleTypeType)
|
||||
module_type_list = ObjectListField(ModuleTypeType)
|
||||
|
||||
def resolve_module_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleType.objects.all(), info)
|
||||
|
||||
platform = ObjectField(PlatformType)
|
||||
platform_list = ObjectListField(PlatformType)
|
||||
|
||||
def resolve_platform_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Platform.objects.all(), info)
|
||||
|
||||
power_feed = ObjectField(PowerFeedType)
|
||||
power_feed_list = ObjectListField(PowerFeedType)
|
||||
|
||||
def resolve_power_feed_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerFeed.objects.all(), info)
|
||||
|
||||
power_outlet = ObjectField(PowerOutletType)
|
||||
power_outlet_list = ObjectListField(PowerOutletType)
|
||||
|
||||
def resolve_power_outlet_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerOutlet.objects.all(), info)
|
||||
|
||||
power_outlet_template = ObjectField(PowerOutletTemplateType)
|
||||
power_outlet_template_list = ObjectListField(PowerOutletTemplateType)
|
||||
|
||||
def resolve_power_outlet_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerOutletTemplate.objects.all(), info)
|
||||
|
||||
power_panel = ObjectField(PowerPanelType)
|
||||
power_panel_list = ObjectListField(PowerPanelType)
|
||||
|
||||
def resolve_power_panel_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPanel.objects.all(), info)
|
||||
|
||||
power_port = ObjectField(PowerPortType)
|
||||
power_port_list = ObjectListField(PowerPortType)
|
||||
|
||||
def resolve_power_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPort.objects.all(), info)
|
||||
|
||||
power_port_template = ObjectField(PowerPortTemplateType)
|
||||
power_port_template_list = ObjectListField(PowerPortTemplateType)
|
||||
|
||||
def resolve_power_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPortTemplate.objects.all(), info)
|
||||
|
||||
rack = ObjectField(RackType)
|
||||
rack_list = ObjectListField(RackType)
|
||||
|
||||
def resolve_rack_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Rack.objects.all(), info)
|
||||
|
||||
rack_reservation = ObjectField(RackReservationType)
|
||||
rack_reservation_list = ObjectListField(RackReservationType)
|
||||
|
||||
def resolve_rack_reservation_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RackReservation.objects.all(), info)
|
||||
|
||||
rack_role = ObjectField(RackRoleType)
|
||||
rack_role_list = ObjectListField(RackRoleType)
|
||||
|
||||
def resolve_rack_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RackRole.objects.all(), info)
|
||||
|
||||
rear_port = ObjectField(RearPortType)
|
||||
rear_port_list = ObjectListField(RearPortType)
|
||||
|
||||
def resolve_rear_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RearPort.objects.all(), info)
|
||||
|
||||
rear_port_template = ObjectField(RearPortTemplateType)
|
||||
rear_port_template_list = ObjectListField(RearPortTemplateType)
|
||||
|
||||
def resolve_rear_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RearPortTemplate.objects.all(), info)
|
||||
|
||||
region = ObjectField(RegionType)
|
||||
region_list = ObjectListField(RegionType)
|
||||
|
||||
def resolve_region_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Region.objects.all(), info)
|
||||
|
||||
site = ObjectField(SiteType)
|
||||
site_list = ObjectListField(SiteType)
|
||||
|
||||
def resolve_site_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Site.objects.all(), info)
|
||||
|
||||
site_group = ObjectField(SiteGroupType)
|
||||
site_group_list = ObjectListField(SiteGroupType)
|
||||
|
||||
def resolve_site_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.SiteGroup.objects.all(), info)
|
||||
|
||||
virtual_chassis = ObjectField(VirtualChassisType)
|
||||
virtual_chassis_list = ObjectListField(VirtualChassisType)
|
||||
|
||||
def resolve_virtual_chassis_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualChassis.objects.all(), info)
|
||||
|
||||
virtual_device_context = ObjectField(VirtualDeviceContextType)
|
||||
virtual_device_context_list = ObjectListField(VirtualDeviceContextType)
|
||||
|
||||
def resolve_virtual_device_context_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualDeviceContext.objects.all(), info)
|
||||
from .types import *
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class DCIMQuery:
|
||||
@strawberry.field
|
||||
def cable(self, id: int) -> CableType:
|
||||
return models.Cable.objects.get(pk=id)
|
||||
cable_list: List[CableType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_port(self, id: int) -> ConsolePortType:
|
||||
return models.ConsolePort.objects.get(pk=id)
|
||||
console_port_list: List[ConsolePortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_port_template(self, id: int) -> ConsolePortTemplateType:
|
||||
return models.ConsolePortTemplate.objects.get(pk=id)
|
||||
console_port_template_list: List[ConsolePortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_server_port(self, id: int) -> ConsoleServerPortType:
|
||||
return models.ConsoleServerPort.objects.get(pk=id)
|
||||
console_server_port_list: List[ConsoleServerPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_server_port_template(self, id: int) -> ConsoleServerPortTemplateType:
|
||||
return models.ConsoleServerPortTemplate.objects.get(pk=id)
|
||||
console_server_port_template_list: List[ConsoleServerPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device(self, id: int) -> DeviceType:
|
||||
return models.Device.objects.get(pk=id)
|
||||
device_list: List[DeviceType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_bay(self, id: int) -> DeviceBayType:
|
||||
return models.DeviceBay.objects.get(pk=id)
|
||||
device_bay_list: List[DeviceBayType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_bay_template(self, id: int) -> DeviceBayTemplateType:
|
||||
return models.DeviceBayTemplate.objects.get(pk=id)
|
||||
device_bay_template_list: List[DeviceBayTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_role(self, id: int) -> DeviceRoleType:
|
||||
return models.DeviceRole.objects.get(pk=id)
|
||||
device_role_list: List[DeviceRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_type(self, id: int) -> DeviceTypeType:
|
||||
return models.DeviceType.objects.get(pk=id)
|
||||
device_type_list: List[DeviceTypeType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def front_port(self, id: int) -> FrontPortType:
|
||||
return models.FrontPort.objects.get(pk=id)
|
||||
front_port_list: List[FrontPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def front_port_template(self, id: int) -> FrontPortTemplateType:
|
||||
return models.FrontPortTemplate.objects.get(pk=id)
|
||||
front_port_template_list: List[FrontPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def interface(self, id: int) -> InterfaceType:
|
||||
return models.Interface.objects.get(pk=id)
|
||||
interface_list: List[InterfaceType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def interface_template(self, id: int) -> InterfaceTemplateType:
|
||||
return models.InterfaceTemplate.objects.get(pk=id)
|
||||
interface_template_list: List[InterfaceTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item(self, id: int) -> InventoryItemType:
|
||||
return models.InventoryItem.objects.get(pk=id)
|
||||
inventory_item_list: List[InventoryItemType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item_role(self, id: int) -> InventoryItemRoleType:
|
||||
return models.InventoryItemRole.objects.get(pk=id)
|
||||
inventory_item_role_list: List[InventoryItemRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item_template(self, id: int) -> InventoryItemTemplateType:
|
||||
return models.InventoryItemTemplate.objects.get(pk=id)
|
||||
inventory_item_template_list: List[InventoryItemTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def location(self, id: int) -> LocationType:
|
||||
return models.Location.objects.get(pk=id)
|
||||
location_list: List[LocationType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def manufacturer(self, id: int) -> ManufacturerType:
|
||||
return models.Manufacturer.objects.get(pk=id)
|
||||
manufacturer_list: List[ManufacturerType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module(self, id: int) -> ModuleType:
|
||||
return models.Module.objects.get(pk=id)
|
||||
module_list: List[ModuleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_bay(self, id: int) -> ModuleBayType:
|
||||
return models.ModuleBay.objects.get(pk=id)
|
||||
module_bay_list: List[ModuleBayType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_bay_template(self, id: int) -> ModuleBayTemplateType:
|
||||
return models.ModuleBayTemplate.objects.get(pk=id)
|
||||
module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_type(self, id: int) -> ModuleTypeType:
|
||||
return models.ModuleType.objects.get(pk=id)
|
||||
module_type_list: List[ModuleTypeType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def platform(self, id: int) -> PlatformType:
|
||||
return models.Platform.objects.get(pk=id)
|
||||
platform_list: List[PlatformType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_feed(self, id: int) -> PowerFeedType:
|
||||
return models.PowerFeed.objects.get(pk=id)
|
||||
power_feed_list: List[PowerFeedType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_outlet(self, id: int) -> PowerOutletType:
|
||||
return models.PowerOutlet.objects.get(pk=id)
|
||||
power_outlet_list: List[PowerOutletType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_outlet_template(self, id: int) -> PowerOutletTemplateType:
|
||||
return models.PowerOutletTemplate.objects.get(pk=id)
|
||||
power_outlet_template_list: List[PowerOutletTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_panel(self, id: int) -> PowerPanelType:
|
||||
return models.PowerPanel.objects.get(id=id)
|
||||
power_panel_list: List[PowerPanelType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_port(self, id: int) -> PowerPortType:
|
||||
return models.PowerPort.objects.get(id=id)
|
||||
power_port_list: List[PowerPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_port_template(self, id: int) -> PowerPortTemplateType:
|
||||
return models.PowerPortTemplate.objects.get(id=id)
|
||||
power_port_template_list: List[PowerPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack(self, id: int) -> RackType:
|
||||
return models.Rack.objects.get(id=id)
|
||||
rack_list: List[RackType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack_reservation(self, id: int) -> RackReservationType:
|
||||
return models.RackReservation.objects.get(id=id)
|
||||
rack_reservation_list: List[RackReservationType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack_role(self, id: int) -> RackRoleType:
|
||||
return models.RackRole.objects.get(id=id)
|
||||
rack_role_list: List[RackRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rear_port(self, id: int) -> RearPortType:
|
||||
return models.RearPort.objects.get(id=id)
|
||||
rear_port_list: List[RearPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rear_port_template(self, id: int) -> RearPortTemplateType:
|
||||
return models.RearPortTemplate.objects.get(id=id)
|
||||
rear_port_template_list: List[RearPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def region(self, id: int) -> RegionType:
|
||||
return models.Region.objects.get(id=id)
|
||||
region_list: List[RegionType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def site(self, id: int) -> SiteType:
|
||||
return models.Site.objects.get(id=id)
|
||||
site_list: List[SiteType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def site_group(self, id: int) -> SiteGroupType:
|
||||
return models.SiteGroup.objects.get(id=id)
|
||||
site_group_list: List[SiteGroupType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def virtual_chassis(self, id: int) -> VirtualChassisType:
|
||||
return models.VirtualChassis.objects.get(id=id)
|
||||
virtual_chassis_list: List[VirtualChassisType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def virtual_device_context(self, id: int) -> VirtualDeviceContextType:
|
||||
return models.VirtualDeviceContext.objects.get(id=id)
|
||||
virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()
|
||||
|
File diff suppressed because it is too large
Load Diff
98
netbox/extras/graphql/filters.py
Normal file
98
netbox/extras/graphql/filters.py
Normal file
@ -0,0 +1,98 @@
|
||||
import strawberry_django
|
||||
|
||||
from extras import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextFilter',
|
||||
'ConfigTemplateFilter',
|
||||
'CustomFieldFilter',
|
||||
'CustomFieldChoiceSetFilter',
|
||||
'CustomLinkFilter',
|
||||
'EventRuleFilter',
|
||||
'ExportTemplateFilter',
|
||||
'ImageAttachmentFilter',
|
||||
'JournalEntryFilter',
|
||||
'ObjectChangeFilter',
|
||||
'SavedFilterFilter',
|
||||
'TagFilter',
|
||||
'WebhookFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConfigContext, lookups=True)
|
||||
@autotype_decorator(filtersets.ConfigContextFilterSet)
|
||||
class ConfigContextFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConfigTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConfigTemplateFilterSet)
|
||||
class ConfigTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomField, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomFieldFilterSet)
|
||||
class CustomFieldFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomFieldChoiceSet, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomFieldChoiceSetFilterSet)
|
||||
class CustomFieldChoiceSetFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomLink, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomLinkFilterSet)
|
||||
class CustomLinkFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ExportTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ExportTemplateFilterSet)
|
||||
class ExportTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ImageAttachment, lookups=True)
|
||||
@autotype_decorator(filtersets.ImageAttachmentFilterSet)
|
||||
class ImageAttachmentFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.JournalEntry, lookups=True)
|
||||
@autotype_decorator(filtersets.JournalEntryFilterSet)
|
||||
class JournalEntryFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ObjectChange, lookups=True)
|
||||
@autotype_decorator(filtersets.ObjectChangeFilterSet)
|
||||
class ObjectChangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.SavedFilter, lookups=True)
|
||||
@autotype_decorator(filtersets.SavedFilterFilterSet)
|
||||
class SavedFilterFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Tag, lookups=True)
|
||||
@autotype_decorator(filtersets.TagFilterSet)
|
||||
class TagFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Webhook, lookups=True)
|
||||
@autotype_decorator(filtersets.WebhookFilterSet)
|
||||
class WebhookFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.EventRule, lookups=True)
|
||||
@autotype_decorator(filtersets.EventRuleFilterSet)
|
||||
class EventRuleFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,6 +1,8 @@
|
||||
import graphene
|
||||
from typing import TYPE_CHECKING, Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from graphene.types.generic import GenericScalar
|
||||
|
||||
from extras.models import ObjectChange
|
||||
|
||||
@ -14,56 +16,67 @@ __all__ = (
|
||||
'TagsMixin',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import ImageAttachmentType, JournalEntryType, ObjectChangeType, TagType
|
||||
from tenancy.graphql.types import ContactAssignmentType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ChangelogMixin:
|
||||
changelog = graphene.List('extras.graphql.types.ObjectChangeType')
|
||||
|
||||
def resolve_changelog(self, info):
|
||||
@strawberry_django.field
|
||||
def changelog(self, info) -> List[Annotated["ObjectChangeType", strawberry.lazy('.types')]]:
|
||||
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.user, 'view')
|
||||
return object_changes.restrict(info.context.request.user, 'view')
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ConfigContextMixin:
|
||||
config_context = GenericScalar()
|
||||
|
||||
def resolve_config_context(self, info):
|
||||
@strawberry_django.field
|
||||
def config_context(self) -> strawberry.scalars.JSON:
|
||||
return self.get_config_context()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CustomFieldsMixin:
|
||||
custom_fields = GenericScalar()
|
||||
|
||||
def resolve_custom_fields(self, info):
|
||||
@strawberry_django.field
|
||||
def custom_fields(self) -> strawberry.scalars.JSON:
|
||||
return self.custom_field_data
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ImageAttachmentsMixin:
|
||||
image_attachments = graphene.List('extras.graphql.types.ImageAttachmentType')
|
||||
|
||||
def resolve_image_attachments(self, info):
|
||||
return self.images.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def image_attachments(self, info) -> List[Annotated["ImageAttachmentType", strawberry.lazy('.types')]]:
|
||||
return self.images.restrict(info.context.request.user, 'view')
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class JournalEntriesMixin:
|
||||
journal_entries = graphene.List('extras.graphql.types.JournalEntryType')
|
||||
|
||||
def resolve_journal_entries(self, info):
|
||||
return self.journal_entries.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def journal_entries(self, info) -> List[Annotated["JournalEntryType", strawberry.lazy('.types')]]:
|
||||
return self.journal_entries.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class TagsMixin:
|
||||
tags = graphene.List('extras.graphql.types.TagType')
|
||||
|
||||
def resolve_tags(self, info):
|
||||
@strawberry_django.field
|
||||
def tags(self) -> List[Annotated["TagType", strawberry.lazy('.types')]]:
|
||||
return self.tags.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ContactsMixin:
|
||||
contacts = graphene.List('tenancy.graphql.types.ContactAssignmentType')
|
||||
|
||||
def resolve_contacts(self, info):
|
||||
@strawberry_django.field
|
||||
def contacts(self) -> List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return list(self.contacts.all())
|
||||
|
@ -1,80 +1,70 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class ExtrasQuery(graphene.ObjectType):
|
||||
config_context = ObjectField(ConfigContextType)
|
||||
config_context_list = ObjectListField(ConfigContextType)
|
||||
@strawberry.type
|
||||
class ExtrasQuery:
|
||||
@strawberry.field
|
||||
def config_context(self, id: int) -> ConfigContextType:
|
||||
return models.ConfigContext.objects.get(pk=id)
|
||||
config_context_list: List[ConfigContextType] = strawberry_django.field()
|
||||
|
||||
def resolve_config_context_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConfigContext.objects.all(), info)
|
||||
@strawberry.field
|
||||
def config_template(self, id: int) -> ConfigTemplateType:
|
||||
return models.ConfigTemplate.objects.get(pk=id)
|
||||
config_template_list: List[ConfigTemplateType] = strawberry_django.field()
|
||||
|
||||
config_template = ObjectField(ConfigTemplateType)
|
||||
config_template_list = ObjectListField(ConfigTemplateType)
|
||||
@strawberry.field
|
||||
def custom_field(self, id: int) -> CustomFieldType:
|
||||
return models.CustomField.objects.get(pk=id)
|
||||
custom_field_list: List[CustomFieldType] = strawberry_django.field()
|
||||
|
||||
def resolve_config_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConfigTemplate.objects.all(), info)
|
||||
@strawberry.field
|
||||
def custom_field_choice_set(self, id: int) -> CustomFieldChoiceSetType:
|
||||
return models.CustomFieldChoiceSet.objects.get(pk=id)
|
||||
custom_field_choice_set_list: List[CustomFieldChoiceSetType] = strawberry_django.field()
|
||||
|
||||
custom_field = ObjectField(CustomFieldType)
|
||||
custom_field_list = ObjectListField(CustomFieldType)
|
||||
@strawberry.field
|
||||
def custom_link(self, id: int) -> CustomLinkType:
|
||||
return models.CustomLink.objects.get(pk=id)
|
||||
custom_link_list: List[CustomLinkType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_field_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomField.objects.all(), info)
|
||||
@strawberry.field
|
||||
def export_template(self, id: int) -> ExportTemplateType:
|
||||
return models.ExportTemplate.objects.get(pk=id)
|
||||
export_template_list: List[ExportTemplateType] = strawberry_django.field()
|
||||
|
||||
custom_field_choice_set = ObjectField(CustomFieldChoiceSetType)
|
||||
custom_field_choice_set_list = ObjectListField(CustomFieldChoiceSetType)
|
||||
@strawberry.field
|
||||
def image_attachment(self, id: int) -> ImageAttachmentType:
|
||||
return models.ImageAttachment.objects.get(pk=id)
|
||||
image_attachment_list: List[ImageAttachmentType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_field_choices_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomFieldChoiceSet.objects.all(), info)
|
||||
@strawberry.field
|
||||
def saved_filter(self, id: int) -> SavedFilterType:
|
||||
return models.SavedFilter.objects.get(pk=id)
|
||||
saved_filter_list: List[SavedFilterType] = strawberry_django.field()
|
||||
|
||||
custom_link = ObjectField(CustomLinkType)
|
||||
custom_link_list = ObjectListField(CustomLinkType)
|
||||
@strawberry.field
|
||||
def journal_entry(self, id: int) -> JournalEntryType:
|
||||
return models.JournalEntry.objects.get(pk=id)
|
||||
journal_entry_list: List[JournalEntryType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_link_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomLink.objects.all(), info)
|
||||
@strawberry.field
|
||||
def tag(self, id: int) -> TagType:
|
||||
return models.Tag.objects.get(pk=id)
|
||||
tag_list: List[TagType] = strawberry_django.field()
|
||||
|
||||
export_template = ObjectField(ExportTemplateType)
|
||||
export_template_list = ObjectListField(ExportTemplateType)
|
||||
@strawberry.field
|
||||
def webhook(self, id: int) -> WebhookType:
|
||||
return models.Webhook.objects.get(pk=id)
|
||||
webhook_list: List[WebhookType] = strawberry_django.field()
|
||||
|
||||
def resolve_export_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ExportTemplate.objects.all(), info)
|
||||
|
||||
image_attachment = ObjectField(ImageAttachmentType)
|
||||
image_attachment_list = ObjectListField(ImageAttachmentType)
|
||||
|
||||
def resolve_image_attachment_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ImageAttachment.objects.all(), info)
|
||||
|
||||
saved_filter = ObjectField(SavedFilterType)
|
||||
saved_filter_list = ObjectListField(SavedFilterType)
|
||||
|
||||
def resolve_saved_filter_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.SavedFilter.objects.all(), info)
|
||||
|
||||
journal_entry = ObjectField(JournalEntryType)
|
||||
journal_entry_list = ObjectListField(JournalEntryType)
|
||||
|
||||
def resolve_journal_entry_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.JournalEntry.objects.all(), info)
|
||||
|
||||
tag = ObjectField(TagType)
|
||||
tag_list = ObjectListField(TagType)
|
||||
|
||||
def resolve_tag_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Tag.objects.all(), info)
|
||||
|
||||
webhook = ObjectField(WebhookType)
|
||||
webhook_list = ObjectListField(WebhookType)
|
||||
|
||||
def resolve_webhook_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Webhook.objects.all(), info)
|
||||
|
||||
event_rule = ObjectField(EventRuleType)
|
||||
event_rule_list = ObjectListField(EventRuleType)
|
||||
|
||||
def resolve_eventrule_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.EventRule.objects.all(), info)
|
||||
@strawberry.field
|
||||
def event_rule(self, id: int) -> EventRuleType:
|
||||
return models.EventRule.objects.get(pk=id)
|
||||
event_rule_list: List[EventRuleType] = strawberry_django.field()
|
||||
|
@ -1,6 +1,12 @@
|
||||
from extras import filtersets, models
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras import models
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType, OrganizationalObjectType
|
||||
from netbox.graphql.types import BaseObjectType, ContentTypeType, ObjectType, OrganizationalObjectType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextType',
|
||||
@ -19,104 +25,202 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ConfigContext,
|
||||
fields='__all__',
|
||||
filters=ConfigContextFilter
|
||||
)
|
||||
class ConfigContextType(ObjectType):
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ConfigContext
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ConfigContextFilterSet
|
||||
@strawberry_django.field
|
||||
def roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.roles.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def device_types(self) -> List[Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.device_types.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tags(self) -> List[Annotated["TagType", strawberry.lazy('extras.graphql.types')]]:
|
||||
return self.tags.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.platforms.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def regions(self) -> List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.regions.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def cluster_groups(self) -> List[Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.cluster_groups.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tenant_groups(self) -> List[Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return self.tenant_groups.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def cluster_types(self) -> List[Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.cluster_types.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.clusters.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.locations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.sites.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tenants(self) -> List[Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return self.tenants.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def site_groups(self) -> List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.site_groups.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ConfigTemplate,
|
||||
fields='__all__',
|
||||
filters=ConfigTemplateFilter
|
||||
)
|
||||
class ConfigTemplateType(TagsMixin, ObjectType):
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ConfigTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ConfigTemplateFilterSet
|
||||
@strawberry_django.field
|
||||
def virtualmachines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.virtualmachines.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.devices.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.platforms.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def device_roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.device_roles.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomField,
|
||||
fields='__all__',
|
||||
filters=CustomFieldFilter
|
||||
)
|
||||
class CustomFieldType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomField
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomFieldFilterSet
|
||||
related_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
choice_set: Annotated["CustomFieldChoiceSetType", strawberry.lazy('extras.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomFieldChoiceSet,
|
||||
exclude=('extra_choices', ),
|
||||
filters=CustomFieldChoiceSetFilter
|
||||
)
|
||||
class CustomFieldChoiceSetType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomFieldChoiceSet
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomFieldChoiceSetFilterSet
|
||||
@strawberry_django.field
|
||||
def choices_for(self) -> List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]:
|
||||
return self.choices_for.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def extra_choices(self) -> List[str] | None:
|
||||
return list(self.extra_choices)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomLink,
|
||||
fields='__all__',
|
||||
filters=CustomLinkFilter
|
||||
)
|
||||
class CustomLinkType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomLink
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomLinkFilterSet
|
||||
|
||||
|
||||
class EventRuleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.EventRule
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.EventRuleFilterSet
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ExportTemplate,
|
||||
fields='__all__',
|
||||
filters=ExportTemplateFilter
|
||||
)
|
||||
class ExportTemplateType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ExportTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ExportTemplateFilterSet
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ImageAttachment,
|
||||
fields='__all__',
|
||||
filters=ImageAttachmentFilter
|
||||
)
|
||||
class ImageAttachmentType(BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ImageAttachment
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||
object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.JournalEntry,
|
||||
fields='__all__',
|
||||
filters=JournalEntryFilter
|
||||
)
|
||||
class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.JournalEntry
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.JournalEntryFilterSet
|
||||
assigned_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
created_by: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ObjectChange,
|
||||
fields='__all__',
|
||||
filters=ObjectChangeFilter
|
||||
)
|
||||
class ObjectChangeType(BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ObjectChange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ObjectChangeFilterSet
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.SavedFilter,
|
||||
exclude=['content_types',],
|
||||
filters=SavedFilterFilter
|
||||
)
|
||||
class SavedFilterType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.SavedFilter
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.SavedFilterFilterSet
|
||||
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Tag,
|
||||
exclude=['extras_taggeditem_items', ],
|
||||
filters=TagFilter
|
||||
)
|
||||
class TagType(ObjectType):
|
||||
color: str
|
||||
|
||||
class Meta:
|
||||
model = models.Tag
|
||||
exclude = ('extras_taggeditem_items',)
|
||||
filterset_class = filtersets.TagFilterSet
|
||||
@strawberry_django.field
|
||||
def object_types(self) -> List[ContentTypeType]:
|
||||
return self.object_types.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Webhook,
|
||||
exclude=['content_types',],
|
||||
filters=WebhookFilter
|
||||
)
|
||||
class WebhookType(OrganizationalObjectType):
|
||||
pass
|
||||
|
||||
class Meta:
|
||||
model = models.Webhook
|
||||
filterset_class = filtersets.WebhookFilterSet
|
||||
|
||||
@strawberry_django.type(
|
||||
models.EventRule,
|
||||
exclude=['content_types',],
|
||||
filters=EventRuleFilter
|
||||
)
|
||||
class EventRuleType(OrganizationalObjectType):
|
||||
action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
|
119
netbox/ipam/graphql/filters.py
Normal file
119
netbox/ipam/graphql/filters.py
Normal file
@ -0,0 +1,119 @@
|
||||
import strawberry_django
|
||||
|
||||
from ipam import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'ASNFilter',
|
||||
'ASNRangeFilter',
|
||||
'AggregateFilter',
|
||||
'FHRPGroupFilter',
|
||||
'FHRPGroupAssignmentFilter',
|
||||
'IPAddressFilter',
|
||||
'IPRangeFilter',
|
||||
'PrefixFilter',
|
||||
'RIRFilter',
|
||||
'RoleFilter',
|
||||
'RouteTargetFilter',
|
||||
'ServiceFilter',
|
||||
'ServiceTemplateFilter',
|
||||
'VLANFilter',
|
||||
'VLANGroupFilter',
|
||||
'VRFFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ASN, lookups=True)
|
||||
@autotype_decorator(filtersets.ASNFilterSet)
|
||||
class ASNFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ASNRange, lookups=True)
|
||||
@autotype_decorator(filtersets.ASNRangeFilterSet)
|
||||
class ASNRangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Aggregate, lookups=True)
|
||||
@autotype_decorator(filtersets.AggregateFilterSet)
|
||||
class AggregateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FHRPGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.FHRPGroupFilterSet)
|
||||
class FHRPGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FHRPGroupAssignment, lookups=True)
|
||||
@autotype_decorator(filtersets.FHRPGroupAssignmentFilterSet)
|
||||
class FHRPGroupAssignmentFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPAddress, lookups=True)
|
||||
@autotype_decorator(filtersets.IPAddressFilterSet)
|
||||
class IPAddressFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPRange, lookups=True)
|
||||
@autotype_decorator(filtersets.IPRangeFilterSet)
|
||||
class IPRangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Prefix, lookups=True)
|
||||
@autotype_decorator(filtersets.PrefixFilterSet)
|
||||
class PrefixFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RIR, lookups=True)
|
||||
@autotype_decorator(filtersets.RIRFilterSet)
|
||||
class RIRFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Role, lookups=True)
|
||||
@autotype_decorator(filtersets.RoleFilterSet)
|
||||
class RoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RouteTarget, lookups=True)
|
||||
@autotype_decorator(filtersets.RouteTargetFilterSet)
|
||||
class RouteTargetFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Service, lookups=True)
|
||||
@autotype_decorator(filtersets.ServiceFilterSet)
|
||||
class ServiceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ServiceTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ServiceTemplateFilterSet)
|
||||
class ServiceTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VLAN, lookups=True)
|
||||
@autotype_decorator(filtersets.VLANFilterSet)
|
||||
class VLANFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VLANGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.VLANGroupFilterSet)
|
||||
class VLANGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VRF, lookups=True)
|
||||
@autotype_decorator(filtersets.VRFFilterSet)
|
||||
class VRFFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,95 +0,0 @@
|
||||
import graphene
|
||||
from dcim.graphql.types import (
|
||||
InterfaceType,
|
||||
LocationType,
|
||||
RackType,
|
||||
RegionType,
|
||||
SiteGroupType,
|
||||
SiteType,
|
||||
)
|
||||
from dcim.models import Interface, Location, Rack, Region, Site, SiteGroup
|
||||
from ipam.graphql.types import FHRPGroupType, VLANType
|
||||
from ipam.models import VLAN, FHRPGroup
|
||||
from virtualization.graphql.types import ClusterGroupType, ClusterType, VMInterfaceType
|
||||
from virtualization.models import Cluster, ClusterGroup, VMInterface
|
||||
|
||||
|
||||
class IPAddressAssignmentType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
FHRPGroupType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is FHRPGroup:
|
||||
return FHRPGroupType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class L2VPNAssignmentType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
VLANType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is VLAN:
|
||||
return VLANType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class FHRPGroupInterfaceType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class VLANGroupScopeType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
ClusterType,
|
||||
ClusterGroupType,
|
||||
LocationType,
|
||||
RackType,
|
||||
RegionType,
|
||||
SiteType,
|
||||
SiteGroupType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Cluster:
|
||||
return ClusterType
|
||||
if type(instance) is ClusterGroup:
|
||||
return ClusterGroupType
|
||||
if type(instance) is Location:
|
||||
return LocationType
|
||||
if type(instance) is Rack:
|
||||
return RackType
|
||||
if type(instance) is Region:
|
||||
return RegionType
|
||||
if type(instance) is Site:
|
||||
return SiteType
|
||||
if type(instance) is SiteGroup:
|
||||
return SiteGroupType
|
@ -1,4 +1,7 @@
|
||||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
__all__ = (
|
||||
'IPAddressesMixin',
|
||||
@ -6,15 +9,15 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class IPAddressesMixin:
|
||||
ip_addresses = graphene.List('ipam.graphql.types.IPAddressType')
|
||||
|
||||
def resolve_ip_addresses(self, info):
|
||||
return self.ip_addresses.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_addresses.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class VLANGroupsMixin:
|
||||
vlan_groups = graphene.List('ipam.graphql.types.VLANGroupType')
|
||||
|
||||
def resolve_vlan_groups(self, info):
|
||||
return self.vlan_groups.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def vlan_groups(self) -> List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vlan_groups.all()
|
||||
|
@ -1,104 +1,90 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from ipam import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from .types import *
|
||||
|
||||
|
||||
class IPAMQuery(graphene.ObjectType):
|
||||
asn = ObjectField(ASNType)
|
||||
asn_list = ObjectListField(ASNType)
|
||||
@strawberry.type
|
||||
class IPAMQuery:
|
||||
@strawberry.field
|
||||
def asn(self, id: int) -> ASNType:
|
||||
return models.ASN.objects.get(pk=id)
|
||||
asn_list: List[ASNType] = strawberry_django.field()
|
||||
|
||||
def resolve_asn_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ASN.objects.all(), info)
|
||||
@strawberry.field
|
||||
def asn_range(self, id: int) -> ASNRangeType:
|
||||
return models.ASNRange.objects.get(pk=id)
|
||||
asn_range_list: List[ASNRangeType] = strawberry_django.field()
|
||||
|
||||
asn_range = ObjectField(ASNRangeType)
|
||||
asn_range_list = ObjectListField(ASNRangeType)
|
||||
@strawberry.field
|
||||
def aggregate(self, id: int) -> AggregateType:
|
||||
return models.Aggregate.objects.get(pk=id)
|
||||
aggregate_list: List[AggregateType] = strawberry_django.field()
|
||||
|
||||
def resolve_asn_range_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ASNRange.objects.all(), info)
|
||||
@strawberry.field
|
||||
def ip_address(self, id: int) -> IPAddressType:
|
||||
return models.IPAddress.objects.get(pk=id)
|
||||
ip_address_list: List[IPAddressType] = strawberry_django.field()
|
||||
|
||||
aggregate = ObjectField(AggregateType)
|
||||
aggregate_list = ObjectListField(AggregateType)
|
||||
@strawberry.field
|
||||
def ip_range(self, id: int) -> IPRangeType:
|
||||
return models.IPRange.objects.get(pk=id)
|
||||
ip_range_list: List[IPRangeType] = strawberry_django.field()
|
||||
|
||||
def resolve_aggregate_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Aggregate.objects.all(), info)
|
||||
@strawberry.field
|
||||
def prefix(self, id: int) -> PrefixType:
|
||||
return models.Prefix.objects.get(pk=id)
|
||||
prefix_list: List[PrefixType] = strawberry_django.field()
|
||||
|
||||
ip_address = ObjectField(IPAddressType)
|
||||
ip_address_list = ObjectListField(IPAddressType)
|
||||
@strawberry.field
|
||||
def rir(self, id: int) -> RIRType:
|
||||
return models.RIR.objects.get(pk=id)
|
||||
rir_list: List[RIRType] = strawberry_django.field()
|
||||
|
||||
def resolve_ip_address_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPAddress.objects.all(), info)
|
||||
@strawberry.field
|
||||
def role(self, id: int) -> RoleType:
|
||||
return models.Role.objects.get(pk=id)
|
||||
role_list: List[RoleType] = strawberry_django.field()
|
||||
|
||||
ip_range = ObjectField(IPRangeType)
|
||||
ip_range_list = ObjectListField(IPRangeType)
|
||||
@strawberry.field
|
||||
def route_target(self, id: int) -> RouteTargetType:
|
||||
return models.RouteTarget.objects.get(pk=id)
|
||||
route_target_list: List[RouteTargetType] = strawberry_django.field()
|
||||
|
||||
def resolve_ip_range_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPRange.objects.all(), info)
|
||||
@strawberry.field
|
||||
def service(self, id: int) -> ServiceType:
|
||||
return models.Service.objects.get(pk=id)
|
||||
service_list: List[ServiceType] = strawberry_django.field()
|
||||
|
||||
prefix = ObjectField(PrefixType)
|
||||
prefix_list = ObjectListField(PrefixType)
|
||||
@strawberry.field
|
||||
def service_template(self, id: int) -> ServiceTemplateType:
|
||||
return models.ServiceTemplate.objects.get(pk=id)
|
||||
service_template_list: List[ServiceTemplateType] = strawberry_django.field()
|
||||
|
||||
def resolve_prefix_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Prefix.objects.all(), info)
|
||||
@strawberry.field
|
||||
def fhrp_group(self, id: int) -> FHRPGroupType:
|
||||
return models.FHRPGroup.objects.get(pk=id)
|
||||
fhrp_group_list: List[FHRPGroupType] = strawberry_django.field()
|
||||
|
||||
rir = ObjectField(RIRType)
|
||||
rir_list = ObjectListField(RIRType)
|
||||
@strawberry.field
|
||||
def fhrp_group_assignment(self, id: int) -> FHRPGroupAssignmentType:
|
||||
return models.FHRPGroupAssignment.objects.get(pk=id)
|
||||
fhrp_group_assignment_list: List[FHRPGroupAssignmentType] = strawberry_django.field()
|
||||
|
||||
def resolve_rir_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RIR.objects.all(), info)
|
||||
@strawberry.field
|
||||
def vlan(self, id: int) -> VLANType:
|
||||
return models.VLAN.objects.get(pk=id)
|
||||
vlan_list: List[VLANType] = strawberry_django.field()
|
||||
|
||||
role = ObjectField(RoleType)
|
||||
role_list = ObjectListField(RoleType)
|
||||
@strawberry.field
|
||||
def vlan_group(self, id: int) -> VLANGroupType:
|
||||
return models.VLANGroup.objects.get(pk=id)
|
||||
vlan_group_list: List[VLANGroupType] = strawberry_django.field()
|
||||
|
||||
def resolve_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Role.objects.all(), info)
|
||||
|
||||
route_target = ObjectField(RouteTargetType)
|
||||
route_target_list = ObjectListField(RouteTargetType)
|
||||
|
||||
def resolve_route_target_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RouteTarget.objects.all(), info)
|
||||
|
||||
service = ObjectField(ServiceType)
|
||||
service_list = ObjectListField(ServiceType)
|
||||
|
||||
def resolve_service_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Service.objects.all(), info)
|
||||
|
||||
service_template = ObjectField(ServiceTemplateType)
|
||||
service_template_list = ObjectListField(ServiceTemplateType)
|
||||
|
||||
def resolve_service_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ServiceTemplate.objects.all(), info)
|
||||
|
||||
fhrp_group = ObjectField(FHRPGroupType)
|
||||
fhrp_group_list = ObjectListField(FHRPGroupType)
|
||||
|
||||
def resolve_fhrp_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FHRPGroup.objects.all(), info)
|
||||
|
||||
fhrp_group_assignment = ObjectField(FHRPGroupAssignmentType)
|
||||
fhrp_group_assignment_list = ObjectListField(FHRPGroupAssignmentType)
|
||||
|
||||
def resolve_fhrp_group_assignment_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FHRPGroupAssignment.objects.all(), info)
|
||||
|
||||
vlan = ObjectField(VLANType)
|
||||
vlan_list = ObjectListField(VLANType)
|
||||
|
||||
def resolve_vlan_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VLAN.objects.all(), info)
|
||||
|
||||
vlan_group = ObjectField(VLANGroupType)
|
||||
vlan_group_list = ObjectListField(VLANGroupType)
|
||||
|
||||
def resolve_vlan_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VLANGroup.objects.all(), info)
|
||||
|
||||
vrf = ObjectField(VRFType)
|
||||
vrf_list = ObjectListField(VRFType)
|
||||
|
||||
def resolve_vrf_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VRF.objects.all(), info)
|
||||
@strawberry.field
|
||||
def vrf(self, id: int) -> VRFType:
|
||||
return models.VRF.objects.get(pk=id)
|
||||
vrf_list: List[VRFType] = strawberry_django.field()
|
||||
|
@ -1,9 +1,15 @@
|
||||
import graphene
|
||||
from typing import Annotated, List, Union
|
||||
|
||||
from ipam import filtersets, models
|
||||
from .mixins import IPAddressesMixin
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits.graphql.types import ProviderType
|
||||
from dcim.graphql.types import SiteType
|
||||
from ipam import models
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
|
||||
from .filters import *
|
||||
from .mixins import IPAddressesMixin
|
||||
|
||||
__all__ = (
|
||||
'ASNType',
|
||||
@ -25,164 +31,335 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class IPAddressFamilyType(graphene.ObjectType):
|
||||
|
||||
value = graphene.Int()
|
||||
label = graphene.String()
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.label = f'IPv{value}'
|
||||
@strawberry.type
|
||||
class IPAddressFamilyType:
|
||||
value: int
|
||||
label: str
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class BaseIPAddressFamilyType:
|
||||
"""
|
||||
Base type for models that need to expose their IPAddress family type.
|
||||
"""
|
||||
family = graphene.Field(IPAddressFamilyType)
|
||||
|
||||
def resolve_family(self, _):
|
||||
@strawberry.field
|
||||
def family(self) -> IPAddressFamilyType:
|
||||
# Note that self, is an instance of models.IPAddress
|
||||
# thus resolves to the address family value.
|
||||
return IPAddressFamilyType(self.family)
|
||||
return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ASN,
|
||||
fields='__all__',
|
||||
filters=ASNFilter
|
||||
)
|
||||
class ASNType(NetBoxObjectType):
|
||||
asn = graphene.Field(BigInt)
|
||||
asn: BigInt
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ASN
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ASNFilterSet
|
||||
@strawberry_django.field
|
||||
def sites(self) -> List[SiteType]:
|
||||
return self.sites.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def providers(self) -> List[ProviderType]:
|
||||
return self.providers.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ASNRange,
|
||||
fields='__all__',
|
||||
filters=ASNRangeFilter
|
||||
)
|
||||
class ASNRangeType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ASNRange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ASNRangeFilterSet
|
||||
start: BigInt
|
||||
end: BigInt
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Aggregate,
|
||||
fields='__all__',
|
||||
filters=AggregateFilter
|
||||
)
|
||||
class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
|
||||
class Meta:
|
||||
model = models.Aggregate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.AggregateFilterSet
|
||||
prefix: str
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.FHRPGroup,
|
||||
fields='__all__',
|
||||
filters=FHRPGroupFilter
|
||||
)
|
||||
class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
|
||||
|
||||
class Meta:
|
||||
model = models.FHRPGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.FHRPGroupFilterSet
|
||||
|
||||
def resolve_auth_type(self, info):
|
||||
return self.auth_type or None
|
||||
@strawberry_django.field
|
||||
def fhrpgroupassignment_set(self) -> List[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.fhrpgroupassignment_set.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.FHRPGroupAssignment,
|
||||
exclude=('interface_type', 'interface_id'),
|
||||
filters=FHRPGroupAssignmentFilter
|
||||
)
|
||||
class FHRPGroupAssignmentType(BaseObjectType):
|
||||
interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType')
|
||||
group: Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.FHRPGroupAssignment
|
||||
exclude = ('interface_type', 'interface_id')
|
||||
filterset_class = filtersets.FHRPGroupAssignmentFilterSet
|
||||
@strawberry_django.field
|
||||
def interface(self) -> Annotated[Union[
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||
], strawberry.union("FHRPGroupInterfaceType")]:
|
||||
return self.interface
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPAddress,
|
||||
exclude=('assigned_object_type', 'assigned_object_id', 'address'),
|
||||
filters=IPAddressFilter
|
||||
)
|
||||
class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
assigned_object = graphene.Field('ipam.graphql.gfk_mixins.IPAddressAssignmentType')
|
||||
address: str
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
nat_inside: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.IPAddress
|
||||
exclude = ('assigned_object_type', 'assigned_object_id')
|
||||
filterset_class = filtersets.IPAddressFilterSet
|
||||
@strawberry_django.field
|
||||
def nat_outside(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.nat_outside.all()
|
||||
|
||||
def resolve_role(self, info):
|
||||
return self.role or None
|
||||
@strawberry_django.field
|
||||
def tunnel_terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.tunnel_terminations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.services.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def assigned_object(self) -> Annotated[Union[
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')],
|
||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||
], strawberry.union("IPAddressAssignmentType")]:
|
||||
return self.assigned_object
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPRange,
|
||||
fields='__all__',
|
||||
filters=IPRangeFilter
|
||||
)
|
||||
class IPRangeType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPRange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IPRangeFilterSet
|
||||
|
||||
def resolve_role(self, info):
|
||||
return self.role or None
|
||||
start_address: str
|
||||
end_address: str
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Prefix,
|
||||
fields='__all__',
|
||||
filters=PrefixFilter
|
||||
)
|
||||
class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
|
||||
class Meta:
|
||||
model = models.Prefix
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.PrefixFilterSet
|
||||
prefix: str
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.RIR,
|
||||
fields='__all__',
|
||||
filters=RIRFilter
|
||||
)
|
||||
class RIRType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RIR
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RIRFilterSet
|
||||
@strawberry_django.field
|
||||
def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asn_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def aggregates(self) -> List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.aggregates.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Role,
|
||||
fields='__all__',
|
||||
filters=RoleFilter
|
||||
)
|
||||
class RoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Role
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RoleFilterSet
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vlans.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.RouteTarget,
|
||||
fields='__all__',
|
||||
filters=RouteTargetFilter
|
||||
)
|
||||
class RouteTargetType(NetBoxObjectType):
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.RouteTarget
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RouteTargetFilterSet
|
||||
@strawberry_django.field
|
||||
def exporting_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.exporting_l2vpns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def exporting_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.exporting_vrfs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def importing_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.importing_vrfs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def importing_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.importing_l2vpns.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Service,
|
||||
fields='__all__',
|
||||
filters=ServiceFilter
|
||||
)
|
||||
class ServiceType(NetBoxObjectType):
|
||||
ports: List[int]
|
||||
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.Service
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ServiceFilterSet
|
||||
@strawberry_django.field
|
||||
def ipaddresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ipaddresses.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ServiceTemplate,
|
||||
fields='__all__',
|
||||
filters=ServiceTemplateFilter
|
||||
)
|
||||
class ServiceTemplateType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ServiceTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ServiceTemplateFilterSet
|
||||
ports: List[int]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VLAN,
|
||||
fields='__all__',
|
||||
filters=VLANFilter
|
||||
)
|
||||
class VLANType(NetBoxObjectType):
|
||||
site: Annotated["SiteType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
group: Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VLAN
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VLANFilterSet
|
||||
@strawberry_django.field
|
||||
def interfaces_as_untagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces_as_untagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces_as_untagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces_as_untagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def wirelesslan_set(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
|
||||
return self.wirelesslan_set.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def interfaces_as_tagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces_as_tagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces_as_tagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces_as_tagged.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VLANGroup,
|
||||
exclude=('scope_type', 'scope_id'),
|
||||
filters=VLANGroupFilter
|
||||
)
|
||||
class VLANGroupType(OrganizationalObjectType):
|
||||
scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType')
|
||||
|
||||
class Meta:
|
||||
model = models.VLANGroup
|
||||
exclude = ('scope_type', 'scope_id')
|
||||
filterset_class = filtersets.VLANGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def vlans(self) -> List[VLANType]:
|
||||
return self.vlans.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def scope(self) -> Annotated[Union[
|
||||
Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')],
|
||||
Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')],
|
||||
Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RackType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("VLANGroupScopeType")]:
|
||||
return self.scope
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VRF,
|
||||
fields='__all__',
|
||||
filters=VRFFilter
|
||||
)
|
||||
class VRFType(NetBoxObjectType):
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VRF
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VRFFilterSet
|
||||
@strawberry_django.field
|
||||
def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_addresses.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def export_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.export_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def import_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.import_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
@ -1,69 +0,0 @@
|
||||
import graphene
|
||||
from dcim.fields import MACAddressField, WWNField
|
||||
from django.db import models
|
||||
from graphene import Dynamic
|
||||
from graphene_django.converter import convert_django_field, get_django_field_description
|
||||
from graphene_django.fields import DjangoConnectionField
|
||||
from ipam.fields import IPAddressField, IPNetworkField
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from .fields import ObjectListField
|
||||
|
||||
|
||||
@convert_django_field.register(TaggableManager)
|
||||
def convert_field_to_tags_list(field, registry=None):
|
||||
"""
|
||||
Register conversion handler for django-taggit's TaggableManager
|
||||
"""
|
||||
return graphene.List(graphene.String)
|
||||
|
||||
|
||||
@convert_django_field.register(IPAddressField)
|
||||
@convert_django_field.register(IPNetworkField)
|
||||
@convert_django_field.register(MACAddressField)
|
||||
@convert_django_field.register(WWNField)
|
||||
def convert_field_to_string(field, registry=None):
|
||||
# TODO: Update to use get_django_field_description under django_graphene v3.0
|
||||
return graphene.String(description=field.help_text, required=not field.null)
|
||||
|
||||
|
||||
@convert_django_field.register(models.ManyToManyField)
|
||||
@convert_django_field.register(models.ManyToManyRel)
|
||||
@convert_django_field.register(models.ManyToOneRel)
|
||||
def convert_field_to_list_or_connection(field, registry=None):
|
||||
"""
|
||||
From graphene_django.converter.py we need to monkey-patch this to return
|
||||
our ObjectListField with filtering support instead of DjangoListField
|
||||
"""
|
||||
model = field.related_model
|
||||
|
||||
def dynamic_type():
|
||||
_type = registry.get_type_for_model(model)
|
||||
if not _type:
|
||||
return
|
||||
|
||||
if isinstance(field, models.ManyToManyField):
|
||||
description = get_django_field_description(field)
|
||||
else:
|
||||
description = get_django_field_description(field.field)
|
||||
|
||||
# If there is a connection, we should transform the field
|
||||
# into a DjangoConnectionField
|
||||
if _type._meta.connection:
|
||||
# Use a DjangoFilterConnectionField if there are
|
||||
# defined filter_fields or a filterset_class in the
|
||||
# DjangoObjectType Meta
|
||||
if _type._meta.filter_fields or _type._meta.filterset_class:
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
|
||||
return DjangoFilterConnectionField(_type, required=True, description=description)
|
||||
|
||||
return DjangoConnectionField(_type, required=True, description=description)
|
||||
|
||||
return ObjectListField(
|
||||
_type,
|
||||
required=True, # A Set is always returned, never None.
|
||||
description=description,
|
||||
)
|
||||
|
||||
return Dynamic(dynamic_type)
|
@ -1,70 +0,0 @@
|
||||
from functools import partial
|
||||
|
||||
import graphene
|
||||
from graphene_django import DjangoListField
|
||||
from .utils import get_graphene_type
|
||||
|
||||
__all__ = (
|
||||
'ObjectField',
|
||||
'ObjectListField',
|
||||
)
|
||||
|
||||
|
||||
class ObjectField(graphene.Field):
|
||||
"""
|
||||
Retrieve a single object, identified by its numeric ID.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
if 'id' not in kwargs:
|
||||
kwargs['id'] = graphene.Int(required=True)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def object_resolver(django_object_type, root, info, **args):
|
||||
"""
|
||||
Return an object given its numeric ID.
|
||||
"""
|
||||
manager = django_object_type._meta.model._default_manager
|
||||
queryset = django_object_type.get_queryset(manager, info)
|
||||
|
||||
return queryset.get(**args)
|
||||
|
||||
def get_resolver(self, parent_resolver):
|
||||
return partial(self.object_resolver, self._type)
|
||||
|
||||
|
||||
class ObjectListField(DjangoListField):
|
||||
"""
|
||||
Retrieve a list of objects, optionally filtered by one or more FilterSet filters.
|
||||
"""
|
||||
def __init__(self, _type, *args, **kwargs):
|
||||
filter_kwargs = {}
|
||||
|
||||
# Get FilterSet kwargs
|
||||
filterset_class = getattr(_type._meta, 'filterset_class', None)
|
||||
if filterset_class:
|
||||
for filter_name, filter_field in filterset_class.get_filters().items():
|
||||
field_type = get_graphene_type(type(filter_field))
|
||||
filter_kwargs[filter_name] = graphene.Argument(field_type)
|
||||
|
||||
super().__init__(_type, args=filter_kwargs, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def list_resolver(django_object_type, resolver, default_manager, root, info, **args):
|
||||
queryset = super(ObjectListField, ObjectListField).list_resolver(django_object_type, resolver, default_manager, root, info, **args)
|
||||
|
||||
# if there are no filter params then don't need to filter
|
||||
if not args:
|
||||
return queryset
|
||||
|
||||
filterset_class = django_object_type._meta.filterset_class
|
||||
if filterset_class:
|
||||
filterset = filterset_class(data=args if args else None, queryset=queryset, request=info.context)
|
||||
|
||||
if not filterset.is_valid():
|
||||
return queryset.none()
|
||||
return filterset.qs
|
||||
|
||||
return queryset
|
198
netbox/netbox/graphql/filter_mixins.py
Normal file
198
netbox/netbox/graphql/filter_mixins.py
Normal file
@ -0,0 +1,198 @@
|
||||
from functools import partial, partialmethod, wraps
|
||||
from typing import List
|
||||
|
||||
import django_filters
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry import auto
|
||||
from ipam.fields import ASNField
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from utilities.fields import ColorField, CounterCacheField
|
||||
from utilities.filters import *
|
||||
|
||||
|
||||
def map_strawberry_type(field):
|
||||
should_create_function = False
|
||||
attr_type = None
|
||||
|
||||
# NetBox Filter types - put base classes after derived classes
|
||||
if isinstance(field, ContentTypeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif isinstance(field, MultiValueArrayFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueCharFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueDateFilter):
|
||||
attr_type = auto
|
||||
elif isinstance(field, MultiValueDateTimeFilter):
|
||||
attr_type = auto
|
||||
elif isinstance(field, MultiValueDecimalFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueMACAddressFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueNumberFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueTimeFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueWWNFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, NullableCharFieldFilter):
|
||||
pass
|
||||
elif isinstance(field, NumericArrayFilter):
|
||||
should_create_function = True
|
||||
attr_type = int
|
||||
elif isinstance(field, TreeNodeMultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
|
||||
# From django_filters - ordering of these matters as base classes must
|
||||
# come after derived classes so the base class doesn't get matched first
|
||||
# a pass for the check (no attr_type) means we don't currently handle
|
||||
# or use that type
|
||||
elif issubclass(type(field), django_filters.OrderingFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BaseRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BaseInFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.LookupChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.AllValuesMultipleFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.AllValuesFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.TimeRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.IsoDateTimeFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateTimeFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.RangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.NumericRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.NumberFilter):
|
||||
should_create_function = True
|
||||
attr_type = int
|
||||
elif issubclass(type(field), django_filters.ModelMultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif issubclass(type(field), django_filters.ModelChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DurationFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.IsoDateTimeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.DateTimeFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.TimeFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.DateFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.TypedMultipleChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.MultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif issubclass(type(field), django_filters.TypedChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.ChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BooleanFilter):
|
||||
should_create_function = True
|
||||
attr_type = bool | None
|
||||
elif issubclass(type(field), django_filters.UUIDFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.CharFilter):
|
||||
# looks like only used by 'q'
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
|
||||
return should_create_function, attr_type
|
||||
|
||||
|
||||
def autotype_decorator(filterset):
|
||||
"""
|
||||
Decorator used to auto creates a dataclass used by Strawberry based on a filterset.
|
||||
Must go after the Strawberry decorator as follows:
|
||||
|
||||
@strawberry_django.filter(models.Example, lookups=True)
|
||||
@autotype_decorator(filtersets.ExampleFilterSet)
|
||||
class ExampleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
The Filter itself must be derived from BaseFilterMixin. For items listed in meta.fields
|
||||
of the filterset, usually just a type specifier is generated, so for
|
||||
`fields = [created, ]` the dataclass would be:
|
||||
|
||||
class ExampleFilter(BaseFilterMixin):
|
||||
created: auto
|
||||
|
||||
For other filter fields a function needs to be created for Strawberry with the
|
||||
naming convention `filter_{fieldname}` which is auto detected and called by
|
||||
Strawberry, this function uses the filterset to handle the query.
|
||||
"""
|
||||
def create_attribute_and_function(cls, fieldname, attr_type, should_create_function):
|
||||
if fieldname not in cls.__annotations__ and attr_type:
|
||||
cls.__annotations__[fieldname] = attr_type
|
||||
|
||||
filter_name = f"filter_{fieldname}"
|
||||
if should_create_function and not hasattr(cls, filter_name):
|
||||
filter_by_filterset = getattr(cls, 'filter_by_filterset')
|
||||
setattr(cls, filter_name, partialmethod(filter_by_filterset, key=fieldname))
|
||||
|
||||
def wrapper(cls):
|
||||
cls.filterset = filterset
|
||||
fields = filterset.get_fields()
|
||||
model = filterset._meta.model
|
||||
for fieldname in fields.keys():
|
||||
should_create_function = False
|
||||
attr_type = auto
|
||||
if fieldname not in cls.__annotations__:
|
||||
field = model._meta.get_field(fieldname)
|
||||
if isinstance(field, CounterCacheField):
|
||||
should_create_function = True
|
||||
attr_type = BigInt | None
|
||||
elif isinstance(field, ASNField):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, ColorField):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
|
||||
create_attribute_and_function(cls, fieldname, attr_type, should_create_function)
|
||||
|
||||
declared_filters = filterset.declared_filters
|
||||
for fieldname, field in declared_filters.items():
|
||||
|
||||
should_create_function, attr_type = map_strawberry_type(field)
|
||||
if attr_type is None:
|
||||
raise NotImplementedError(f"GraphQL Filter field unknown: {fieldname}: {field}")
|
||||
|
||||
create_attribute_and_function(cls, fieldname, attr_type, should_create_function)
|
||||
|
||||
return cls
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@strawberry.input
|
||||
class BaseFilterMixin:
|
||||
|
||||
def filter_by_filterset(self, queryset, key):
|
||||
return self.filterset(data={key: getattr(self, key)}, queryset=queryset).qs
|
@ -1,23 +1,10 @@
|
||||
from graphene import Scalar
|
||||
from graphql.language import ast
|
||||
from graphene.types.scalars import MAX_INT, MIN_INT
|
||||
from typing import Union
|
||||
|
||||
import strawberry
|
||||
|
||||
class BigInt(Scalar):
|
||||
"""
|
||||
Handle any BigInts
|
||||
"""
|
||||
@staticmethod
|
||||
def to_float(value):
|
||||
num = int(value)
|
||||
if num > MAX_INT or num < MIN_INT:
|
||||
return float(num)
|
||||
return num
|
||||
|
||||
serialize = to_float
|
||||
parse_value = to_float
|
||||
|
||||
@staticmethod
|
||||
def parse_literal(node):
|
||||
if isinstance(node, ast.IntValue):
|
||||
return BigInt.to_float(node.value)
|
||||
BigInt = strawberry.scalar(
|
||||
Union[int, str], # type: ignore
|
||||
serialize=lambda v: int(v),
|
||||
parse_value=lambda v: str(v),
|
||||
description="BigInt field",
|
||||
)
|
||||
|
@ -1,4 +1,6 @@
|
||||
import graphene
|
||||
import strawberry
|
||||
from strawberry_django.optimizer import DjangoOptimizerExtension
|
||||
from strawberry.schema.config import StrawberryConfig
|
||||
|
||||
from circuits.graphql.schema import CircuitsQuery
|
||||
from core.graphql.schema import CoreQuery
|
||||
@ -13,6 +15,7 @@ from vpn.graphql.schema import VPNQuery
|
||||
from wireless.graphql.schema import WirelessQuery
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Query(
|
||||
UsersQuery,
|
||||
CircuitsQuery,
|
||||
@ -25,9 +28,14 @@ class Query(
|
||||
VPNQuery,
|
||||
WirelessQuery,
|
||||
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
||||
graphene.ObjectType
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
schema = graphene.Schema(query=Query, auto_camelcase=False)
|
||||
schema = strawberry.Schema(
|
||||
query=Query,
|
||||
config=StrawberryConfig(auto_camel_case=False),
|
||||
extensions=[
|
||||
DjangoOptimizerExtension,
|
||||
]
|
||||
)
|
||||
|
@ -1,4 +1,8 @@
|
||||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
from strawberry import auto
|
||||
import strawberry_django
|
||||
|
||||
from core.models import ObjectType as ObjectType_
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -8,13 +12,10 @@ from extras.graphql.mixins import (
|
||||
JournalEntriesMixin,
|
||||
TagsMixin,
|
||||
)
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
__all__ = (
|
||||
'BaseObjectType',
|
||||
'ContentTypeType',
|
||||
'ObjectType',
|
||||
'ObjectTypeType',
|
||||
'OrganizationalObjectType',
|
||||
'NetBoxObjectType',
|
||||
)
|
||||
@ -24,26 +25,27 @@ __all__ = (
|
||||
# Base types
|
||||
#
|
||||
|
||||
class BaseObjectType(DjangoObjectType):
|
||||
@strawberry.type
|
||||
class BaseObjectType:
|
||||
"""
|
||||
Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions.
|
||||
"""
|
||||
display = graphene.String()
|
||||
class_type = graphene.String()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
def get_queryset(cls, queryset, info, **kwargs):
|
||||
# Enforce object permissions on the queryset
|
||||
return queryset.restrict(info.context.user, 'view')
|
||||
if hasattr(queryset, 'restrict'):
|
||||
return queryset.restrict(info.context.request.user, 'view')
|
||||
else:
|
||||
return queryset
|
||||
|
||||
def resolve_display(parent, info, **kwargs):
|
||||
return str(parent)
|
||||
@strawberry_django.field
|
||||
def display(self) -> str:
|
||||
return str(self)
|
||||
|
||||
def resolve_class_type(parent, info, **kwargs):
|
||||
return parent.__class__.__name__
|
||||
@strawberry_django.field
|
||||
def class_type(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class ObjectType(
|
||||
@ -53,8 +55,7 @@ class ObjectType(
|
||||
"""
|
||||
Base GraphQL object type for unclassified models which support change logging
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationalObjectType(
|
||||
@ -66,8 +67,7 @@ class OrganizationalObjectType(
|
||||
"""
|
||||
Base type for organizational models
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
class NetBoxObjectType(
|
||||
@ -80,23 +80,24 @@ class NetBoxObjectType(
|
||||
"""
|
||||
GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags.
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Miscellaneous types
|
||||
#
|
||||
|
||||
class ContentTypeType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ('id', 'app_label', 'model')
|
||||
@strawberry_django.type(
|
||||
ContentType,
|
||||
fields=['id', 'app_label', 'model'],
|
||||
)
|
||||
class ContentTypeType:
|
||||
pass
|
||||
|
||||
|
||||
class ObjectTypeType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = ObjectType_
|
||||
fields = ('id', 'app_label', 'model')
|
||||
@strawberry_django.type(
|
||||
ObjectType_,
|
||||
fields=['id', 'app_label', 'model'],
|
||||
)
|
||||
class ObjectTypeType:
|
||||
pass
|
||||
|
@ -1,25 +0,0 @@
|
||||
import graphene
|
||||
from django_filters import filters
|
||||
|
||||
|
||||
def get_graphene_type(filter_cls):
|
||||
"""
|
||||
Return the appropriate Graphene scalar type for a django_filters Filter
|
||||
"""
|
||||
if issubclass(filter_cls, filters.BooleanFilter):
|
||||
field_type = graphene.Boolean
|
||||
elif issubclass(filter_cls, filters.NumberFilter):
|
||||
# TODO: Floats? BigInts?
|
||||
field_type = graphene.Int
|
||||
elif issubclass(filter_cls, filters.DateFilter):
|
||||
field_type = graphene.Date
|
||||
elif issubclass(filter_cls, filters.DateTimeFilter):
|
||||
field_type = graphene.DateTime
|
||||
else:
|
||||
field_type = graphene.String
|
||||
|
||||
# Multi-value filters should be handled as lists
|
||||
if issubclass(filter_cls, filters.MultipleChoiceFilter):
|
||||
return graphene.List(field_type)
|
||||
|
||||
return field_type
|
@ -1,20 +1,26 @@
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.http import HttpResponseNotFound, HttpResponseForbidden
|
||||
from django.http import HttpResponse
|
||||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from graphene_django.views import GraphQLView as GraphQLView_
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from strawberry.django.views import GraphQLView
|
||||
|
||||
from netbox.api.authentication import TokenAuthentication
|
||||
from netbox.config import get_config
|
||||
|
||||
|
||||
class GraphQLView(GraphQLView_):
|
||||
class NetBoxGraphQLView(GraphQLView):
|
||||
"""
|
||||
Extends graphene_django's GraphQLView to support DRF's token-based authentication.
|
||||
Extends strawberry's GraphQLView to support DRF's token-based authentication.
|
||||
"""
|
||||
graphiql_template = 'graphiql.html'
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
config = get_config()
|
||||
|
||||
@ -34,11 +40,15 @@ class GraphQLView(GraphQLView_):
|
||||
|
||||
# Enforce LOGIN_REQUIRED
|
||||
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||
|
||||
# If this is a human user, send a redirect to the login page
|
||||
if self.request_wants_html(request):
|
||||
if request.accepts("text/html"):
|
||||
return redirect_to_login(reverse('graphql'))
|
||||
|
||||
return HttpResponseForbidden("No credentials provided.")
|
||||
else:
|
||||
return HttpResponseForbidden("No credentials provided.")
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def render_graphql_ide(self, request):
|
||||
template = loader.get_template("graphiql.html")
|
||||
context = {"SUBSCRIPTION_ENABLED": json.dumps(self.subscriptions_enabled)}
|
||||
|
||||
return HttpResponse(template.render(context, request))
|
||||
|
@ -73,7 +73,7 @@ def register_graphql_schema(graphql_schema):
|
||||
"""
|
||||
Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
|
||||
"""
|
||||
registry['plugins']['graphql_schemas'].append(graphql_schema)
|
||||
registry['plugins']['graphql_schemas'].extend(graphql_schema)
|
||||
|
||||
|
||||
def register_user_preferences(plugin_name, preferences):
|
||||
|
@ -365,12 +365,11 @@ INSTALLED_APPS = [
|
||||
'django.forms',
|
||||
'corsheaders',
|
||||
'debug_toolbar',
|
||||
'graphiql_debug_toolbar',
|
||||
'django_filters',
|
||||
'django_htmx',
|
||||
'django_tables2',
|
||||
'django_prometheus',
|
||||
'graphene_django',
|
||||
'strawberry_django',
|
||||
'mptt',
|
||||
'rest_framework',
|
||||
'social_django',
|
||||
@ -398,7 +397,7 @@ if DJANGO_ADMIN_ENABLED:
|
||||
|
||||
# Middleware
|
||||
MIDDLEWARE = [
|
||||
'graphiql_debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
"strawberry_django.middlewares.debug_toolbar.DebugToolbarMiddleware",
|
||||
'django_prometheus.middleware.PrometheusBeforeMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
@ -674,17 +673,6 @@ SPECTACULAR_SETTINGS = {
|
||||
'POSTPROCESSING_HOOKS': [],
|
||||
}
|
||||
|
||||
#
|
||||
# Graphene
|
||||
#
|
||||
|
||||
GRAPHENE = {
|
||||
# Avoids naming collision on models with 'type' field; see
|
||||
# https://github.com/graphql-python/graphene-django/issues/185
|
||||
'DJANGO_CHOICE_FIELD_ENUM_V3_NAMING': True,
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Django RQ (events backend)
|
||||
#
|
||||
@ -749,6 +737,13 @@ if not ENABLE_LOCALIZATION:
|
||||
USE_I18N = False
|
||||
USE_L10N = False
|
||||
|
||||
#
|
||||
# Strawberry (GraphQL)
|
||||
#
|
||||
STRAWBERRY_DJANGO = {
|
||||
"TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True,
|
||||
}
|
||||
|
||||
#
|
||||
# Plugins
|
||||
#
|
||||
|
@ -1,21 +1,26 @@
|
||||
import graphene
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from typing import List
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class DummyModelType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DummyModel
|
||||
fields = '__all__'
|
||||
@strawberry_django.type(
|
||||
models.DummyModel,
|
||||
fields='__all__',
|
||||
)
|
||||
class DummyModelType:
|
||||
pass
|
||||
|
||||
|
||||
class DummyQuery(graphene.ObjectType):
|
||||
dummymodel = ObjectField(DummyModelType)
|
||||
dummymodel_list = ObjectListField(DummyModelType)
|
||||
@strawberry.type
|
||||
class DummyQuery:
|
||||
@strawberry.field
|
||||
def dummymodel(self, id: int) -> DummyModelType:
|
||||
return None
|
||||
dummymodel_list: List[DummyModelType] = strawberry_django.field()
|
||||
|
||||
|
||||
schema = DummyQuery
|
||||
schema = [
|
||||
DummyQuery,
|
||||
]
|
||||
|
@ -1,16 +1,16 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.static import serve
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||
|
||||
from account.views import LoginView, LogoutView
|
||||
from netbox.api.views import APIRootView, StatusView
|
||||
from netbox.graphql.schema import schema
|
||||
from netbox.graphql.views import GraphQLView
|
||||
from netbox.graphql.views import NetBoxGraphQLView
|
||||
from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
|
||||
from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
|
||||
from strawberry.django.views import GraphQLView
|
||||
|
||||
_patterns = [
|
||||
|
||||
@ -60,7 +60,7 @@ _patterns = [
|
||||
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'),
|
||||
|
||||
# GraphQL
|
||||
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema)), name='graphql'),
|
||||
path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'),
|
||||
|
||||
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
||||
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||
|
@ -1,5 +1,8 @@
|
||||
const esbuild = require('esbuild');
|
||||
const { sassPlugin } = require('esbuild-sass-plugin');
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
const copyFilePromise = util.promisify(fs.copyFile);
|
||||
|
||||
// Bundler options common to all bundle jobs.
|
||||
const options = {
|
||||
@ -14,24 +17,57 @@ const options = {
|
||||
// Get CLI arguments for optional overrides.
|
||||
const ARGS = process.argv.slice(2);
|
||||
|
||||
function copyFiles(files) {
|
||||
return Promise.all(files.map(f => {
|
||||
return copyFilePromise(f.source, f.dest);
|
||||
}));
|
||||
}
|
||||
|
||||
async function bundleGraphIQL() {
|
||||
let fileMap = [
|
||||
{
|
||||
source: './node_modules/react/umd/react.production.min.js',
|
||||
dest: './dist/graphiql/react.production.min.js'
|
||||
},
|
||||
{
|
||||
source: './node_modules/react-dom/umd/react-dom.production.min.js',
|
||||
dest: './dist/graphiql/react-dom.production.min.js'
|
||||
},
|
||||
{
|
||||
source: './node_modules/js-cookie/dist/js.cookie.min.js',
|
||||
dest: './dist/graphiql/js.cookie.min.js'
|
||||
},
|
||||
{
|
||||
source: './node_modules/graphiql/graphiql.min.js',
|
||||
dest: './dist/graphiql/graphiql.min.js'
|
||||
},
|
||||
{
|
||||
source: './node_modules/@graphiql/plugin-explorer/dist/index.umd.js',
|
||||
dest: './dist/graphiql/index.umd.js'
|
||||
},
|
||||
{
|
||||
source: './node_modules/graphiql/graphiql.min.css',
|
||||
dest: './dist/graphiql/graphiql.min.css'
|
||||
},
|
||||
{
|
||||
source: './node_modules/@graphiql/plugin-explorer/dist/style.css',
|
||||
dest: './dist/graphiql/plugin-explorer-style.css'
|
||||
}
|
||||
];
|
||||
|
||||
try {
|
||||
const result = await esbuild.build({
|
||||
...options,
|
||||
entryPoints: {
|
||||
graphiql: 'netbox-graphiql/index.ts',
|
||||
},
|
||||
target: 'es2016',
|
||||
define: {
|
||||
global: 'window',
|
||||
},
|
||||
});
|
||||
if (result.errors.length === 0) {
|
||||
console.log(`✅ Bundled source file 'netbox-graphiql/index.ts' to 'graphiql.js'`);
|
||||
if (!fs.existsSync('./dist/graphiql/')) {
|
||||
fs.mkdirSync('./dist/graphiql/');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
copyFiles(fileMap).then(() => {
|
||||
console.log('✅ Copied graphiql files');
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,7 +113,6 @@ async function bundleStyles() {
|
||||
'netbox': 'styles/netbox.scss',
|
||||
rack_elevation: 'styles/svg/rack_elevation.scss',
|
||||
cable_trace: 'styles/svg/cable_trace.scss',
|
||||
graphiql: 'netbox-graphiql/graphiql.scss',
|
||||
};
|
||||
const pluginOptions = { outputStyle: 'compressed' };
|
||||
// Allow cache disabling.
|
||||
|
BIN
netbox/project-static/dist/graphiql.css
vendored
BIN
netbox/project-static/dist/graphiql.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/graphiql.min.css
vendored
Normal file
BIN
netbox/project-static/dist/graphiql.min.css
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/graphiql.min.js
vendored
Normal file
BIN
netbox/project-static/dist/graphiql.min.js
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/index.umd.js
vendored
Normal file
BIN
netbox/project-static/dist/index.umd.js
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/js.cookie.min.js
vendored
Normal file
BIN
netbox/project-static/dist/js.cookie.min.js
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/plugin-explorer-style.css
vendored
Normal file
BIN
netbox/project-static/dist/plugin-explorer-style.css
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/react-dom.production.min.js
vendored
Normal file
BIN
netbox/project-static/dist/react-dom.production.min.js
vendored
Normal file
Binary file not shown.
BIN
netbox/project-static/dist/react.production.min.js
vendored
Normal file
BIN
netbox/project-static/dist/react.production.min.js
vendored
Normal file
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
// Rather than use CDNs to include GraphiQL dependencies, import and bundle the dependencies so
|
||||
// they can be locally served.
|
||||
@import '../node_modules/graphiql/graphiql.css';
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Rather than use CDNs to include GraphiQL dependencies, import and bundle the dependencies so
|
||||
* they can be locally served.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import 'graphql';
|
||||
import GraphiQL from 'graphiql';
|
||||
import SubscriptionsTransportWs from 'subscriptions-transport-ws';
|
||||
|
||||
window.React = React;
|
||||
window.ReactDOM = ReactDOM;
|
||||
// @ts-expect-error Assigning to window is required for graphene-django
|
||||
window.SubscriptionsTransportWs = SubscriptionsTransportWs;
|
||||
// @ts-expect-error Assigning to window is required for graphene-django
|
||||
window.GraphiQL = GraphiQL;
|
@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "netbox-graphiql",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "NetBox GraphiQL Custom Front End",
|
||||
"main": "dist/graphiql.js",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"graphiql": "1.8.9",
|
||||
"graphql": ">= v14.5.0 <= 15.5.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"subscriptions-transport-ws": "0.9.18",
|
||||
"whatwg-fetch": "3.6.2"
|
||||
"graphiql": "3.0.9",
|
||||
"graphql": "16.8.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"@graphiql/plugin-explorer": "1.0.2"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{% comment %}
|
||||
This template derives from the graphene-django project:
|
||||
https://github.com/graphql-python/graphene-django/blob/main/graphene_django/templates/graphene/graphiql.html
|
||||
This template derives from the strawberry-graphql project:
|
||||
https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/static/graphiql.html
|
||||
{% endcomment %}
|
||||
<!--
|
||||
The request to this GraphQL server provided the header "Accept: text/html"
|
||||
@ -11,36 +11,130 @@ add "&raw" to the end of the URL within a browser.
|
||||
-->
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<title>GraphiQL | NetBox</title>
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,
|
||||
<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>
|
||||
<!-- Strawberry Emoji as a HTML Entity (hex) -->
|
||||
<text y=%22.9em%22 font-size=%2280%22>🍓</text>
|
||||
</svg>"
|
||||
/>
|
||||
<style>
|
||||
html, body, #editor {
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#graphiql {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.docExplorerHide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.doc-explorer-contents {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.docExplorerWrap {
|
||||
width: unset !important;
|
||||
min-width: unset !important;
|
||||
}
|
||||
|
||||
.graphiql-explorer-actions select {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
<link href="{% static 'graphiql.css'%}" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="{% static 'graphql.ico' %}" />
|
||||
<title>GraphiQL | NetBox</title>
|
||||
|
||||
<script src="{% static 'graphiql/react.production.min.js' %}"></script>
|
||||
<script src="{% static 'graphiql/react-dom.production.min.js' %}"></script>
|
||||
<script src="{% static 'graphiql/js.cookie.min.js' %}"></script>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'graphiql/graphiql.min.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'graphiql/plugin-explorer-style.css' %}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="editor"></div>
|
||||
{% csrf_token %}
|
||||
<script type="application/javascript">
|
||||
window.GRAPHENE_SETTINGS = {
|
||||
{% if subscription_path %}
|
||||
subscriptionPath: "{{subscription_path}}",
|
||||
{% endif %}
|
||||
graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }},
|
||||
};
|
||||
<div id="graphiql" class="graphiql-container">Loading...</div>
|
||||
<script src="{% static 'graphiql/graphiql.min.js' %}"></script>
|
||||
<script src="{% static 'graphiql/index.umd.js' %}"></script>
|
||||
|
||||
<script>
|
||||
const EXAMPLE_QUERY = `# Welcome to GraphiQL 🍓
|
||||
#
|
||||
# GraphiQL is an in-browser tool for writing, validating, and
|
||||
# testing GraphQL queries.
|
||||
#
|
||||
# Type queries into this side of the screen, and you will see intelligent
|
||||
# typeaheads aware of the current GraphQL type schema and live syntax and
|
||||
# validation errors highlighted within the text.
|
||||
#
|
||||
# GraphQL queries typically start with a "{" character. Lines that starts
|
||||
# with a # are ignored.
|
||||
#
|
||||
# An example GraphQL query might look like:
|
||||
#
|
||||
# {
|
||||
# field(arg: "value") {
|
||||
# subField
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Keyboard shortcuts:
|
||||
#
|
||||
# Run Query: Ctrl-Enter (or press the play button above)
|
||||
#
|
||||
# Auto Complete: Ctrl-Space (or just start typing)
|
||||
#
|
||||
`;
|
||||
|
||||
const fetchURL = window.location.href;
|
||||
|
||||
function httpUrlToWebSockeUrl(url) {
|
||||
const parsedURL = new URL(url);
|
||||
const protocol = parsedURL.protocol === "http:" ? "ws:" : "wss:";
|
||||
parsedURL.protocol = protocol;
|
||||
parsedURL.hash = "";
|
||||
return parsedURL.toString();
|
||||
}
|
||||
|
||||
const headers = {};
|
||||
const csrfToken = Cookies.get("csrftoken");
|
||||
|
||||
if (csrfToken) {
|
||||
headers["x-csrftoken"] = csrfToken;
|
||||
}
|
||||
|
||||
const subscriptionsEnabled = JSON.parse("{{ SUBSCRIPTION_ENABLED }}");
|
||||
const subscriptionUrl = subscriptionsEnabled
|
||||
? httpUrlToWebSockeUrl(fetchURL)
|
||||
: null;
|
||||
|
||||
const fetcher = GraphiQL.createFetcher({
|
||||
url: fetchURL,
|
||||
headers: headers,
|
||||
subscriptionUrl,
|
||||
});
|
||||
|
||||
const explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("graphiql"));
|
||||
|
||||
root.render(
|
||||
React.createElement(GraphiQL, {
|
||||
fetcher: fetcher,
|
||||
defaultEditorToolsVisibility: true,
|
||||
plugins: [explorerPlugin],
|
||||
inputValueDeprecation: true,
|
||||
}),
|
||||
);
|
||||
</script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{% static 'graphiql.js' %}"
|
||||
onerror="window.location='{% url 'media_failure' %}?filename=graphiql.js'">
|
||||
</script>
|
||||
<script src="{% static 'graphene_django/graphiql.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -127,11 +127,10 @@ class ContactAssignmentFilterSet(NetBoxModelFilterSet):
|
||||
to_field_name='slug',
|
||||
label=_('Contact role (slug)'),
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = ContactAssignment
|
||||
fields = ('id', 'object_type_id', 'object_id', 'priority', 'tag')
|
||||
fields = ('id', 'object_type_id', 'object_id', 'priority')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
49
netbox/tenancy/graphql/filters.py
Normal file
49
netbox/tenancy/graphql/filters.py
Normal file
@ -0,0 +1,49 @@
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
from tenancy import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'TenantFilter',
|
||||
'TenantGroupFilter',
|
||||
'ContactFilter',
|
||||
'ContactRoleFilter',
|
||||
'ContactGroupFilter',
|
||||
'ContactAssignmentFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Tenant, lookups=True)
|
||||
@autotype_decorator(filtersets.TenantFilterSet)
|
||||
class TenantFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.TenantGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.TenantGroupFilterSet)
|
||||
class TenantGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Contact, lookups=True)
|
||||
@autotype_decorator(filtersets.ContactFilterSet)
|
||||
class ContactFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ContactRole, lookups=True)
|
||||
@autotype_decorator(filtersets.ContactRoleFilterSet)
|
||||
class ContactRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ContactGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.ContactGroupFilterSet)
|
||||
class ContactGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ContactAssignment, lookups=True)
|
||||
@autotype_decorator(filtersets.ContactAssignmentFilterSet)
|
||||
class ContactAssignmentFilter(BaseFilterMixin):
|
||||
pass
|
17
netbox/tenancy/graphql/mixins.py
Normal file
17
netbox/tenancy/graphql/mixins.py
Normal file
@ -0,0 +1,17 @@
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentsMixin',
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ContactAssignmentsMixin:
|
||||
|
||||
@strawberry_django.field
|
||||
def assignments(self) -> List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return self.assignments.all()
|
@ -1,44 +1,40 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from tenancy import models
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class TenancyQuery(graphene.ObjectType):
|
||||
tenant = ObjectField(TenantType)
|
||||
tenant_list = ObjectListField(TenantType)
|
||||
@strawberry.type
|
||||
class TenancyQuery:
|
||||
@strawberry.field
|
||||
def tenant(self, id: int) -> TenantType:
|
||||
return models.Tenant.objects.get(pk=id)
|
||||
tenant_list: List[TenantType] = strawberry_django.field()
|
||||
|
||||
def resolve_tenant_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Tenant.objects.all(), info)
|
||||
@strawberry.field
|
||||
def tenant_group(self, id: int) -> TenantGroupType:
|
||||
return models.TenantGroup.objects.get(pk=id)
|
||||
tenant_group_list: List[TenantGroupType] = strawberry_django.field()
|
||||
|
||||
tenant_group = ObjectField(TenantGroupType)
|
||||
tenant_group_list = ObjectListField(TenantGroupType)
|
||||
@strawberry.field
|
||||
def contact(self, id: int) -> ContactType:
|
||||
return models.Contact.objects.get(pk=id)
|
||||
contact_list: List[ContactType] = strawberry_django.field()
|
||||
|
||||
def resolve_tenant_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.TenantGroup.objects.all(), info)
|
||||
@strawberry.field
|
||||
def contact_role(self, id: int) -> ContactRoleType:
|
||||
return models.ContactRole.objects.get(pk=id)
|
||||
contact_role_list: List[ContactRoleType] = strawberry_django.field()
|
||||
|
||||
contact = ObjectField(ContactType)
|
||||
contact_list = ObjectListField(ContactType)
|
||||
@strawberry.field
|
||||
def contact_group(self, id: int) -> ContactGroupType:
|
||||
return models.ContactGroup.objects.get(pk=id)
|
||||
contact_group_list: List[ContactGroupType] = strawberry_django.field()
|
||||
|
||||
def resolve_contact_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Contact.objects.all(), info)
|
||||
|
||||
contact_role = ObjectField(ContactRoleType)
|
||||
contact_role_list = ObjectListField(ContactRoleType)
|
||||
|
||||
def resolve_contact_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ContactRole.objects.all(), info)
|
||||
|
||||
contact_group = ObjectField(ContactGroupType)
|
||||
contact_group_list = ObjectListField(ContactGroupType)
|
||||
|
||||
def resolve_contact_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ContactGroup.objects.all(), info)
|
||||
|
||||
contact_assignment = ObjectField(ContactAssignmentType)
|
||||
contact_assignment_list = ObjectListField(ContactAssignmentType)
|
||||
|
||||
def resolve_contact_assignment_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ContactAssignment.objects.all(), info)
|
||||
@strawberry.field
|
||||
def contact_assignment(self, id: int) -> ContactAssignmentType:
|
||||
return models.ContactAssignment.objects.get(pk=id)
|
||||
contact_assignment_list: List[ContactAssignmentType] = strawberry_django.field()
|
||||
|
@ -1,8 +1,13 @@
|
||||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
||||
from tenancy import filtersets, models
|
||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from tenancy import models
|
||||
from .mixins import ContactAssignmentsMixin
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'ContactAssignmentType',
|
||||
@ -14,64 +19,169 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ContactAssignmentsMixin:
|
||||
assignments = graphene.List('tenancy.graphql.types.ContactAssignmentType')
|
||||
|
||||
def resolve_assignments(self, info):
|
||||
return self.assignments.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
#
|
||||
# Tenants
|
||||
#
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Tenant,
|
||||
fields='__all__',
|
||||
filters=TenantFilter
|
||||
)
|
||||
class TenantType(NetBoxObjectType):
|
||||
group: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.Tenant
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TenantFilterSet
|
||||
@strawberry_django.field
|
||||
def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.sites.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vlans.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
|
||||
return self.wireless_lans.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def route_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.route_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.locations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def rackreservations(self) -> List[Annotated["RackReservationType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.rackreservations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def racks(self) -> List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.racks.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vdcs(self) -> List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.vdcs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def cables(self) -> List[Annotated["CableType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.cables.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.virtual_machines.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vrfs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asn_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def wireless_links(self) -> List[Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')]]:
|
||||
return self.wireless_links.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def aggregates(self) -> List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.aggregates.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def power_feeds(self) -> List[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.power_feeds.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.devices.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.tunnels.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_addresses.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.clusters.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.l2vpns.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.TenantGroup,
|
||||
fields='__all__',
|
||||
filters=TenantGroupFilter
|
||||
)
|
||||
class TenantGroupType(OrganizationalObjectType):
|
||||
parent: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.TenantGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TenantGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def tenants(self) -> List[TenantType]:
|
||||
return self.tenants.all()
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Contact,
|
||||
fields='__all__',
|
||||
filters=ContactFilter
|
||||
)
|
||||
class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Contact
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactFilterSet
|
||||
group: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ContactRole,
|
||||
fields='__all__',
|
||||
filters=ContactRoleFilter
|
||||
)
|
||||
class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ContactRole
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactRoleFilterSet
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ContactGroup,
|
||||
fields='__all__',
|
||||
filters=ContactGroupFilter
|
||||
)
|
||||
class ContactGroupType(OrganizationalObjectType):
|
||||
parent: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ContactGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def contacts(self) -> List[ContactType]:
|
||||
return self.contacts.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ContactAssignment,
|
||||
fields='__all__',
|
||||
filters=ContactAssignmentFilter
|
||||
)
|
||||
class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ContactAssignment
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ContactAssignmentFilterSet
|
||||
object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
contact: Annotated["ContactType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
role: Annotated["ContactRoleType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
@ -1,4 +1,5 @@
|
||||
import django_filters
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
|
23
netbox/users/graphql/filters.py
Normal file
23
netbox/users/graphql/filters.py
Normal file
@ -0,0 +1,23 @@
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.auth import get_user_model
|
||||
from users import filtersets, models
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'GroupFilter',
|
||||
'UserFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Group, lookups=True)
|
||||
@autotype_decorator(filtersets.GroupFilterSet)
|
||||
class GroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(get_user_model(), lookups=True)
|
||||
@autotype_decorator(filtersets.UserFilterSet)
|
||||
class UserFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,21 +1,21 @@
|
||||
import graphene
|
||||
from django.contrib.auth import get_user_model
|
||||
from typing import List
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from users.models import Group
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from users import models
|
||||
from .types import *
|
||||
|
||||
|
||||
class UsersQuery(graphene.ObjectType):
|
||||
group = ObjectField(GroupType)
|
||||
group_list = ObjectListField(GroupType)
|
||||
@strawberry.type
|
||||
class UsersQuery:
|
||||
@strawberry.field
|
||||
def group(self, id: int) -> GroupType:
|
||||
return models.Group.objects.get(pk=id)
|
||||
group_list: List[GroupType] = strawberry_django.field()
|
||||
|
||||
def resolve_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(Group.objects.all(), info)
|
||||
|
||||
user = ObjectField(UserType)
|
||||
user_list = ObjectListField(UserType)
|
||||
|
||||
def resolve_user_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(get_user_model().objects.all(), info)
|
||||
@strawberry.field
|
||||
def user(self, id: int) -> UserType:
|
||||
return models.User.objects.get(pk=id)
|
||||
user_list: List[UserType] = strawberry_django.field()
|
||||
|
@ -1,9 +1,14 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from graphene_django import DjangoObjectType
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from strawberry import auto
|
||||
from users import filtersets
|
||||
from users.models import Group
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'GroupType',
|
||||
@ -11,28 +16,24 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class GroupType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ('id', 'name')
|
||||
filterset_class = filtersets.GroupFilterSet
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view')
|
||||
@strawberry_django.type(
|
||||
Group,
|
||||
fields=['id', 'name'],
|
||||
filters=GroupFilter
|
||||
)
|
||||
class GroupType:
|
||||
pass
|
||||
|
||||
|
||||
class UserType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = (
|
||||
'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined',
|
||||
'groups',
|
||||
)
|
||||
filterset_class = filtersets.UserFilterSet
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
return RestrictedQuerySet(model=get_user_model()).restrict(info.context.user, 'view')
|
||||
@strawberry_django.type(
|
||||
get_user_model(),
|
||||
fields=[
|
||||
'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff',
|
||||
'is_active', 'date_joined', 'groups',
|
||||
],
|
||||
filters=UserFilter
|
||||
)
|
||||
class UserType:
|
||||
@strawberry_django.field
|
||||
def groups(self) -> List[GroupType]:
|
||||
return self.groups.all()
|
||||
|
@ -1,252 +0,0 @@
|
||||
import functools
|
||||
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.db.models import ForeignKey
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields.reverse_related import ManyToOneRel
|
||||
from graphene import InputObjectType
|
||||
from graphene.types.generic import GenericScalar
|
||||
from graphene.types.resolver import default_resolver
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphql import GraphQLResolveInfo, GraphQLSchema
|
||||
from graphql.execution.execute import get_field_def
|
||||
from graphql.language.ast import FragmentSpreadNode, InlineFragmentNode, VariableNode
|
||||
from graphql.pyutils import Path
|
||||
from graphql.type.definition import GraphQLInterfaceType, GraphQLUnionType
|
||||
|
||||
__all__ = (
|
||||
'gql_query_optimizer',
|
||||
)
|
||||
|
||||
|
||||
def gql_query_optimizer(queryset, info, **options):
|
||||
return QueryOptimizer(info).optimize(queryset)
|
||||
|
||||
|
||||
class QueryOptimizer(object):
|
||||
def __init__(self, info, **options):
|
||||
self.root_info = info
|
||||
|
||||
def optimize(self, queryset):
|
||||
info = self.root_info
|
||||
field_def = get_field_def(info.schema, info.parent_type, info.field_nodes[0])
|
||||
|
||||
field_names = self._optimize_gql_selections(
|
||||
self._get_type(field_def),
|
||||
info.field_nodes[0],
|
||||
)
|
||||
|
||||
qs = queryset.prefetch_related(*field_names)
|
||||
return qs
|
||||
|
||||
def _get_type(self, field_def):
|
||||
a_type = field_def.type
|
||||
while hasattr(a_type, "of_type"):
|
||||
a_type = a_type.of_type
|
||||
return a_type
|
||||
|
||||
def _get_graphql_schema(self, schema):
|
||||
if isinstance(schema, GraphQLSchema):
|
||||
return schema
|
||||
else:
|
||||
return schema.graphql_schema
|
||||
|
||||
def _get_possible_types(self, graphql_type):
|
||||
if isinstance(graphql_type, (GraphQLInterfaceType, GraphQLUnionType)):
|
||||
graphql_schema = self._get_graphql_schema(self.root_info.schema)
|
||||
return graphql_schema.get_possible_types(graphql_type)
|
||||
else:
|
||||
return (graphql_type,)
|
||||
|
||||
def _get_base_model(self, graphql_types):
|
||||
models = tuple(t.graphene_type._meta.model for t in graphql_types)
|
||||
for model in models:
|
||||
if all(issubclass(m, model) for m in models):
|
||||
return model
|
||||
return None
|
||||
|
||||
def handle_inline_fragment(self, selection, schema, possible_types, field_names):
|
||||
fragment_type_name = selection.type_condition.name.value
|
||||
graphql_schema = self._get_graphql_schema(schema)
|
||||
fragment_type = graphql_schema.get_type(fragment_type_name)
|
||||
fragment_possible_types = self._get_possible_types(fragment_type)
|
||||
for fragment_possible_type in fragment_possible_types:
|
||||
fragment_model = fragment_possible_type.graphene_type._meta.model
|
||||
parent_model = self._get_base_model(possible_types)
|
||||
if not parent_model:
|
||||
continue
|
||||
path_from_parent = fragment_model._meta.get_path_from_parent(parent_model)
|
||||
select_related_name = LOOKUP_SEP.join(p.join_field.name for p in path_from_parent)
|
||||
if not select_related_name:
|
||||
continue
|
||||
sub_field_names = self._optimize_gql_selections(
|
||||
fragment_possible_type,
|
||||
selection,
|
||||
)
|
||||
field_names.append(select_related_name)
|
||||
return
|
||||
|
||||
def handle_fragment_spread(self, field_names, name, field_type):
|
||||
fragment = self.root_info.fragments[name]
|
||||
sub_field_names = self._optimize_gql_selections(
|
||||
field_type,
|
||||
fragment,
|
||||
)
|
||||
|
||||
def _optimize_gql_selections(self, field_type, field_ast):
|
||||
field_names = []
|
||||
selection_set = field_ast.selection_set
|
||||
if not selection_set:
|
||||
return field_names
|
||||
optimized_fields_by_model = {}
|
||||
schema = self.root_info.schema
|
||||
graphql_schema = self._get_graphql_schema(schema)
|
||||
graphql_type = graphql_schema.get_type(field_type.name)
|
||||
|
||||
possible_types = self._get_possible_types(graphql_type)
|
||||
for selection in selection_set.selections:
|
||||
if isinstance(selection, InlineFragmentNode):
|
||||
self.handle_inline_fragment(selection, schema, possible_types, field_names)
|
||||
else:
|
||||
name = selection.name.value
|
||||
if isinstance(selection, FragmentSpreadNode):
|
||||
self.handle_fragment_spread(field_names, name, field_type)
|
||||
else:
|
||||
for possible_type in possible_types:
|
||||
selection_field_def = possible_type.fields.get(name)
|
||||
if not selection_field_def:
|
||||
continue
|
||||
|
||||
graphene_type = possible_type.graphene_type
|
||||
model = getattr(graphene_type._meta, "model", None)
|
||||
if model and name not in optimized_fields_by_model:
|
||||
field_model = optimized_fields_by_model[name] = model
|
||||
if field_model == model:
|
||||
self._optimize_field(
|
||||
field_names,
|
||||
model,
|
||||
selection,
|
||||
selection_field_def,
|
||||
possible_type,
|
||||
)
|
||||
return field_names
|
||||
|
||||
def _get_field_info(self, field_names, model, selection, field_def):
|
||||
name = None
|
||||
model_field = None
|
||||
name = self._get_name_from_resolver(field_def.resolve)
|
||||
if not name and callable(field_def.resolve) and not isinstance(field_def.resolve, functools.partial):
|
||||
name = selection.name.value
|
||||
if name:
|
||||
model_field = self._get_model_field_from_name(model, name)
|
||||
|
||||
return (name, model_field)
|
||||
|
||||
def _optimize_field(self, field_names, model, selection, field_def, parent_type):
|
||||
name, model_field = self._get_field_info(field_names, model, selection, field_def)
|
||||
if model_field:
|
||||
self._optimize_field_by_name(field_names, model, selection, field_def, name, model_field)
|
||||
|
||||
return
|
||||
|
||||
def _optimize_field_by_name(self, field_names, model, selection, field_def, name, model_field):
|
||||
if model_field.many_to_one or model_field.one_to_one:
|
||||
sub_field_names = self._optimize_gql_selections(
|
||||
self._get_type(field_def),
|
||||
selection,
|
||||
)
|
||||
if name not in field_names:
|
||||
field_names.append(name)
|
||||
|
||||
for field in sub_field_names:
|
||||
prefetch_key = f"{name}__{field}"
|
||||
if prefetch_key not in field_names:
|
||||
field_names.append(prefetch_key)
|
||||
|
||||
if model_field.one_to_many or model_field.many_to_many:
|
||||
sub_field_names = self._optimize_gql_selections(
|
||||
self._get_type(field_def),
|
||||
selection,
|
||||
)
|
||||
|
||||
if isinstance(model_field, ManyToOneRel):
|
||||
sub_field_names.append(model_field.field.name)
|
||||
|
||||
field_names.append(name)
|
||||
for field in sub_field_names:
|
||||
prefetch_key = f"{name}__{field}"
|
||||
if prefetch_key not in field_names:
|
||||
field_names.append(prefetch_key)
|
||||
|
||||
return
|
||||
|
||||
def _get_optimization_hints(self, resolver):
|
||||
return getattr(resolver, "optimization_hints", None)
|
||||
|
||||
def _get_value(self, info, value):
|
||||
if isinstance(value, VariableNode):
|
||||
var_name = value.name.value
|
||||
value = info.variable_values.get(var_name)
|
||||
return value
|
||||
elif isinstance(value, InputObjectType):
|
||||
return value.__dict__
|
||||
else:
|
||||
return GenericScalar.parse_literal(value)
|
||||
|
||||
def _get_name_from_resolver(self, resolver):
|
||||
optimization_hints = self._get_optimization_hints(resolver)
|
||||
if optimization_hints:
|
||||
name_fn = optimization_hints.model_field
|
||||
if name_fn:
|
||||
return name_fn()
|
||||
if self._is_resolver_for_id_field(resolver):
|
||||
return "id"
|
||||
elif isinstance(resolver, functools.partial):
|
||||
resolver_fn = resolver
|
||||
if resolver_fn.func != default_resolver:
|
||||
# Some resolvers have the partial function as the second
|
||||
# argument.
|
||||
for arg in resolver_fn.args:
|
||||
if isinstance(arg, (str, functools.partial)):
|
||||
break
|
||||
else:
|
||||
# No suitable instances found, default to first arg
|
||||
arg = resolver_fn.args[0]
|
||||
resolver_fn = arg
|
||||
if isinstance(resolver_fn, functools.partial) and resolver_fn.func == default_resolver:
|
||||
return resolver_fn.args[0]
|
||||
if self._is_resolver_for_id_field(resolver_fn):
|
||||
return "id"
|
||||
return resolver_fn
|
||||
|
||||
def _is_resolver_for_id_field(self, resolver):
|
||||
resolve_id = DjangoObjectType.resolve_id
|
||||
return resolver == resolve_id
|
||||
|
||||
def _get_model_field_from_name(self, model, name):
|
||||
try:
|
||||
return model._meta.get_field(name)
|
||||
except FieldDoesNotExist:
|
||||
descriptor = model.__dict__.get(name)
|
||||
if not descriptor:
|
||||
return None
|
||||
return getattr(descriptor, "rel", None) or getattr(descriptor, "related", None) # Django < 1.9
|
||||
|
||||
def _is_foreign_key_id(self, model_field, name):
|
||||
return isinstance(model_field, ForeignKey) and model_field.name != name and model_field.get_attname() == name
|
||||
|
||||
def _create_resolve_info(self, field_name, field_asts, return_type, parent_type):
|
||||
return GraphQLResolveInfo(
|
||||
field_name,
|
||||
field_asts,
|
||||
return_type,
|
||||
parent_type,
|
||||
Path(None, 0, None),
|
||||
schema=self.root_info.schema,
|
||||
fragments=self.root_info.fragments,
|
||||
root_value=self.root_info.root_value,
|
||||
operation=self.root_info.operation,
|
||||
variable_values=self.root_info.variable_values,
|
||||
context=self.root_info.context,
|
||||
is_awaitable=self.root_info.is_awaitable,
|
||||
)
|
@ -1,12 +1,12 @@
|
||||
import inspect
|
||||
import json
|
||||
import strawberry_django
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
from django.test import override_settings
|
||||
from graphene.types import Dynamic as GQLDynamic, List as GQLList, Union as GQLUnion, String as GQLString, NonNull as GQLNonNull
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
@ -19,7 +19,10 @@ from .base import ModelTestCase
|
||||
from .utils import disable_warnings
|
||||
|
||||
from ipam.graphql.types import IPAddressFamilyType
|
||||
|
||||
from strawberry.field import StrawberryField
|
||||
from strawberry.lazy_type import LazyType
|
||||
from strawberry.type import StrawberryList, StrawberryOptional
|
||||
from strawberry.union import StrawberryUnion
|
||||
|
||||
__all__ = (
|
||||
'APITestCase',
|
||||
@ -447,34 +450,34 @@ class APIViewTestCases:
|
||||
|
||||
# Compile list of fields to include
|
||||
fields_string = ''
|
||||
for field_name, field in type_class._meta.fields.items():
|
||||
is_string_array = False
|
||||
if type(field.type) is GQLList:
|
||||
if field.type.of_type is GQLString:
|
||||
is_string_array = True
|
||||
elif type(field.type.of_type) is GQLNonNull and field.type.of_type.of_type is GQLString:
|
||||
is_string_array = True
|
||||
|
||||
if type(field) is GQLDynamic:
|
||||
# Dynamic fields must specify a subselection
|
||||
fields_string += f'{field_name} {{ id }}\n'
|
||||
# TODO: Improve field detection logic to avoid nested ArrayFields
|
||||
elif field_name == 'extra_choices':
|
||||
file_fields = (strawberry_django.fields.types.DjangoFileType, strawberry_django.fields.types.DjangoImageType)
|
||||
for field in type_class.__strawberry_definition__.fields:
|
||||
if (
|
||||
field.type in file_fields or (
|
||||
type(field.type) is StrawberryOptional and field.type.of_type in file_fields
|
||||
)
|
||||
):
|
||||
# image / file fields nullable or not...
|
||||
fields_string += f'{field.name} {{ name }}\n'
|
||||
elif type(field.type) is StrawberryList and type(field.type.of_type) is LazyType:
|
||||
# List of related objects (queryset)
|
||||
fields_string += f'{field.name} {{ id }}\n'
|
||||
elif type(field.type) is StrawberryList and type(field.type.of_type) is StrawberryUnion:
|
||||
# this would require a fragment query
|
||||
continue
|
||||
elif inspect.isclass(field.type) and issubclass(field.type, GQLUnion):
|
||||
# Union types dont' have an id or consistent values
|
||||
elif type(field.type) is StrawberryUnion:
|
||||
# this would require a fragment query
|
||||
continue
|
||||
elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
|
||||
# Union types dont' have an id or consistent values
|
||||
continue
|
||||
elif type(field.type) is GQLList and not is_string_array:
|
||||
# TODO: Come up with something more elegant
|
||||
# Temporary hack to support automated testing of reverse generic relations
|
||||
fields_string += f'{field_name} {{ id }}\n'
|
||||
elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
|
||||
fields_string += f'{field.name} {{ id }}\n'
|
||||
elif hasattr(field, 'is_relation') and field.is_relation:
|
||||
# Note: StrawberryField types do not have is_relation
|
||||
fields_string += f'{field.name} {{ id }}\n'
|
||||
elif inspect.isclass(field.type) and issubclass(field.type, IPAddressFamilyType):
|
||||
fields_string += f'{field_name} {{ value, label }}\n'
|
||||
fields_string += f'{field.name} {{ value, label }}\n'
|
||||
else:
|
||||
fields_string += f'{field_name}\n'
|
||||
fields_string += f'{field.name}\n'
|
||||
|
||||
query = f"""
|
||||
{{
|
||||
@ -496,7 +499,10 @@ class APIViewTestCases:
|
||||
|
||||
# Non-authenticated requests should fail
|
||||
with disable_warnings('django.request'):
|
||||
self.assertHttpStatus(self.client.post(url, data={'query': query}), status.HTTP_403_FORBIDDEN)
|
||||
header = {
|
||||
'HTTP_ACCEPT': 'application/json',
|
||||
}
|
||||
self.assertHttpStatus(self.client.post(url, data={'query': query}, format="json", **header), status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Add object-level permission
|
||||
obj_perm = ObjectPermission(
|
||||
@ -507,7 +513,7 @@ class APIViewTestCases:
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
|
||||
|
||||
response = self.client.post(url, data={'query': query}, **self.header)
|
||||
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)
|
||||
@ -521,7 +527,10 @@ class APIViewTestCases:
|
||||
|
||||
# Non-authenticated requests should fail
|
||||
with disable_warnings('django.request'):
|
||||
self.assertHttpStatus(self.client.post(url, data={'query': query}), status.HTTP_403_FORBIDDEN)
|
||||
header = {
|
||||
'HTTP_ACCEPT': 'application/json',
|
||||
}
|
||||
self.assertHttpStatus(self.client.post(url, data={'query': query}, format="json", **header), status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Add object-level permission
|
||||
obj_perm = ObjectPermission(
|
||||
@ -532,7 +541,7 @@ class APIViewTestCases:
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
|
||||
|
||||
response = self.client.post(url, data={'query': query}, **self.header)
|
||||
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)
|
||||
|
49
netbox/virtualization/graphql/filters.py
Normal file
49
netbox/virtualization/graphql/filters.py
Normal file
@ -0,0 +1,49 @@
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
from virtualization import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'ClusterFilter',
|
||||
'ClusterGroupFilter',
|
||||
'ClusterTypeFilter',
|
||||
'VirtualMachineFilter',
|
||||
'VMInterfaceFilter',
|
||||
'VirtualDiskFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Cluster, lookups=True)
|
||||
@autotype_decorator(filtersets.ClusterFilterSet)
|
||||
class ClusterFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ClusterGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.ClusterGroupFilterSet)
|
||||
class ClusterGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ClusterType, lookups=True)
|
||||
@autotype_decorator(filtersets.ClusterTypeFilterSet)
|
||||
class ClusterTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualMachine, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualMachineFilterSet)
|
||||
class VirtualMachineFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VMInterface, lookups=True)
|
||||
@autotype_decorator(filtersets.VMInterfaceFilterSet)
|
||||
class VMInterfaceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualDisk, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualDiskFilterSet)
|
||||
class VirtualDiskFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,44 +1,40 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from virtualization import models
|
||||
from .types import *
|
||||
|
||||
|
||||
class VirtualizationQuery(graphene.ObjectType):
|
||||
cluster = ObjectField(ClusterType)
|
||||
cluster_list = ObjectListField(ClusterType)
|
||||
@strawberry.type
|
||||
class VirtualizationQuery:
|
||||
@strawberry.field
|
||||
def cluster(self, id: int) -> ClusterType:
|
||||
return models.Cluster.objects.get(pk=id)
|
||||
cluster_list: List[ClusterType] = strawberry_django.field()
|
||||
|
||||
def resolve_cluster_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Cluster.objects.all(), info)
|
||||
@strawberry.field
|
||||
def cluster_group(self, id: int) -> ClusterGroupType:
|
||||
return models.ClusterGroup.objects.get(pk=id)
|
||||
cluster_group_list: List[ClusterGroupType] = strawberry_django.field()
|
||||
|
||||
cluster_group = ObjectField(ClusterGroupType)
|
||||
cluster_group_list = ObjectListField(ClusterGroupType)
|
||||
@strawberry.field
|
||||
def cluster_type(self, id: int) -> ClusterTypeType:
|
||||
return models.ClusterType.objects.get(pk=id)
|
||||
cluster_type_list: List[ClusterTypeType] = strawberry_django.field()
|
||||
|
||||
def resolve_cluster_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ClusterGroup.objects.all(), info)
|
||||
@strawberry.field
|
||||
def virtual_machine(self, id: int) -> VirtualMachineType:
|
||||
return models.VirtualMachine.objects.get(pk=id)
|
||||
virtual_machine_list: List[VirtualMachineType] = strawberry_django.field()
|
||||
|
||||
cluster_type = ObjectField(ClusterTypeType)
|
||||
cluster_type_list = ObjectListField(ClusterTypeType)
|
||||
@strawberry.field
|
||||
def vm_interface(self, id: int) -> VMInterfaceType:
|
||||
return models.VMInterface.objects.get(pk=id)
|
||||
vm_interface_list: List[VMInterfaceType] = strawberry_django.field()
|
||||
|
||||
def resolve_cluster_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ClusterType.objects.all(), info)
|
||||
|
||||
virtual_machine = ObjectField(VirtualMachineType)
|
||||
virtual_machine_list = ObjectListField(VirtualMachineType)
|
||||
|
||||
def resolve_virtual_machine_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualMachine.objects.all(), info)
|
||||
|
||||
vm_interface = ObjectField(VMInterfaceType)
|
||||
vm_interface_list = ObjectListField(VMInterfaceType)
|
||||
|
||||
def resolve_vm_interface_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VMInterface.objects.all(), info)
|
||||
|
||||
virtual_disk = ObjectField(VirtualDiskType)
|
||||
virtual_disk_list = ObjectListField(VirtualDiskType)
|
||||
|
||||
def resolve_virtual_disk_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualDisk.objects.all(), info)
|
||||
@strawberry.field
|
||||
def virtual_disk(self, id: int) -> VirtualDiskType:
|
||||
return models.VirtualDisk.objects.get(pk=id)
|
||||
virtual_disk_list: List[VirtualDiskType] = strawberry_django.field()
|
||||
|
@ -1,8 +1,14 @@
|
||||
from dcim.graphql.types import ComponentObjectType
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin
|
||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
|
||||
from virtualization import filtersets, models
|
||||
from virtualization import models
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'ClusterType',
|
||||
@ -14,55 +20,121 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ComponentType(NetBoxObjectType):
|
||||
"""
|
||||
Base type for device/VM components
|
||||
"""
|
||||
_name: str
|
||||
virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Cluster,
|
||||
fields='__all__',
|
||||
filters=ClusterFilter
|
||||
)
|
||||
class ClusterType(VLANGroupsMixin, NetBoxObjectType):
|
||||
type: Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
group: Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.Cluster
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ClusterFilterSet
|
||||
@strawberry_django.field
|
||||
def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.virtual_machines.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.devices.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ClusterGroup,
|
||||
fields='__all__',
|
||||
filters=ClusterGroupFilter
|
||||
)
|
||||
class ClusterGroupType(VLANGroupsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ClusterGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ClusterGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.clusters.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ClusterType,
|
||||
fields='__all__',
|
||||
filters=ClusterTypeFilter
|
||||
)
|
||||
class ClusterTypeType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ClusterType
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ClusterTypeFilterSet
|
||||
@strawberry_django.field
|
||||
def clusters(self) -> List[ClusterType]:
|
||||
return self.clusters.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualMachine,
|
||||
fields='__all__',
|
||||
filters=VirtualMachineFilter
|
||||
)
|
||||
class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
|
||||
_name: str
|
||||
interface_count: BigInt
|
||||
virtual_disk_count: BigInt
|
||||
interface_count: BigInt
|
||||
config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
cluster: Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
role: Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VirtualMachine
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VirtualMachineFilterSet
|
||||
@strawberry_django.field
|
||||
def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.services.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def virtualdisks(self) -> List[Annotated["VirtualDiskType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.virtualdisks.all()
|
||||
|
||||
|
||||
class VMInterfaceType(IPAddressesMixin, ComponentObjectType):
|
||||
@strawberry_django.type(
|
||||
models.VMInterface,
|
||||
fields='__all__',
|
||||
filters=VMInterfaceFilter
|
||||
)
|
||||
class VMInterfaceType(IPAddressesMixin, ComponentType):
|
||||
mac_address: str | None
|
||||
parent: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VMInterface
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VMInterfaceFilterSet
|
||||
@strawberry_django.field
|
||||
def tagged_vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.tagged_vlans.all()
|
||||
|
||||
def resolve_mode(self, info):
|
||||
return self.mode or None
|
||||
@strawberry_django.field
|
||||
def bridge_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.bridge_interfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def child_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.child_interfaces.all()
|
||||
|
||||
|
||||
class VirtualDiskType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VirtualDisk
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VirtualDiskFilterSet
|
||||
|
||||
def resolve_mode(self, info):
|
||||
return self.mode or None
|
||||
@strawberry_django.type(
|
||||
models.VirtualDisk,
|
||||
fields='__all__',
|
||||
filters=VirtualDiskFilter
|
||||
)
|
||||
class VirtualDiskType(ComponentType):
|
||||
pass
|
||||
|
77
netbox/vpn/graphql/filters.py
Normal file
77
netbox/vpn/graphql/filters.py
Normal file
@ -0,0 +1,77 @@
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
from vpn import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'TunnelGroupFilter',
|
||||
'TunnelTerminationFilter',
|
||||
'TunnelFilter',
|
||||
'IKEProposalFilter',
|
||||
'IKEPolicyFilter',
|
||||
'IPSecProposalFilter',
|
||||
'IPSecPolicyFilter',
|
||||
'IPSecProfileFilter',
|
||||
'L2VPNFilter',
|
||||
'L2VPNTerminationFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.TunnelGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.TunnelGroupFilterSet)
|
||||
class TunnelGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.TunnelTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.TunnelTerminationFilterSet)
|
||||
class TunnelTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Tunnel, lookups=True)
|
||||
@autotype_decorator(filtersets.TunnelFilterSet)
|
||||
class TunnelFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IKEProposal, lookups=True)
|
||||
@autotype_decorator(filtersets.IKEProposalFilterSet)
|
||||
class IKEProposalFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IKEPolicy, lookups=True)
|
||||
@autotype_decorator(filtersets.IKEPolicyFilterSet)
|
||||
class IKEPolicyFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPSecProposal, lookups=True)
|
||||
@autotype_decorator(filtersets.IPSecProposalFilterSet)
|
||||
class IPSecProposalFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPSecPolicy, lookups=True)
|
||||
@autotype_decorator(filtersets.IPSecPolicyFilterSet)
|
||||
class IPSecPolicyFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPSecProfile, lookups=True)
|
||||
@autotype_decorator(filtersets.IPSecProfileFilterSet)
|
||||
class IPSecProfileFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.L2VPN, lookups=True)
|
||||
@autotype_decorator(filtersets.L2VPNFilterSet)
|
||||
class L2VPNFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.L2VPNTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.L2VPNTerminationFilterSet)
|
||||
class L2VPNTerminationFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,30 +0,0 @@
|
||||
import graphene
|
||||
|
||||
from dcim.graphql.types import InterfaceType
|
||||
from dcim.models import Interface
|
||||
from ipam.graphql.types import VLANType
|
||||
from ipam.models import VLAN
|
||||
from virtualization.graphql.types import VMInterfaceType
|
||||
from virtualization.models import VMInterface
|
||||
|
||||
__all__ = (
|
||||
'L2VPNAssignmentType',
|
||||
)
|
||||
|
||||
|
||||
class L2VPNAssignmentType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
VLANType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is VLAN:
|
||||
return VLANType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
@ -1,69 +1,60 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from vpn import models
|
||||
from .types import *
|
||||
|
||||
|
||||
class VPNQuery(graphene.ObjectType):
|
||||
@strawberry.type
|
||||
class VPNQuery:
|
||||
@strawberry.field
|
||||
def ike_policy(self, id: int) -> IKEPolicyType:
|
||||
return models.IKEPolicy.objects.get(pk=id)
|
||||
ike_policy_list: List[IKEPolicyType] = strawberry_django.field()
|
||||
|
||||
ike_policy = ObjectField(IKEPolicyType)
|
||||
ike_policy_list = ObjectListField(IKEPolicyType)
|
||||
@strawberry.field
|
||||
def ike_proposal(self, id: int) -> IKEProposalType:
|
||||
return models.IKEProposal.objects.get(pk=id)
|
||||
ike_proposal_list: List[IKEProposalType] = strawberry_django.field()
|
||||
|
||||
def resolve_ike_policy_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IKEPolicy.objects.all(), info)
|
||||
@strawberry.field
|
||||
def ipsec_policy(self, id: int) -> IPSecPolicyType:
|
||||
return models.IPSecPolicy.objects.get(pk=id)
|
||||
ipsec_policy_list: List[IPSecPolicyType] = strawberry_django.field()
|
||||
|
||||
ike_proposal = ObjectField(IKEProposalType)
|
||||
ike_proposal_list = ObjectListField(IKEProposalType)
|
||||
@strawberry.field
|
||||
def ipsec_profile(self, id: int) -> IPSecProfileType:
|
||||
return models.IPSecProfile.objects.get(pk=id)
|
||||
ipsec_profile_list: List[IPSecProfileType] = strawberry_django.field()
|
||||
|
||||
def resolve_ike_proposal_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IKEProposal.objects.all(), info)
|
||||
@strawberry.field
|
||||
def ipsec_proposal(self, id: int) -> IPSecProposalType:
|
||||
return models.IPSecProposal.objects.get(pk=id)
|
||||
ipsec_proposal_list: List[IPSecProposalType] = strawberry_django.field()
|
||||
|
||||
ipsec_policy = ObjectField(IPSecPolicyType)
|
||||
ipsec_policy_list = ObjectListField(IPSecPolicyType)
|
||||
@strawberry.field
|
||||
def l2vpn(self, id: int) -> L2VPNType:
|
||||
return models.L2VPN.objects.get(pk=id)
|
||||
l2vpn_list: List[L2VPNType] = strawberry_django.field()
|
||||
|
||||
def resolve_ipsec_policy_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPSecPolicy.objects.all(), info)
|
||||
@strawberry.field
|
||||
def l2vpn_termination(self, id: int) -> L2VPNTerminationType:
|
||||
return models.L2VPNTermination.objects.get(pk=id)
|
||||
l2vpn_termination_list: List[L2VPNTerminationType] = strawberry_django.field()
|
||||
|
||||
ipsec_profile = ObjectField(IPSecProfileType)
|
||||
ipsec_profile_list = ObjectListField(IPSecProfileType)
|
||||
@strawberry.field
|
||||
def tunnel(self, id: int) -> TunnelType:
|
||||
return models.Tunnel.objects.get(pk=id)
|
||||
tunnel_list: List[TunnelType] = strawberry_django.field()
|
||||
|
||||
def resolve_ipsec_profile_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPSecProfile.objects.all(), info)
|
||||
@strawberry.field
|
||||
def tunnel_group(self, id: int) -> TunnelGroupType:
|
||||
return models.TunnelGroup.objects.get(pk=id)
|
||||
tunnel_group_list: List[TunnelGroupType] = strawberry_django.field()
|
||||
|
||||
ipsec_proposal = ObjectField(IPSecProposalType)
|
||||
ipsec_proposal_list = ObjectListField(IPSecProposalType)
|
||||
|
||||
def resolve_ipsec_proposal_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPSecProposal.objects.all(), info)
|
||||
|
||||
l2vpn = ObjectField(L2VPNType)
|
||||
l2vpn_list = ObjectListField(L2VPNType)
|
||||
|
||||
def resolve_l2vpn_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.L2VPN.objects.all(), info)
|
||||
|
||||
l2vpn_termination = ObjectField(L2VPNTerminationType)
|
||||
l2vpn_termination_list = ObjectListField(L2VPNTerminationType)
|
||||
|
||||
def resolve_l2vpn_termination_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.L2VPNTermination.objects.all(), info)
|
||||
|
||||
tunnel = ObjectField(TunnelType)
|
||||
tunnel_list = ObjectListField(TunnelType)
|
||||
|
||||
def resolve_tunnel_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Tunnel.objects.all(), info)
|
||||
|
||||
tunnel_group = ObjectField(TunnelGroupType)
|
||||
tunnel_group_list = ObjectListField(TunnelGroupType)
|
||||
|
||||
def resolve_tunnel_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.TunnelGroup.objects.all(), info)
|
||||
|
||||
tunnel_termination = ObjectField(TunnelTerminationType)
|
||||
tunnel_termination_list = ObjectListField(TunnelTerminationType)
|
||||
|
||||
def resolve_tunnel_termination_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.TunnelTermination.objects.all(), info)
|
||||
@strawberry.field
|
||||
def tunnel_termination(self, id: int) -> TunnelTerminationType:
|
||||
return models.TunnelTermination.objects.get(pk=id)
|
||||
tunnel_termination_list: List[TunnelTerminationType] = strawberry_django.field()
|
||||
|
@ -1,8 +1,12 @@
|
||||
import graphene
|
||||
from typing import Annotated, List, Union
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from vpn import filtersets, models
|
||||
from vpn import models
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'IKEPolicyType',
|
||||
@ -18,81 +22,147 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.TunnelGroup,
|
||||
fields='__all__',
|
||||
filters=TunnelGroupFilter
|
||||
)
|
||||
class TunnelGroupType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.TunnelGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TunnelGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.tunnels.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.TunnelTermination,
|
||||
fields='__all__',
|
||||
filters=TunnelTerminationFilter
|
||||
)
|
||||
class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.TunnelTermination
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TunnelTerminationFilterSet
|
||||
tunnel: Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]
|
||||
termination_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
outside_ip: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Tunnel,
|
||||
fields='__all__',
|
||||
filters=TunnelFilter
|
||||
)
|
||||
class TunnelType(NetBoxObjectType):
|
||||
group: Annotated["TunnelGroupType", strawberry.lazy('vpn.graphql.types')] | None
|
||||
ipsec_profile: Annotated["IPSecProfileType", strawberry.lazy('vpn.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.Tunnel
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.TunnelFilterSet
|
||||
@strawberry_django.field
|
||||
def terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IKEProposal,
|
||||
fields='__all__',
|
||||
filters=IKEProposalFilter
|
||||
)
|
||||
class IKEProposalType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IKEProposal
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IKEProposalFilterSet
|
||||
@strawberry_django.field
|
||||
def ike_policies(self) -> List[Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.ike_policies.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IKEPolicy,
|
||||
fields='__all__',
|
||||
filters=IKEPolicyFilter
|
||||
)
|
||||
class IKEPolicyType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IKEPolicy
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IKEPolicyFilterSet
|
||||
@strawberry_django.field
|
||||
def proposals(self) -> List[Annotated["IKEProposalType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.proposals.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ipsec_profiles(self) -> List[Annotated["IPSecProposalType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.ipsec_profiles.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPSecProposal,
|
||||
fields='__all__',
|
||||
filters=IPSecProposalFilter
|
||||
)
|
||||
class IPSecProposalType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPSecProposal
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IPSecProposalFilterSet
|
||||
@strawberry_django.field
|
||||
def ipsec_policies(self) -> List[Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.ipsec_policies.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPSecPolicy,
|
||||
fields='__all__',
|
||||
filters=IPSecPolicyFilter
|
||||
)
|
||||
class IPSecPolicyType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPSecPolicy
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IPSecPolicyFilterSet
|
||||
@strawberry_django.field
|
||||
def proposals(self) -> List[Annotated["IPSecProposalType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.proposals.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ipsec_profiles(self) -> List[Annotated["IPSecProfileType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.ipsec_profiles.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPSecProfile,
|
||||
fields='__all__',
|
||||
filters=IPSecProfileFilter
|
||||
)
|
||||
class IPSecProfileType(OrganizationalObjectType):
|
||||
ike_policy: Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]
|
||||
ipsec_policy: Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.IPSecProfile
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IPSecProfileFilterSet
|
||||
@strawberry_django.field
|
||||
def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.tunnels.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.L2VPN,
|
||||
fields='__all__',
|
||||
filters=L2VPNFilter
|
||||
)
|
||||
class L2VPNType(ContactsMixin, NetBoxObjectType):
|
||||
class Meta:
|
||||
model = models.L2VPN
|
||||
fields = '__all__'
|
||||
filtersets_class = filtersets.L2VPNFilterSet
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
@strawberry_django.field
|
||||
def export_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.export_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def terminations(self) -> List[Annotated["L2VPNTerminationType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.terminations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def import_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.import_targets.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.L2VPNTermination,
|
||||
exclude=('assigned_object_type', 'assigned_object_id'),
|
||||
filters=L2VPNTerminationFilter
|
||||
)
|
||||
class L2VPNTerminationType(NetBoxObjectType):
|
||||
assigned_object = graphene.Field('vpn.graphql.gfk_mixins.L2VPNAssignmentType')
|
||||
l2vpn: Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.L2VPNTermination
|
||||
exclude = ('assigned_object_type', 'assigned_object_id')
|
||||
filtersets_class = filtersets.L2VPNTerminationFilterSet
|
||||
@strawberry_django.field
|
||||
def assigned_object(self) -> Annotated[Union[
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["VLANType", strawberry.lazy('ipam.graphql.types')],
|
||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||
], strawberry.union("L2VPNAssignmentType")]:
|
||||
return self.assigned_object
|
||||
|
28
netbox/wireless/graphql/filters.py
Normal file
28
netbox/wireless/graphql/filters.py
Normal file
@ -0,0 +1,28 @@
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
from wireless import filtersets, models
|
||||
|
||||
__all__ = (
|
||||
'WirelessLANGroupFilter',
|
||||
'WirelessLANFilter',
|
||||
'WirelessLinkFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.WirelessLANGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.WirelessLANGroupFilterSet)
|
||||
class WirelessLANGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.WirelessLAN, lookups=True)
|
||||
@autotype_decorator(filtersets.WirelessLANFilterSet)
|
||||
class WirelessLANFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.WirelessLink, lookups=True)
|
||||
@autotype_decorator(filtersets.WirelessLinkFilterSet)
|
||||
class WirelessLinkFilter(BaseFilterMixin):
|
||||
pass
|
@ -1,26 +1,25 @@
|
||||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from wireless import models
|
||||
from .types import *
|
||||
|
||||
|
||||
class WirelessQuery(graphene.ObjectType):
|
||||
wireless_lan = ObjectField(WirelessLANType)
|
||||
wireless_lan_list = ObjectListField(WirelessLANType)
|
||||
@strawberry.type
|
||||
class WirelessQuery:
|
||||
@strawberry.field
|
||||
def wireless_lan(self, id: int) -> WirelessLANType:
|
||||
return models.WirelessLAN.objects.get(pk=id)
|
||||
wireless_lan_list: List[WirelessLANType] = strawberry_django.field()
|
||||
|
||||
def resolve_wireless_lan_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.WirelessLAN.objects.all(), info)
|
||||
@strawberry.field
|
||||
def wireless_lan_group(self, id: int) -> WirelessLANGroupType:
|
||||
return models.WirelessLANGroup.objects.get(pk=id)
|
||||
wireless_lan_group_list: List[WirelessLANGroupType] = strawberry_django.field()
|
||||
|
||||
wireless_lan_group = ObjectField(WirelessLANGroupType)
|
||||
wireless_lan_group_list = ObjectListField(WirelessLANGroupType)
|
||||
|
||||
def resolve_wireless_lan_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.WirelessLANGroup.objects.all(), info)
|
||||
|
||||
wireless_link = ObjectField(WirelessLinkType)
|
||||
wireless_link_list = ObjectListField(WirelessLinkType)
|
||||
|
||||
def resolve_wireless_link_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.WirelessLink.objects.all(), info)
|
||||
@strawberry.field
|
||||
def wireless_link(self, id: int) -> WirelessLinkType:
|
||||
return models.WirelessLink.objects.get(pk=id)
|
||||
wireless_link_list: List[WirelessLinkType] = strawberry_django.field()
|
||||
|
@ -1,5 +1,11 @@
|
||||
from wireless import filtersets, models
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
|
||||
from wireless import models
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'WirelessLANType',
|
||||
@ -8,37 +14,42 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.WirelessLANGroup,
|
||||
fields='__all__',
|
||||
filters=WirelessLANGroupFilter
|
||||
)
|
||||
class WirelessLANGroupType(OrganizationalObjectType):
|
||||
parent: Annotated["WirelessLANGroupType", strawberry.lazy('wireless.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.WirelessLANGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.WirelessLANGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
|
||||
return self.wireless_lans.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.WirelessLAN,
|
||||
fields='__all__',
|
||||
filters=WirelessLANFilter
|
||||
)
|
||||
class WirelessLANType(NetBoxObjectType):
|
||||
group: Annotated["WirelessLANGroupType", strawberry.lazy('wireless.graphql.types')] | None
|
||||
vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.WirelessLAN
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.WirelessLANFilterSet
|
||||
|
||||
def resolve_auth_type(self, info):
|
||||
return self.auth_type or None
|
||||
|
||||
def resolve_auth_cipher(self, info):
|
||||
return self.auth_cipher or None
|
||||
@strawberry_django.field
|
||||
def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.WirelessLink,
|
||||
fields='__all__',
|
||||
filters=WirelessLinkFilter
|
||||
)
|
||||
class WirelessLinkType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.WirelessLink
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.WirelessLinkFilterSet
|
||||
|
||||
def resolve_auth_type(self, info):
|
||||
return self.auth_type or None
|
||||
|
||||
def resolve_auth_cipher(self, info):
|
||||
return self.auth_cipher or None
|
||||
interface_a: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
|
||||
interface_b: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
_interface_a_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
_interface_b_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
@ -2,7 +2,6 @@ Django==5.0.3
|
||||
django-cors-headers==4.3.1
|
||||
django-debug-toolbar==4.3.0
|
||||
django-filter==24.1
|
||||
django-graphiql-debug-toolbar==0.2.0
|
||||
django-htmx==1.17.3
|
||||
django-mptt==0.14.0
|
||||
django-pglocks==1.0.4
|
||||
@ -17,7 +16,6 @@ djangorestframework==3.14.0
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular-sidecar==2024.3.4
|
||||
feedparser==6.0.11
|
||||
graphene-django==3.0.0
|
||||
gunicorn==21.2.0
|
||||
Jinja2==3.1.3
|
||||
Markdown==3.5.2
|
||||
@ -31,6 +29,8 @@ PyYAML==6.0.1
|
||||
requests==2.31.0
|
||||
social-auth-app-django==5.4.0
|
||||
social-auth-core[openidconnect]==4.5.3
|
||||
strawberry-graphql==0.221.1
|
||||
strawberry-graphql-django==0.35.1
|
||||
svgwrite==1.4.3
|
||||
tablib==3.5.0
|
||||
tzdata==2024.1
|
||||
|
Loading…
Reference in New Issue
Block a user