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:
Arthur Hanson 2024-03-22 09:56:30 -07:00 committed by GitHub
parent 423c9813a2
commit 45c99e4477
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 13159 additions and 2289 deletions

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
netbox/project-static/dist/index.umd.js vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

@ -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>&#x1f353;</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>

View File

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

View 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

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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