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 # https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
django-filter 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 # HTMX utilities for Django
# https://django-htmx.readthedocs.io/en/latest/changelog.html # https://django-htmx.readthedocs.io/en/latest/changelog.html
django-htmx django-htmx
@ -75,11 +71,6 @@ drf-spectacular-sidecar
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
feedparser 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 # WSGI HTTP server
# https://docs.gunicorn.org/en/latest/news.html # https://docs.gunicorn.org/en/latest/news.html
gunicorn gunicorn
@ -136,8 +127,16 @@ social-auth-core
# https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md # https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
social-auth-app-django 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) # 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 svgwrite
# Tabular dataset library (for table-based exports) # 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 ```python
# graphql.py # graphql.py
import graphene from typing import List
from netbox.graphql.types import NetBoxObjectType import strawberry
from netbox.graphql.fields import ObjectField, ObjectListField import strawberry_django
from . import filtersets, models
class MyModelType(NetBoxObjectType): from . import models
class Meta:
model = models.MyModel
fields = '__all__'
filterset_class = filtersets.MyModelFilterSet
class MyQuery(graphene.ObjectType): @strawberry_django.type(
mymodel = ObjectField(MyModelType) models.MyModel,
mymodel_list = ObjectListField(MyModelType) 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 ## GraphQL Objects
@ -38,15 +47,3 @@ NetBox provides two object type classes for use by plugins.
::: netbox.graphql.types.NetBoxObjectType ::: netbox.graphql.types.NetBoxObjectType
options: options:
members: false 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 circuits import models
from netbox.graphql.fields import ObjectField, ObjectListField
from .types import * from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
class CircuitsQuery(graphene.ObjectType): @strawberry.type
circuit = ObjectField(CircuitType) class CircuitsQuery:
circuit_list = ObjectListField(CircuitType) @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): @strawberry.field
return gql_query_optimizer(models.Circuit.objects.all(), info) 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) @strawberry.field
circuit_termination_list = ObjectListField(CircuitTerminationType) 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): @strawberry.field
return gql_query_optimizer(models.CircuitTermination.objects.all(), info) def provider(self, id: int) -> ProviderType:
return models.Provider.objects.get(pk=id)
provider_list: List[ProviderType] = strawberry_django.field()
circuit_type = ObjectField(CircuitTypeType) @strawberry.field
circuit_type_list = ObjectListField(CircuitTypeType) 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): @strawberry.field
return gql_query_optimizer(models.CircuitType.objects.all(), info) def provider_network(self, id: int) -> ProviderNetworkType:
return models.ProviderNetwork.objects.get(pk=id)
provider = ObjectField(ProviderType) provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
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)

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 dcim.graphql.mixins import CabledObjectMixin
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin, ContactsMixin from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import NetBoxObjectType, ObjectType, OrganizationalObjectType
from tenancy.graphql.types import TenantType
from .filters import *
__all__ = ( __all__ = (
'CircuitTerminationType', 'CircuitTerminationType',
@ -15,48 +20,93 @@ __all__ = (
) )
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType): @strawberry_django.type(
models.Provider,
class Meta: fields='__all__',
model = models.CircuitTermination filters=ProviderFilter
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
class ProviderType(NetBoxObjectType, ContactsMixin): class ProviderType(NetBoxObjectType, ContactsMixin):
class Meta: @strawberry_django.field
model = models.Provider def networks(self) -> List[Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')]]:
fields = '__all__' return self.networks.all()
filterset_class = filtersets.ProviderFilterSet
@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): class ProviderAccountType(NetBoxObjectType):
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
class Meta: @strawberry_django.field
model = models.ProviderAccount def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
fields = '__all__' return self.circuits.all()
filterset_class = filtersets.ProviderAccountFilterSet
@strawberry_django.type(
models.ProviderNetwork,
fields='__all__',
filters=ProviderNetworkFilter
)
class ProviderNetworkType(NetBoxObjectType): class ProviderNetworkType(NetBoxObjectType):
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
class Meta: @strawberry_django.field
model = models.ProviderNetwork def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
fields = '__all__' return self.circuit_terminations.all()
filterset_class = filtersets.ProviderNetworkFilterSet
@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 core import models
from netbox.graphql.fields import ObjectField, ObjectListField
from .types import * from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
class CoreQuery(graphene.ObjectType): @strawberry.type
data_file = ObjectField(DataFileType) class CoreQuery:
data_file_list = ObjectListField(DataFileType) @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): @strawberry.field
return gql_query_optimizer(models.DataFile.objects.all(), info) def data_source(self, id: int) -> DataSourceType:
return models.DataSource.objects.get(pk=id)
data_source = ObjectField(DataSourceType) data_source_list: List[DataSourceType] = strawberry_django.field()
data_source_list = ObjectListField(DataSourceType)
def resolve_data_source_list(root, info, **kwargs):
return gql_query_optimizer(models.DataSource.objects.all(), info)

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 netbox.graphql.types import BaseObjectType, NetBoxObjectType
from .filters import *
__all__ = ( __all__ = (
'DataFileType', 'DataFileType',
@ -7,15 +13,22 @@ __all__ = (
) )
@strawberry_django.type(
models.DataFile,
exclude=['data',],
filters=DataFileFilter
)
class DataFileType(BaseObjectType): class DataFileType(BaseObjectType):
class Meta: source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')]
model = models.DataFile
exclude = ('data',)
filterset_class = filtersets.DataFileFilterSet
@strawberry_django.type(
models.DataSource,
fields='__all__',
filters=DataSourceFilter
)
class DataSourceType(NetBoxObjectType): class DataSourceType(NetBoxObjectType):
class Meta:
model = models.DataSource @strawberry_django.field
fields = '__all__' def datafiles(self) -> List[Annotated["DataFileType", strawberry.lazy('core.graphql.types')]]:
filterset_class = filtersets.DataSourceFilterSet 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.graphql.types import CircuitTerminationType, ProviderNetworkType
from circuits.models import CircuitTermination, ProviderNetwork from circuits.models import CircuitTermination, ProviderNetwork
from dcim.graphql.types import ( from dcim.graphql.types import (
@ -37,79 +36,7 @@ from dcim.models import (
) )
class LinkPeerType(graphene.Union): class InventoryItemTemplateComponentType:
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 Meta: class Meta:
types = ( types = (
ConsolePortTemplateType, ConsolePortTemplateType,
@ -139,7 +66,7 @@ class InventoryItemTemplateComponentType(graphene.Union):
return RearPortTemplateType return RearPortTemplateType
class InventoryItemComponentType(graphene.Union): class InventoryItemComponentType:
class Meta: class Meta:
types = ( types = (
ConsolePortType, ConsolePortType,
@ -169,7 +96,7 @@ class InventoryItemComponentType(graphene.Union):
return RearPortType return RearPortType
class ConnectedEndpointType(graphene.Union): class ConnectedEndpointType:
class Meta: class Meta:
types = ( types = (
CircuitTerminationType, 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: 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): @strawberry_django.field
# Handle empty values def link_peers(self) -> List[Annotated[Union[
return self.cable_end or None Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
def resolve_link_peers(self, info): 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 return self.link_peers
@strawberry.type
class PathEndpointMixin: class PathEndpointMixin:
connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.ConnectedEndpointType')
def resolve_connected_endpoints(self, info): @strawberry_django.field
# Handle empty values 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 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 dcim import models
from .types import VirtualDeviceContextType from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
@strawberry.type
class DCIMQuery(graphene.ObjectType): class DCIMQuery:
cable = ObjectField(CableType) @strawberry.field
cable_list = ObjectListField(CableType) def cable(self, id: int) -> CableType:
return models.Cable.objects.get(pk=id)
def resolve_cable_list(root, info, **kwargs): cable_list: List[CableType] = strawberry_django.field()
return gql_query_optimizer(models.Cable.objects.all(), info)
@strawberry.field
console_port = ObjectField(ConsolePortType) def console_port(self, id: int) -> ConsolePortType:
console_port_list = ObjectListField(ConsolePortType) return models.ConsolePort.objects.get(pk=id)
console_port_list: List[ConsolePortType] = strawberry_django.field()
def resolve_console_port_list(root, info, **kwargs):
return gql_query_optimizer(models.ConsolePort.objects.all(), info) @strawberry.field
def console_port_template(self, id: int) -> ConsolePortTemplateType:
console_port_template = ObjectField(ConsolePortTemplateType) return models.ConsolePortTemplate.objects.get(pk=id)
console_port_template_list = ObjectListField(ConsolePortTemplateType) console_port_template_list: List[ConsolePortTemplateType] = strawberry_django.field()
def resolve_console_port_template_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.ConsolePortTemplate.objects.all(), info) def console_server_port(self, id: int) -> ConsoleServerPortType:
return models.ConsoleServerPort.objects.get(pk=id)
console_server_port = ObjectField(ConsoleServerPortType) console_server_port_list: List[ConsoleServerPortType] = strawberry_django.field()
console_server_port_list = ObjectListField(ConsoleServerPortType)
@strawberry.field
def resolve_console_server_port_list(root, info, **kwargs): def console_server_port_template(self, id: int) -> ConsoleServerPortTemplateType:
return gql_query_optimizer(models.ConsoleServerPort.objects.all(), info) return models.ConsoleServerPortTemplate.objects.get(pk=id)
console_server_port_template_list: List[ConsoleServerPortTemplateType] = strawberry_django.field()
console_server_port_template = ObjectField(ConsoleServerPortTemplateType)
console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType) @strawberry.field
def device(self, id: int) -> DeviceType:
def resolve_console_server_port_template_list(root, info, **kwargs): return models.Device.objects.get(pk=id)
return gql_query_optimizer(models.ConsoleServerPortTemplate.objects.all(), info) device_list: List[DeviceType] = strawberry_django.field()
device = ObjectField(DeviceType) @strawberry.field
device_list = ObjectListField(DeviceType) def device_bay(self, id: int) -> DeviceBayType:
return models.DeviceBay.objects.get(pk=id)
def resolve_device_list(root, info, **kwargs): device_bay_list: List[DeviceBayType] = strawberry_django.field()
return gql_query_optimizer(models.Device.objects.all(), info)
@strawberry.field
device_bay = ObjectField(DeviceBayType) def device_bay_template(self, id: int) -> DeviceBayTemplateType:
device_bay_list = ObjectListField(DeviceBayType) return models.DeviceBayTemplate.objects.get(pk=id)
device_bay_template_list: List[DeviceBayTemplateType] = strawberry_django.field()
def resolve_device_bay_list(root, info, **kwargs):
return gql_query_optimizer(models.DeviceBay.objects.all(), info) @strawberry.field
def device_role(self, id: int) -> DeviceRoleType:
device_bay_template = ObjectField(DeviceBayTemplateType) return models.DeviceRole.objects.get(pk=id)
device_bay_template_list = ObjectListField(DeviceBayTemplateType) device_role_list: List[DeviceRoleType] = strawberry_django.field()
def resolve_device_bay_template_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.DeviceBayTemplate.objects.all(), info) def device_type(self, id: int) -> DeviceTypeType:
return models.DeviceType.objects.get(pk=id)
device_role = ObjectField(DeviceRoleType) device_type_list: List[DeviceTypeType] = strawberry_django.field()
device_role_list = ObjectListField(DeviceRoleType)
@strawberry.field
def resolve_device_role_list(root, info, **kwargs): def front_port(self, id: int) -> FrontPortType:
return gql_query_optimizer(models.DeviceRole.objects.all(), info) return models.FrontPort.objects.get(pk=id)
front_port_list: List[FrontPortType] = strawberry_django.field()
device_type = ObjectField(DeviceTypeType)
device_type_list = ObjectListField(DeviceTypeType) @strawberry.field
def front_port_template(self, id: int) -> FrontPortTemplateType:
def resolve_device_type_list(root, info, **kwargs): return models.FrontPortTemplate.objects.get(pk=id)
return gql_query_optimizer(models.DeviceType.objects.all(), info) front_port_template_list: List[FrontPortTemplateType] = strawberry_django.field()
front_port = ObjectField(FrontPortType) @strawberry.field
front_port_list = ObjectListField(FrontPortType) def interface(self, id: int) -> InterfaceType:
return models.Interface.objects.get(pk=id)
def resolve_front_port_list(root, info, **kwargs): interface_list: List[InterfaceType] = strawberry_django.field()
return gql_query_optimizer(models.FrontPort.objects.all(), info)
@strawberry.field
front_port_template = ObjectField(FrontPortTemplateType) def interface_template(self, id: int) -> InterfaceTemplateType:
front_port_template_list = ObjectListField(FrontPortTemplateType) return models.InterfaceTemplate.objects.get(pk=id)
interface_template_list: List[InterfaceTemplateType] = strawberry_django.field()
def resolve_front_port_template_list(root, info, **kwargs):
return gql_query_optimizer(models.FrontPortTemplate.objects.all(), info) @strawberry.field
def inventory_item(self, id: int) -> InventoryItemType:
interface = ObjectField(InterfaceType) return models.InventoryItem.objects.get(pk=id)
interface_list = ObjectListField(InterfaceType) inventory_item_list: List[InventoryItemType] = strawberry_django.field()
def resolve_interface_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.Interface.objects.all(), info) def inventory_item_role(self, id: int) -> InventoryItemRoleType:
return models.InventoryItemRole.objects.get(pk=id)
interface_template = ObjectField(InterfaceTemplateType) inventory_item_role_list: List[InventoryItemRoleType] = strawberry_django.field()
interface_template_list = ObjectListField(InterfaceTemplateType)
@strawberry.field
def resolve_interface_template_list(root, info, **kwargs): def inventory_item_template(self, id: int) -> InventoryItemTemplateType:
return gql_query_optimizer(models.InterfaceTemplate.objects.all(), info) return models.InventoryItemTemplate.objects.get(pk=id)
inventory_item_template_list: List[InventoryItemTemplateType] = strawberry_django.field()
inventory_item = ObjectField(InventoryItemType)
inventory_item_list = ObjectListField(InventoryItemType) @strawberry.field
def location(self, id: int) -> LocationType:
def resolve_inventory_item_list(root, info, **kwargs): return models.Location.objects.get(pk=id)
return gql_query_optimizer(models.InventoryItem.objects.all(), info) location_list: List[LocationType] = strawberry_django.field()
inventory_item_role = ObjectField(InventoryItemRoleType) @strawberry.field
inventory_item_role_list = ObjectListField(InventoryItemRoleType) def manufacturer(self, id: int) -> ManufacturerType:
return models.Manufacturer.objects.get(pk=id)
def resolve_inventory_item_role_list(root, info, **kwargs): manufacturer_list: List[ManufacturerType] = strawberry_django.field()
return gql_query_optimizer(models.InventoryItemRole.objects.all(), info)
@strawberry.field
inventory_item_template = ObjectField(InventoryItemTemplateType) def module(self, id: int) -> ModuleType:
inventory_item_template_list = ObjectListField(InventoryItemTemplateType) return models.Module.objects.get(pk=id)
module_list: List[ModuleType] = strawberry_django.field()
def resolve_inventory_item_template_list(root, info, **kwargs):
return gql_query_optimizer(models.InventoryItemTemplate.objects.all(), info) @strawberry.field
def module_bay(self, id: int) -> ModuleBayType:
location = ObjectField(LocationType) return models.ModuleBay.objects.get(pk=id)
location_list = ObjectListField(LocationType) module_bay_list: List[ModuleBayType] = strawberry_django.field()
def resolve_location_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.Location.objects.all(), info) def module_bay_template(self, id: int) -> ModuleBayTemplateType:
return models.ModuleBayTemplate.objects.get(pk=id)
manufacturer = ObjectField(ManufacturerType) module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
manufacturer_list = ObjectListField(ManufacturerType)
@strawberry.field
def resolve_manufacturer_list(root, info, **kwargs): def module_type(self, id: int) -> ModuleTypeType:
return gql_query_optimizer(models.Manufacturer.objects.all(), info) return models.ModuleType.objects.get(pk=id)
module_type_list: List[ModuleTypeType] = strawberry_django.field()
module = ObjectField(ModuleType)
module_list = ObjectListField(ModuleType) @strawberry.field
def platform(self, id: int) -> PlatformType:
def resolve_module_list(root, info, **kwargs): return models.Platform.objects.get(pk=id)
return gql_query_optimizer(models.Module.objects.all(), info) platform_list: List[PlatformType] = strawberry_django.field()
module_bay = ObjectField(ModuleBayType) @strawberry.field
module_bay_list = ObjectListField(ModuleBayType) def power_feed(self, id: int) -> PowerFeedType:
return models.PowerFeed.objects.get(pk=id)
def resolve_module_bay_list(root, info, **kwargs): power_feed_list: List[PowerFeedType] = strawberry_django.field()
return gql_query_optimizer(models.ModuleBay.objects.all(), info)
@strawberry.field
module_bay_template = ObjectField(ModuleBayTemplateType) def power_outlet(self, id: int) -> PowerOutletType:
module_bay_template_list = ObjectListField(ModuleBayTemplateType) return models.PowerOutlet.objects.get(pk=id)
power_outlet_list: List[PowerOutletType] = strawberry_django.field()
def resolve_module_bay_template_list(root, info, **kwargs):
return gql_query_optimizer(models.ModuleBayTemplate.objects.all(), info) @strawberry.field
def power_outlet_template(self, id: int) -> PowerOutletTemplateType:
module_type = ObjectField(ModuleTypeType) return models.PowerOutletTemplate.objects.get(pk=id)
module_type_list = ObjectListField(ModuleTypeType) power_outlet_template_list: List[PowerOutletTemplateType] = strawberry_django.field()
def resolve_module_type_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.ModuleType.objects.all(), info) def power_panel(self, id: int) -> PowerPanelType:
return models.PowerPanel.objects.get(id=id)
platform = ObjectField(PlatformType) power_panel_list: List[PowerPanelType] = strawberry_django.field()
platform_list = ObjectListField(PlatformType)
@strawberry.field
def resolve_platform_list(root, info, **kwargs): def power_port(self, id: int) -> PowerPortType:
return gql_query_optimizer(models.Platform.objects.all(), info) return models.PowerPort.objects.get(id=id)
power_port_list: List[PowerPortType] = strawberry_django.field()
power_feed = ObjectField(PowerFeedType)
power_feed_list = ObjectListField(PowerFeedType) @strawberry.field
def power_port_template(self, id: int) -> PowerPortTemplateType:
def resolve_power_feed_list(root, info, **kwargs): return models.PowerPortTemplate.objects.get(id=id)
return gql_query_optimizer(models.PowerFeed.objects.all(), info) power_port_template_list: List[PowerPortTemplateType] = strawberry_django.field()
power_outlet = ObjectField(PowerOutletType) @strawberry.field
power_outlet_list = ObjectListField(PowerOutletType) def rack(self, id: int) -> RackType:
return models.Rack.objects.get(id=id)
def resolve_power_outlet_list(root, info, **kwargs): rack_list: List[RackType] = strawberry_django.field()
return gql_query_optimizer(models.PowerOutlet.objects.all(), info)
@strawberry.field
power_outlet_template = ObjectField(PowerOutletTemplateType) def rack_reservation(self, id: int) -> RackReservationType:
power_outlet_template_list = ObjectListField(PowerOutletTemplateType) return models.RackReservation.objects.get(id=id)
rack_reservation_list: List[RackReservationType] = strawberry_django.field()
def resolve_power_outlet_template_list(root, info, **kwargs):
return gql_query_optimizer(models.PowerOutletTemplate.objects.all(), info) @strawberry.field
def rack_role(self, id: int) -> RackRoleType:
power_panel = ObjectField(PowerPanelType) return models.RackRole.objects.get(id=id)
power_panel_list = ObjectListField(PowerPanelType) rack_role_list: List[RackRoleType] = strawberry_django.field()
def resolve_power_panel_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.PowerPanel.objects.all(), info) def rear_port(self, id: int) -> RearPortType:
return models.RearPort.objects.get(id=id)
power_port = ObjectField(PowerPortType) rear_port_list: List[RearPortType] = strawberry_django.field()
power_port_list = ObjectListField(PowerPortType)
@strawberry.field
def resolve_power_port_list(root, info, **kwargs): def rear_port_template(self, id: int) -> RearPortTemplateType:
return gql_query_optimizer(models.PowerPort.objects.all(), info) return models.RearPortTemplate.objects.get(id=id)
rear_port_template_list: List[RearPortTemplateType] = strawberry_django.field()
power_port_template = ObjectField(PowerPortTemplateType)
power_port_template_list = ObjectListField(PowerPortTemplateType) @strawberry.field
def region(self, id: int) -> RegionType:
def resolve_power_port_template_list(root, info, **kwargs): return models.Region.objects.get(id=id)
return gql_query_optimizer(models.PowerPortTemplate.objects.all(), info) region_list: List[RegionType] = strawberry_django.field()
rack = ObjectField(RackType) @strawberry.field
rack_list = ObjectListField(RackType) def site(self, id: int) -> SiteType:
return models.Site.objects.get(id=id)
def resolve_rack_list(root, info, **kwargs): site_list: List[SiteType] = strawberry_django.field()
return gql_query_optimizer(models.Rack.objects.all(), info)
@strawberry.field
rack_reservation = ObjectField(RackReservationType) def site_group(self, id: int) -> SiteGroupType:
rack_reservation_list = ObjectListField(RackReservationType) return models.SiteGroup.objects.get(id=id)
site_group_list: List[SiteGroupType] = strawberry_django.field()
def resolve_rack_reservation_list(root, info, **kwargs):
return gql_query_optimizer(models.RackReservation.objects.all(), info) @strawberry.field
def virtual_chassis(self, id: int) -> VirtualChassisType:
rack_role = ObjectField(RackRoleType) return models.VirtualChassis.objects.get(id=id)
rack_role_list = ObjectListField(RackRoleType) virtual_chassis_list: List[VirtualChassisType] = strawberry_django.field()
def resolve_rack_role_list(root, info, **kwargs): @strawberry.field
return gql_query_optimizer(models.RackRole.objects.all(), info) def virtual_device_context(self, id: int) -> VirtualDeviceContextType:
return models.VirtualDeviceContext.objects.get(id=id)
rear_port = ObjectField(RearPortType) virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()
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)

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 django.contrib.contenttypes.models import ContentType
from graphene.types.generic import GenericScalar
from extras.models import ObjectChange from extras.models import ObjectChange
@ -14,56 +16,67 @@ __all__ = (
'TagsMixin', 'TagsMixin',
) )
if TYPE_CHECKING:
from .types import ImageAttachmentType, JournalEntryType, ObjectChangeType, TagType
from tenancy.graphql.types import ContactAssignmentType
@strawberry.type
class ChangelogMixin: 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) content_type = ContentType.objects.get_for_model(self)
object_changes = ObjectChange.objects.filter( object_changes = ObjectChange.objects.filter(
changed_object_type=content_type, changed_object_type=content_type,
changed_object_id=self.pk 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: 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() return self.get_config_context()
@strawberry.type
class CustomFieldsMixin: 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 return self.custom_field_data
@strawberry.type
class ImageAttachmentsMixin: class ImageAttachmentsMixin:
image_attachments = graphene.List('extras.graphql.types.ImageAttachmentType')
def resolve_image_attachments(self, info): @strawberry_django.field
return self.images.restrict(info.context.user, 'view') def image_attachments(self, info) -> List[Annotated["ImageAttachmentType", strawberry.lazy('.types')]]:
return self.images.restrict(info.context.request.user, 'view')
@strawberry.type
class JournalEntriesMixin: class JournalEntriesMixin:
journal_entries = graphene.List('extras.graphql.types.JournalEntryType')
def resolve_journal_entries(self, info): @strawberry_django.field
return self.journal_entries.restrict(info.context.user, 'view') def journal_entries(self, info) -> List[Annotated["JournalEntryType", strawberry.lazy('.types')]]:
return self.journal_entries.all()
@strawberry.type
class TagsMixin: 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() return self.tags.all()
@strawberry.type
class ContactsMixin: 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()) 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 extras import models
from netbox.graphql.fields import ObjectField, ObjectListField
from .types import * from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
class ExtrasQuery(graphene.ObjectType): @strawberry.type
config_context = ObjectField(ConfigContextType) class ExtrasQuery:
config_context_list = ObjectListField(ConfigContextType) @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): @strawberry.field
return gql_query_optimizer(models.ConfigContext.objects.all(), info) 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) @strawberry.field
config_template_list = ObjectListField(ConfigTemplateType) 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): @strawberry.field
return gql_query_optimizer(models.ConfigTemplate.objects.all(), info) 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) @strawberry.field
custom_field_list = ObjectListField(CustomFieldType) 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): @strawberry.field
return gql_query_optimizer(models.CustomField.objects.all(), info) 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) @strawberry.field
custom_field_choice_set_list = ObjectListField(CustomFieldChoiceSetType) 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): @strawberry.field
return gql_query_optimizer(models.CustomFieldChoiceSet.objects.all(), info) 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) @strawberry.field
custom_link_list = ObjectListField(CustomLinkType) 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): @strawberry.field
return gql_query_optimizer(models.CustomLink.objects.all(), info) def tag(self, id: int) -> TagType:
return models.Tag.objects.get(pk=id)
tag_list: List[TagType] = strawberry_django.field()
export_template = ObjectField(ExportTemplateType) @strawberry.field
export_template_list = ObjectListField(ExportTemplateType) 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): @strawberry.field
return gql_query_optimizer(models.ExportTemplate.objects.all(), info) def event_rule(self, id: int) -> EventRuleType:
return models.EventRule.objects.get(pk=id)
image_attachment = ObjectField(ImageAttachmentType) event_rule_list: List[EventRuleType] = strawberry_django.field()
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)

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 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__ = ( __all__ = (
'ConfigContextType', 'ConfigContextType',
@ -19,104 +25,202 @@ __all__ = (
) )
@strawberry_django.type(
models.ConfigContext,
fields='__all__',
filters=ConfigContextFilter
)
class ConfigContextType(ObjectType): 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: @strawberry_django.field
model = models.ConfigContext def roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
fields = '__all__' return self.roles.all()
filterset_class = filtersets.ConfigContextFilterSet
@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): 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: @strawberry_django.field
model = models.ConfigTemplate def virtualmachines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
fields = '__all__' return self.virtualmachines.all()
filterset_class = filtersets.ConfigTemplateFilterSet
@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 CustomFieldType(ObjectType):
related_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
class Meta: choice_set: Annotated["CustomFieldChoiceSetType", strawberry.lazy('extras.graphql.types')] | None
model = models.CustomField
fields = '__all__'
filterset_class = filtersets.CustomFieldFilterSet
@strawberry_django.type(
models.CustomFieldChoiceSet,
exclude=('extra_choices', ),
filters=CustomFieldChoiceSetFilter
)
class CustomFieldChoiceSetType(ObjectType): class CustomFieldChoiceSetType(ObjectType):
class Meta: @strawberry_django.field
model = models.CustomFieldChoiceSet def choices_for(self) -> List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]:
fields = '__all__' return self.choices_for.all()
filterset_class = filtersets.CustomFieldChoiceSetFilterSet
@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 CustomLinkType(ObjectType):
pass
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
@strawberry_django.type(
models.ExportTemplate,
fields='__all__',
filters=ExportTemplateFilter
)
class ExportTemplateType(ObjectType): class ExportTemplateType(ObjectType):
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
class Meta: data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
model = models.ExportTemplate
fields = '__all__'
filterset_class = filtersets.ExportTemplateFilterSet
@strawberry_django.type(
models.ImageAttachment,
fields='__all__',
filters=ImageAttachmentFilter
)
class ImageAttachmentType(BaseObjectType): class ImageAttachmentType(BaseObjectType):
object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
class Meta:
model = models.ImageAttachment
fields = '__all__'
filterset_class = filtersets.ImageAttachmentFilterSet
@strawberry_django.type(
models.JournalEntry,
fields='__all__',
filters=JournalEntryFilter
)
class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType): class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
assigned_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
class Meta: created_by: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
model = models.JournalEntry
fields = '__all__'
filterset_class = filtersets.JournalEntryFilterSet
@strawberry_django.type(
models.ObjectChange,
fields='__all__',
filters=ObjectChangeFilter
)
class ObjectChangeType(BaseObjectType): class ObjectChangeType(BaseObjectType):
pass
class Meta:
model = models.ObjectChange
fields = '__all__'
filterset_class = filtersets.ObjectChangeFilterSet
@strawberry_django.type(
models.SavedFilter,
exclude=['content_types',],
filters=SavedFilterFilter
)
class SavedFilterType(ObjectType): class SavedFilterType(ObjectType):
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
class Meta:
model = models.SavedFilter
fields = '__all__'
filterset_class = filtersets.SavedFilterFilterSet
@strawberry_django.type(
models.Tag,
exclude=['extras_taggeditem_items', ],
filters=TagFilter
)
class TagType(ObjectType): class TagType(ObjectType):
color: str
class Meta: @strawberry_django.field
model = models.Tag def object_types(self) -> List[ContentTypeType]:
exclude = ('extras_taggeditem_items',) return self.object_types.all()
filterset_class = filtersets.TagFilterSet
@strawberry_django.type(
models.Webhook,
exclude=['content_types',],
filters=WebhookFilter
)
class WebhookType(OrganizationalObjectType): class WebhookType(OrganizationalObjectType):
pass
class Meta:
model = models.Webhook @strawberry_django.type(
filterset_class = filtersets.WebhookFilterSet 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__ = ( __all__ = (
'IPAddressesMixin', 'IPAddressesMixin',
@ -6,15 +9,15 @@ __all__ = (
) )
@strawberry.type
class IPAddressesMixin: class IPAddressesMixin:
ip_addresses = graphene.List('ipam.graphql.types.IPAddressType') @strawberry_django.field
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
def resolve_ip_addresses(self, info): return self.ip_addresses.all()
return self.ip_addresses.restrict(info.context.user, 'view')
@strawberry.type
class VLANGroupsMixin: class VLANGroupsMixin:
vlan_groups = graphene.List('ipam.graphql.types.VLANGroupType') @strawberry_django.field
def vlan_groups(self) -> List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]]:
def resolve_vlan_groups(self, info): return self.vlan_groups.all()
return self.vlan_groups.restrict(info.context.user, 'view')

View File

@ -1,104 +1,90 @@
import graphene from typing import List
import strawberry
import strawberry_django
from ipam import models from ipam import models
from netbox.graphql.fields import ObjectField, ObjectListField
from utilities.graphql_optimizer import gql_query_optimizer
from .types import * from .types import *
class IPAMQuery(graphene.ObjectType): @strawberry.type
asn = ObjectField(ASNType) class IPAMQuery:
asn_list = ObjectListField(ASNType) @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): @strawberry.field
return gql_query_optimizer(models.ASN.objects.all(), info) 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) @strawberry.field
asn_range_list = ObjectListField(ASNRangeType) 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): @strawberry.field
return gql_query_optimizer(models.ASNRange.objects.all(), info) 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) @strawberry.field
aggregate_list = ObjectListField(AggregateType) 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): @strawberry.field
return gql_query_optimizer(models.Aggregate.objects.all(), info) def prefix(self, id: int) -> PrefixType:
return models.Prefix.objects.get(pk=id)
prefix_list: List[PrefixType] = strawberry_django.field()
ip_address = ObjectField(IPAddressType) @strawberry.field
ip_address_list = ObjectListField(IPAddressType) 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): @strawberry.field
return gql_query_optimizer(models.IPAddress.objects.all(), info) def role(self, id: int) -> RoleType:
return models.Role.objects.get(pk=id)
role_list: List[RoleType] = strawberry_django.field()
ip_range = ObjectField(IPRangeType) @strawberry.field
ip_range_list = ObjectListField(IPRangeType) 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): @strawberry.field
return gql_query_optimizer(models.IPRange.objects.all(), info) def service(self, id: int) -> ServiceType:
return models.Service.objects.get(pk=id)
service_list: List[ServiceType] = strawberry_django.field()
prefix = ObjectField(PrefixType) @strawberry.field
prefix_list = ObjectListField(PrefixType) 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): @strawberry.field
return gql_query_optimizer(models.Prefix.objects.all(), info) 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) @strawberry.field
rir_list = ObjectListField(RIRType) 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): @strawberry.field
return gql_query_optimizer(models.RIR.objects.all(), info) def vlan(self, id: int) -> VLANType:
return models.VLAN.objects.get(pk=id)
vlan_list: List[VLANType] = strawberry_django.field()
role = ObjectField(RoleType) @strawberry.field
role_list = ObjectListField(RoleType) 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): @strawberry.field
return gql_query_optimizer(models.Role.objects.all(), info) def vrf(self, id: int) -> VRFType:
return models.VRF.objects.get(pk=id)
route_target = ObjectField(RouteTargetType) vrf_list: List[VRFType] = strawberry_django.field()
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)

View File

@ -1,9 +1,15 @@
import graphene from typing import Annotated, List, Union
from ipam import filtersets, models import strawberry
from .mixins import IPAddressesMixin 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.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__ = ( __all__ = (
'ASNType', 'ASNType',
@ -25,164 +31,335 @@ __all__ = (
) )
class IPAddressFamilyType(graphene.ObjectType): @strawberry.type
class IPAddressFamilyType:
value = graphene.Int() value: int
label = graphene.String() label: str
def __init__(self, value):
self.value = value
self.label = f'IPv{value}'
@strawberry.type
class BaseIPAddressFamilyType: class BaseIPAddressFamilyType:
""" """
Base type for models that need to expose their IPAddress family type. 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 # Note that self, is an instance of models.IPAddress
# thus resolves to the address family value. # 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): 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: @strawberry_django.field
model = models.ASN def sites(self) -> List[SiteType]:
fields = '__all__' return self.sites.all()
filterset_class = filtersets.ASNFilterSet
@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 ASNRangeType(NetBoxObjectType):
start: BigInt
class Meta: end: BigInt
model = models.ASNRange rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
fields = '__all__' tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
filterset_class = filtersets.ASNRangeFilterSet
@strawberry_django.type(
models.Aggregate,
fields='__all__',
filters=AggregateFilter
)
class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType): class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
prefix: str
class Meta: rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
model = models.Aggregate tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
fields = '__all__'
filterset_class = filtersets.AggregateFilterSet
@strawberry_django.type(
models.FHRPGroup,
fields='__all__',
filters=FHRPGroupFilter
)
class FHRPGroupType(NetBoxObjectType, IPAddressesMixin): class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
class Meta: @strawberry_django.field
model = models.FHRPGroup def fhrpgroupassignment_set(self) -> List[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.fhrpgroupassignment_set.all()
filterset_class = filtersets.FHRPGroupFilterSet
def resolve_auth_type(self, info):
return self.auth_type or None
@strawberry_django.type(
models.FHRPGroupAssignment,
exclude=('interface_type', 'interface_id'),
filters=FHRPGroupAssignmentFilter
)
class FHRPGroupAssignmentType(BaseObjectType): class FHRPGroupAssignmentType(BaseObjectType):
interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType') group: Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')]
class Meta: @strawberry_django.field
model = models.FHRPGroupAssignment def interface(self) -> Annotated[Union[
exclude = ('interface_type', 'interface_id') Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
filterset_class = filtersets.FHRPGroupAssignmentFilterSet 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): 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: @strawberry_django.field
model = models.IPAddress def nat_outside(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
exclude = ('assigned_object_type', 'assigned_object_id') return self.nat_outside.all()
filterset_class = filtersets.IPAddressFilterSet
def resolve_role(self, info): @strawberry_django.field
return self.role or None 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 IPRangeType(NetBoxObjectType):
start_address: str
class Meta: end_address: str
model = models.IPRange vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
fields = '__all__' tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
filterset_class = filtersets.IPRangeFilterSet role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
def resolve_role(self, info):
return self.role or None
@strawberry_django.type(
models.Prefix,
fields='__all__',
filters=PrefixFilter
)
class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType): class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType):
prefix: str
class Meta: site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
model = models.Prefix vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
fields = '__all__' tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
filterset_class = filtersets.PrefixFilterSet 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 RIRType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.RIR def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.asn_ranges.all()
filterset_class = filtersets.RIRFilterSet
@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 RoleType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.Role def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.prefixes.all()
filterset_class = filtersets.RoleFilterSet
@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): class RouteTargetType(NetBoxObjectType):
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.RouteTarget def exporting_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.exporting_l2vpns.all()
filterset_class = filtersets.RouteTargetFilterSet
@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): 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: @strawberry_django.field
model = models.Service def ipaddresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.ipaddresses.all()
filterset_class = filtersets.ServiceFilterSet
@strawberry_django.type(
models.ServiceTemplate,
fields='__all__',
filters=ServiceTemplateFilter
)
class ServiceTemplateType(NetBoxObjectType): class ServiceTemplateType(NetBoxObjectType):
ports: List[int]
class Meta:
model = models.ServiceTemplate
fields = '__all__'
filterset_class = filtersets.ServiceTemplateFilterSet
@strawberry_django.type(
models.VLAN,
fields='__all__',
filters=VLANFilter
)
class VLANType(NetBoxObjectType): 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: @strawberry_django.field
model = models.VLAN def interfaces_as_untagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
fields = '__all__' return self.interfaces_as_untagged.all()
filterset_class = filtersets.VLANFilterSet
@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): class VLANGroupType(OrganizationalObjectType):
scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType')
class Meta: @strawberry_django.field
model = models.VLANGroup def vlans(self) -> List[VLANType]:
exclude = ('scope_type', 'scope_id') return self.vlans.all()
filterset_class = filtersets.VLANGroupFilterSet
@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): class VRFType(NetBoxObjectType):
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.VRF def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
fields = '__all__' return self.interfaces.all()
filterset_class = filtersets.VRFFilterSet
@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 typing import Union
from graphql.language import ast
from graphene.types.scalars import MAX_INT, MIN_INT
import strawberry
class BigInt(Scalar): BigInt = strawberry.scalar(
""" Union[int, str], # type: ignore
Handle any BigInts serialize=lambda v: int(v),
""" parse_value=lambda v: str(v),
@staticmethod description="BigInt field",
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)

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 circuits.graphql.schema import CircuitsQuery
from core.graphql.schema import CoreQuery from core.graphql.schema import CoreQuery
@ -13,6 +15,7 @@ from vpn.graphql.schema import VPNQuery
from wireless.graphql.schema import WirelessQuery from wireless.graphql.schema import WirelessQuery
@strawberry.type
class Query( class Query(
UsersQuery, UsersQuery,
CircuitsQuery, CircuitsQuery,
@ -25,9 +28,14 @@ class Query(
VPNQuery, VPNQuery,
WirelessQuery, WirelessQuery,
*registry['plugins']['graphql_schemas'], # Append plugin schemas *registry['plugins']['graphql_schemas'], # Append plugin schemas
graphene.ObjectType
): ):
pass 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 core.models import ObjectType as ObjectType_
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -8,13 +12,10 @@ from extras.graphql.mixins import (
JournalEntriesMixin, JournalEntriesMixin,
TagsMixin, TagsMixin,
) )
from graphene_django import DjangoObjectType
__all__ = ( __all__ = (
'BaseObjectType', 'BaseObjectType',
'ContentTypeType',
'ObjectType', 'ObjectType',
'ObjectTypeType',
'OrganizationalObjectType', 'OrganizationalObjectType',
'NetBoxObjectType', 'NetBoxObjectType',
) )
@ -24,26 +25,27 @@ __all__ = (
# Base types # 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. 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 @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info, **kwargs):
# Enforce object permissions on the queryset # 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): @strawberry_django.field
return str(parent) def display(self) -> str:
return str(self)
def resolve_class_type(parent, info, **kwargs): @strawberry_django.field
return parent.__class__.__name__ def class_type(self) -> str:
return self.__class__.__name__
class ObjectType( class ObjectType(
@ -53,8 +55,7 @@ class ObjectType(
""" """
Base GraphQL object type for unclassified models which support change logging Base GraphQL object type for unclassified models which support change logging
""" """
class Meta: pass
abstract = True
class OrganizationalObjectType( class OrganizationalObjectType(
@ -66,8 +67,7 @@ class OrganizationalObjectType(
""" """
Base type for organizational models Base type for organizational models
""" """
class Meta: pass
abstract = True
class NetBoxObjectType( class NetBoxObjectType(
@ -80,23 +80,24 @@ class NetBoxObjectType(
""" """
GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags. GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags.
""" """
class Meta: pass
abstract = True
# #
# Miscellaneous types # Miscellaneous types
# #
class ContentTypeType(DjangoObjectType): @strawberry_django.type(
ContentType,
class Meta: fields=['id', 'app_label', 'model'],
model = ContentType )
fields = ('id', 'app_label', 'model') class ContentTypeType:
pass
class ObjectTypeType(DjangoObjectType): @strawberry_django.type(
ObjectType_,
class Meta: fields=['id', 'app_label', 'model'],
model = 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.conf import settings
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseNotFound, HttpResponseForbidden from django.http import HttpResponseNotFound, HttpResponseForbidden
from django.http import HttpResponse
from django.template import loader
from django.urls import reverse 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 rest_framework.exceptions import AuthenticationFailed
from strawberry.django.views import GraphQLView
from netbox.api.authentication import TokenAuthentication from netbox.api.authentication import TokenAuthentication
from netbox.config import get_config 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' graphiql_template = 'graphiql.html'
@csrf_exempt
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
config = get_config() config = get_config()
@ -34,11 +40,15 @@ class GraphQLView(GraphQLView_):
# Enforce LOGIN_REQUIRED # Enforce LOGIN_REQUIRED
if settings.LOGIN_REQUIRED and not request.user.is_authenticated: if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
if request.accepts("text/html"):
# If this is a human user, send a redirect to the login page
if self.request_wants_html(request):
return redirect_to_login(reverse('graphql')) return redirect_to_login(reverse('graphql'))
else:
return HttpResponseForbidden("No credentials provided.") return HttpResponseForbidden("No credentials provided.")
return super().dispatch(request, *args, **kwargs) 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. 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): def register_user_preferences(plugin_name, preferences):

View File

@ -365,12 +365,11 @@ INSTALLED_APPS = [
'django.forms', 'django.forms',
'corsheaders', 'corsheaders',
'debug_toolbar', 'debug_toolbar',
'graphiql_debug_toolbar',
'django_filters', 'django_filters',
'django_htmx', 'django_htmx',
'django_tables2', 'django_tables2',
'django_prometheus', 'django_prometheus',
'graphene_django', 'strawberry_django',
'mptt', 'mptt',
'rest_framework', 'rest_framework',
'social_django', 'social_django',
@ -398,7 +397,7 @@ if DJANGO_ADMIN_ENABLED:
# Middleware # Middleware
MIDDLEWARE = [ MIDDLEWARE = [
'graphiql_debug_toolbar.middleware.DebugToolbarMiddleware', "strawberry_django.middlewares.debug_toolbar.DebugToolbarMiddleware",
'django_prometheus.middleware.PrometheusBeforeMiddleware', 'django_prometheus.middleware.PrometheusBeforeMiddleware',
'corsheaders.middleware.CorsMiddleware', 'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@ -674,17 +673,6 @@ SPECTACULAR_SETTINGS = {
'POSTPROCESSING_HOOKS': [], '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) # Django RQ (events backend)
# #
@ -749,6 +737,13 @@ if not ENABLE_LOCALIZATION:
USE_I18N = False USE_I18N = False
USE_L10N = False USE_L10N = False
#
# Strawberry (GraphQL)
#
STRAWBERRY_DJANGO = {
"TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True,
}
# #
# Plugins # Plugins
# #

View File

@ -1,21 +1,26 @@
import graphene from typing import List
from graphene_django import DjangoObjectType import strawberry
import strawberry_django
from netbox.graphql.fields import ObjectField, ObjectListField
from . import models from . import models
class DummyModelType(DjangoObjectType): @strawberry_django.type(
models.DummyModel,
class Meta: fields='__all__',
model = models.DummyModel )
fields = '__all__' class DummyModelType:
pass
class DummyQuery(graphene.ObjectType): @strawberry.type
dummymodel = ObjectField(DummyModelType) class DummyQuery:
dummymodel_list = ObjectListField(DummyModelType) @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 import settings
from django.conf.urls import include from django.conf.urls import include
from django.urls import path from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from django.views.static import serve from django.views.static import serve
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from account.views import LoginView, LogoutView from account.views import LoginView, LogoutView
from netbox.api.views import APIRootView, StatusView from netbox.api.views import APIRootView, StatusView
from netbox.graphql.schema import schema 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.plugins.urls import plugin_patterns, plugin_api_patterns
from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
from strawberry.django.views import GraphQLView
_patterns = [ _patterns = [
@ -60,7 +60,7 @@ _patterns = [
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'),
# GraphQL # 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 # Serving static media in Django to pipe it through LoginRequiredMiddleware
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}), path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),

View File

@ -1,5 +1,8 @@
const esbuild = require('esbuild'); const esbuild = require('esbuild');
const { sassPlugin } = require('esbuild-sass-plugin'); 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. // Bundler options common to all bundle jobs.
const options = { const options = {
@ -14,24 +17,57 @@ const options = {
// Get CLI arguments for optional overrides. // Get CLI arguments for optional overrides.
const ARGS = process.argv.slice(2); const ARGS = process.argv.slice(2);
function copyFiles(files) {
return Promise.all(files.map(f => {
return copyFilePromise(f.source, f.dest);
}));
}
async function bundleGraphIQL() { 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 { try {
const result = await esbuild.build({ if (!fs.existsSync('./dist/graphiql/')) {
...options, fs.mkdirSync('./dist/graphiql/');
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'`);
} }
} catch (err) { } catch (err) {
console.error(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', 'netbox': 'styles/netbox.scss',
rack_elevation: 'styles/svg/rack_elevation.scss', rack_elevation: 'styles/svg/rack_elevation.scss',
cable_trace: 'styles/svg/cable_trace.scss', cable_trace: 'styles/svg/cable_trace.scss',
graphiql: 'netbox-graphiql/graphiql.scss',
}; };
const pluginOptions = { outputStyle: 'compressed' }; const pluginOptions = { outputStyle: 'compressed' };
// Allow cache disabling. // 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", "name": "netbox-graphiql",
"version": "0.1.0", "version": "0.2.0",
"description": "NetBox GraphiQL Custom Front End", "description": "NetBox GraphiQL Custom Front End",
"main": "dist/graphiql.js", "main": "dist/graphiql.js",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"graphiql": "1.8.9", "graphiql": "3.0.9",
"graphql": ">= v14.5.0 <= 15.5.0", "graphql": "16.8.1",
"react": "17.0.2", "react": "18.2.0",
"react-dom": "17.0.2", "react-dom": "18.2.0",
"subscriptions-transport-ws": "0.9.18", "react-scripts": "5.0.1",
"whatwg-fetch": "3.6.2" "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 %} {% comment %}
This template derives from the graphene-django project: This template derives from the strawberry-graphql project:
https://github.com/graphql-python/graphene-django/blob/main/graphene_django/templates/graphene/graphiql.html https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/static/graphiql.html
{% endcomment %} {% endcomment %}
<!-- <!--
The request to this GraphQL server provided the header "Accept: text/html" 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 %} {% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <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> <style>
html, body, #editor { body {
height: 100%; height: 100%;
margin: 0; margin: 0;
overflow: hidden;
width: 100%; 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> </style>
<link href="{% static 'graphiql.css'%}" rel="stylesheet" />
<link rel="icon" type="image/png" href="{% static 'graphql.ico' %}" /> <script src="{% static 'graphiql/react.production.min.js' %}"></script>
<title>GraphiQL | NetBox</title> <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> </head>
<body> <body>
<div id="editor"></div> <div id="graphiql" class="graphiql-container">Loading...</div>
{% csrf_token %} <script src="{% static 'graphiql/graphiql.min.js' %}"></script>
<script type="application/javascript"> <script src="{% static 'graphiql/index.umd.js' %}"></script>
window.GRAPHENE_SETTINGS = {
{% if subscription_path %} <script>
subscriptionPath: "{{subscription_path}}", const EXAMPLE_QUERY = `# Welcome to GraphiQL 🍓
{% endif %} #
graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }}, # 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>
<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> </body>
</html> </html>

View File

@ -127,11 +127,10 @@ class ContactAssignmentFilterSet(NetBoxModelFilterSet):
to_field_name='slug', to_field_name='slug',
label=_('Contact role (slug)'), label=_('Contact role (slug)'),
) )
tag = TagFilter()
class Meta: class Meta:
model = ContactAssignment 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): def search(self, queryset, name, value):
if not value.strip(): 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 tenancy import models
from .types import * from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
class TenancyQuery(graphene.ObjectType): @strawberry.type
tenant = ObjectField(TenantType) class TenancyQuery:
tenant_list = ObjectListField(TenantType) @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): @strawberry.field
return gql_query_optimizer(models.Tenant.objects.all(), info) 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) @strawberry.field
tenant_group_list = ObjectListField(TenantGroupType) 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): @strawberry.field
return gql_query_optimizer(models.TenantGroup.objects.all(), info) 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) @strawberry.field
contact_list = ObjectListField(ContactType) 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): @strawberry.field
return gql_query_optimizer(models.Contact.objects.all(), info) def contact_assignment(self, id: int) -> ContactAssignmentType:
return models.ContactAssignment.objects.get(pk=id)
contact_role = ObjectField(ContactRoleType) contact_assignment_list: List[ContactAssignmentType] = strawberry_django.field()
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)

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 extras.graphql.mixins import CustomFieldsMixin, TagsMixin
from tenancy import filtersets, models
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
from tenancy import models
from .mixins import ContactAssignmentsMixin
from .filters import *
__all__ = ( __all__ = (
'ContactAssignmentType', '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 # Tenants
# #
@strawberry_django.type(
models.Tenant,
fields='__all__',
filters=TenantFilter
)
class TenantType(NetBoxObjectType): class TenantType(NetBoxObjectType):
group: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.Tenant def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.asns.all()
filterset_class = filtersets.TenantFilterSet
@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): class TenantGroupType(OrganizationalObjectType):
parent: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.TenantGroup def tenants(self) -> List[TenantType]:
fields = '__all__' return self.tenants.all()
filterset_class = filtersets.TenantGroupFilterSet
# #
# Contacts # Contacts
# #
@strawberry_django.type(
models.Contact,
fields='__all__',
filters=ContactFilter
)
class ContactType(ContactAssignmentsMixin, NetBoxObjectType): class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
group: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta:
model = models.Contact
fields = '__all__'
filterset_class = filtersets.ContactFilterSet
@strawberry_django.type(
models.ContactRole,
fields='__all__',
filters=ContactRoleFilter
)
class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType): class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType):
pass
class Meta:
model = models.ContactRole
fields = '__all__'
filterset_class = filtersets.ContactRoleFilterSet
@strawberry_django.type(
models.ContactGroup,
fields='__all__',
filters=ContactGroupFilter
)
class ContactGroupType(OrganizationalObjectType): class ContactGroupType(OrganizationalObjectType):
parent: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.ContactGroup def contacts(self) -> List[ContactType]:
fields = '__all__' return self.contacts.all()
filterset_class = filtersets.ContactGroupFilterSet
@strawberry_django.type(
models.ContactAssignment,
fields='__all__',
filters=ContactAssignmentFilter
)
class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType): class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType):
object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
class Meta: contact: Annotated["ContactType", strawberry.lazy('tenancy.graphql.types')] | None
model = models.ContactAssignment role: Annotated["ContactRoleType", strawberry.lazy('tenancy.graphql.types')] | None
fields = '__all__'
filterset_class = filtersets.ContactAssignmentFilterSet

View File

@ -1,4 +1,5 @@
import django_filters import django_filters
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _ 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 typing import List
from django.contrib.auth import get_user_model import strawberry
import strawberry_django
from netbox.graphql.fields import ObjectField, ObjectListField from django.contrib.auth import get_user_model
from users.models import Group from django.contrib.auth.models import Group
from utilities.graphql_optimizer import gql_query_optimizer from users import models
from .types import * from .types import *
class UsersQuery(graphene.ObjectType): @strawberry.type
group = ObjectField(GroupType) class UsersQuery:
group_list = ObjectListField(GroupType) @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): @strawberry.field
return gql_query_optimizer(Group.objects.all(), info) def user(self, id: int) -> UserType:
return models.User.objects.get(pk=id)
user = ObjectField(UserType) user_list: List[UserType] = strawberry_django.field()
user_list = ObjectListField(UserType)
def resolve_user_list(root, info, **kwargs):
return gql_query_optimizer(get_user_model().objects.all(), info)

View File

@ -1,9 +1,14 @@
from django.contrib.auth import get_user_model from typing import List
from graphene_django import DjangoObjectType
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 import filtersets
from users.models import Group from users.models import Group
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from .filters import *
__all__ = ( __all__ = (
'GroupType', 'GroupType',
@ -11,28 +16,24 @@ __all__ = (
) )
class GroupType(DjangoObjectType): @strawberry_django.type(
Group,
class Meta: fields=['id', 'name'],
model = Group filters=GroupFilter
fields = ('id', 'name') )
filterset_class = filtersets.GroupFilterSet class GroupType:
pass
@classmethod
def get_queryset(cls, queryset, info):
return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view')
class UserType(DjangoObjectType): @strawberry_django.type(
get_user_model(),
class Meta: fields=[
model = get_user_model() 'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff',
fields = ( 'is_active', 'date_joined', 'groups',
'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined', ],
'groups', filters=UserFilter
) )
filterset_class = filtersets.UserFilterSet class UserType:
@strawberry_django.field
@classmethod def groups(self) -> List[GroupType]:
def get_queryset(cls, queryset, info): return self.groups.all()
return RestrictedQuerySet(model=get_user_model()).restrict(info.context.user, 'view')

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 inspect
import json import json
import strawberry_django
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.urls import reverse from django.urls import reverse
from django.test import override_settings 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 import status
from rest_framework.test import APIClient from rest_framework.test import APIClient
@ -19,7 +19,10 @@ from .base import ModelTestCase
from .utils import disable_warnings from .utils import disable_warnings
from ipam.graphql.types import IPAddressFamilyType 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__ = ( __all__ = (
'APITestCase', 'APITestCase',
@ -447,34 +450,34 @@ class APIViewTestCases:
# Compile list of fields to include # Compile list of fields to include
fields_string = '' 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: file_fields = (strawberry_django.fields.types.DjangoFileType, strawberry_django.fields.types.DjangoImageType)
# Dynamic fields must specify a subselection for field in type_class.__strawberry_definition__.fields:
fields_string += f'{field_name} {{ id }}\n' if (
# TODO: Improve field detection logic to avoid nested ArrayFields field.type in file_fields or (
elif field_name == 'extra_choices': 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 continue
elif inspect.isclass(field.type) and issubclass(field.type, GQLUnion): elif type(field.type) is StrawberryUnion:
# Union types dont' have an id or consistent values # this would require a fragment query
continue continue
elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion): elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
# Union types dont' have an id or consistent values fields_string += f'{field.name} {{ id }}\n'
continue elif hasattr(field, 'is_relation') and field.is_relation:
elif type(field.type) is GQLList and not is_string_array: # Note: StrawberryField types do not have is_relation
# TODO: Come up with something more elegant fields_string += f'{field.name} {{ id }}\n'
# Temporary hack to support automated testing of reverse generic relations
fields_string += f'{field_name} {{ id }}\n'
elif inspect.isclass(field.type) and issubclass(field.type, IPAddressFamilyType): 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: else:
fields_string += f'{field_name}\n' fields_string += f'{field.name}\n'
query = f""" query = f"""
{{ {{
@ -496,7 +499,10 @@ class APIViewTestCases:
# Non-authenticated requests should fail # Non-authenticated requests should fail
with disable_warnings('django.request'): 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 # Add object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
@ -507,7 +513,7 @@ class APIViewTestCases:
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) 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) self.assertHttpStatus(response, status.HTTP_200_OK)
data = json.loads(response.content) data = json.loads(response.content)
self.assertNotIn('errors', data) self.assertNotIn('errors', data)
@ -521,7 +527,10 @@ class APIViewTestCases:
# Non-authenticated requests should fail # Non-authenticated requests should fail
with disable_warnings('django.request'): 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 # Add object-level permission
obj_perm = ObjectPermission( obj_perm = ObjectPermission(
@ -532,7 +541,7 @@ class APIViewTestCases:
obj_perm.users.add(self.user) obj_perm.users.add(self.user)
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model)) 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) self.assertHttpStatus(response, status.HTTP_200_OK)
data = json.loads(response.content) data = json.loads(response.content)
self.assertNotIn('errors', data) 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 virtualization import models
from .types import *
class VirtualizationQuery(graphene.ObjectType): @strawberry.type
cluster = ObjectField(ClusterType) class VirtualizationQuery:
cluster_list = ObjectListField(ClusterType) @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): @strawberry.field
return gql_query_optimizer(models.Cluster.objects.all(), info) 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) @strawberry.field
cluster_group_list = ObjectListField(ClusterGroupType) 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): @strawberry.field
return gql_query_optimizer(models.ClusterGroup.objects.all(), info) 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) @strawberry.field
cluster_type_list = ObjectListField(ClusterTypeType) 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): @strawberry.field
return gql_query_optimizer(models.ClusterType.objects.all(), info) def virtual_disk(self, id: int) -> VirtualDiskType:
return models.VirtualDisk.objects.get(pk=id)
virtual_machine = ObjectField(VirtualMachineType) virtual_disk_list: List[VirtualDiskType] = strawberry_django.field()
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)

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 extras.graphql.mixins import ConfigContextMixin, ContactsMixin
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.scalars import BigInt
from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
from virtualization import filtersets, models from virtualization import models
from .filters import *
__all__ = ( __all__ = (
'ClusterType', '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): 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: @strawberry_django.field
model = models.Cluster def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
fields = '__all__' return self.virtual_machines.all()
filterset_class = filtersets.ClusterFilterSet
@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 ClusterGroupType(VLANGroupsMixin, OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.ClusterGroup def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
fields = '__all__' return self.clusters.all()
filterset_class = filtersets.ClusterGroupFilterSet
@strawberry_django.type(
models.ClusterType,
fields='__all__',
filters=ClusterTypeFilter
)
class ClusterTypeType(OrganizationalObjectType): class ClusterTypeType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.ClusterType def clusters(self) -> List[ClusterType]:
fields = '__all__' return self.clusters.all()
filterset_class = filtersets.ClusterTypeFilterSet
@strawberry_django.type(
models.VirtualMachine,
fields='__all__',
filters=VirtualMachineFilter
)
class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType): 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: @strawberry_django.field
model = models.VirtualMachine def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
fields = '__all__' return self.interfaces.all()
filterset_class = filtersets.VirtualMachineFilterSet
@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: @strawberry_django.field
model = models.VMInterface def tagged_vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
fields = '__all__' return self.tagged_vlans.all()
filterset_class = filtersets.VMInterfaceFilterSet
def resolve_mode(self, info): @strawberry_django.field
return self.mode or None 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): @strawberry_django.type(
models.VirtualDisk,
class Meta: fields='__all__',
model = models.VirtualDisk filters=VirtualDiskFilter
fields = '__all__' )
filterset_class = filtersets.VirtualDiskFilterSet class VirtualDiskType(ComponentType):
pass
def resolve_mode(self, info):
return self.mode or None

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 vpn import models
from .types import * 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) @strawberry.field
ike_policy_list = ObjectListField(IKEPolicyType) 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): @strawberry.field
return gql_query_optimizer(models.IKEPolicy.objects.all(), info) 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) @strawberry.field
ike_proposal_list = ObjectListField(IKEProposalType) 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): @strawberry.field
return gql_query_optimizer(models.IKEProposal.objects.all(), info) 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) @strawberry.field
ipsec_policy_list = ObjectListField(IPSecPolicyType) 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): @strawberry.field
return gql_query_optimizer(models.IPSecPolicy.objects.all(), info) 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) @strawberry.field
ipsec_profile_list = ObjectListField(IPSecProfileType) 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): @strawberry.field
return gql_query_optimizer(models.IPSecProfile.objects.all(), info) 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) @strawberry.field
ipsec_proposal_list = ObjectListField(IPSecProposalType) def tunnel_termination(self, id: int) -> TunnelTerminationType:
return models.TunnelTermination.objects.get(pk=id)
def resolve_ipsec_proposal_list(root, info, **kwargs): tunnel_termination_list: List[TunnelTerminationType] = strawberry_django.field()
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)

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 extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
from vpn import filtersets, models from vpn import models
from .filters import *
__all__ = ( __all__ = (
'IKEPolicyType', 'IKEPolicyType',
@ -18,81 +22,147 @@ __all__ = (
) )
@strawberry_django.type(
models.TunnelGroup,
fields='__all__',
filters=TunnelGroupFilter
)
class TunnelGroupType(OrganizationalObjectType): class TunnelGroupType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.TunnelGroup def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.tunnels.all()
filterset_class = filtersets.TunnelGroupFilterSet
@strawberry_django.type(
models.TunnelTermination,
fields='__all__',
filters=TunnelTerminationFilter
)
class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType): class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
tunnel: Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]
class Meta: termination_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
model = models.TunnelTermination outside_ip: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
fields = '__all__'
filterset_class = filtersets.TunnelTerminationFilterSet
@strawberry_django.type(
models.Tunnel,
fields='__all__',
filters=TunnelFilter
)
class TunnelType(NetBoxObjectType): 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: @strawberry_django.field
model = models.Tunnel def terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.terminations.all()
filterset_class = filtersets.TunnelFilterSet
@strawberry_django.type(
models.IKEProposal,
fields='__all__',
filters=IKEProposalFilter
)
class IKEProposalType(OrganizationalObjectType): class IKEProposalType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.IKEProposal def ike_policies(self) -> List[Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.ike_policies.all()
filterset_class = filtersets.IKEProposalFilterSet
@strawberry_django.type(
models.IKEPolicy,
fields='__all__',
filters=IKEPolicyFilter
)
class IKEPolicyType(OrganizationalObjectType): class IKEPolicyType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.IKEPolicy def proposals(self) -> List[Annotated["IKEProposalType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.proposals.all()
filterset_class = filtersets.IKEPolicyFilterSet
@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 IPSecProposalType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.IPSecProposal def ipsec_policies(self) -> List[Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.ipsec_policies.all()
filterset_class = filtersets.IPSecProposalFilterSet
@strawberry_django.type(
models.IPSecPolicy,
fields='__all__',
filters=IPSecPolicyFilter
)
class IPSecPolicyType(OrganizationalObjectType): class IPSecPolicyType(OrganizationalObjectType):
class Meta: @strawberry_django.field
model = models.IPSecPolicy def proposals(self) -> List[Annotated["IPSecProposalType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.proposals.all()
filterset_class = filtersets.IPSecPolicyFilterSet
@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): class IPSecProfileType(OrganizationalObjectType):
ike_policy: Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]
ipsec_policy: Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]
class Meta: @strawberry_django.field
model = models.IPSecProfile def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
fields = '__all__' return self.tunnels.all()
filterset_class = filtersets.IPSecProfileFilterSet
@strawberry_django.type(
models.L2VPN,
fields='__all__',
filters=L2VPNFilter
)
class L2VPNType(ContactsMixin, NetBoxObjectType): class L2VPNType(ContactsMixin, NetBoxObjectType):
class Meta: tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
model = models.L2VPN
fields = '__all__' @strawberry_django.field
filtersets_class = filtersets.L2VPNFilterSet 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): class L2VPNTerminationType(NetBoxObjectType):
assigned_object = graphene.Field('vpn.graphql.gfk_mixins.L2VPNAssignmentType') l2vpn: Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]
class Meta: @strawberry_django.field
model = models.L2VPNTermination def assigned_object(self) -> Annotated[Union[
exclude = ('assigned_object_type', 'assigned_object_id') Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
filtersets_class = filtersets.L2VPNTerminationFilterSet 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 wireless import models
from .types import *
class WirelessQuery(graphene.ObjectType): @strawberry.type
wireless_lan = ObjectField(WirelessLANType) class WirelessQuery:
wireless_lan_list = ObjectListField(WirelessLANType) @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): @strawberry.field
return gql_query_optimizer(models.WirelessLAN.objects.all(), info) 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) @strawberry.field
wireless_lan_group_list = ObjectListField(WirelessLANGroupType) def wireless_link(self, id: int) -> WirelessLinkType:
return models.WirelessLink.objects.get(pk=id)
def resolve_wireless_lan_group_list(root, info, **kwargs): wireless_link_list: List[WirelessLinkType] = strawberry_django.field()
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)

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 netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
from wireless import models
from .filters import *
__all__ = ( __all__ = (
'WirelessLANType', 'WirelessLANType',
@ -8,37 +14,42 @@ __all__ = (
) )
@strawberry_django.type(
models.WirelessLANGroup,
fields='__all__',
filters=WirelessLANGroupFilter
)
class WirelessLANGroupType(OrganizationalObjectType): class WirelessLANGroupType(OrganizationalObjectType):
parent: Annotated["WirelessLANGroupType", strawberry.lazy('wireless.graphql.types')] | None
class Meta: @strawberry_django.field
model = models.WirelessLANGroup def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
fields = '__all__' return self.wireless_lans.all()
filterset_class = filtersets.WirelessLANGroupFilterSet
@strawberry_django.type(
models.WirelessLAN,
fields='__all__',
filters=WirelessLANFilter
)
class WirelessLANType(NetBoxObjectType): 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: @strawberry_django.field
model = models.WirelessLAN def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
fields = '__all__' return self.interfaces.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.type(
models.WirelessLink,
fields='__all__',
filters=WirelessLinkFilter
)
class WirelessLinkType(NetBoxObjectType): class WirelessLinkType(NetBoxObjectType):
interface_a: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
class Meta: interface_b: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
model = models.WirelessLink tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
fields = '__all__' _interface_a_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
filterset_class = filtersets.WirelessLinkFilterSet _interface_b_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
def resolve_auth_type(self, info):
return self.auth_type or None
def resolve_auth_cipher(self, info):
return self.auth_cipher or None

View File

@ -2,7 +2,6 @@ Django==5.0.3
django-cors-headers==4.3.1 django-cors-headers==4.3.1
django-debug-toolbar==4.3.0 django-debug-toolbar==4.3.0
django-filter==24.1 django-filter==24.1
django-graphiql-debug-toolbar==0.2.0
django-htmx==1.17.3 django-htmx==1.17.3
django-mptt==0.14.0 django-mptt==0.14.0
django-pglocks==1.0.4 django-pglocks==1.0.4
@ -17,7 +16,6 @@ djangorestframework==3.14.0
drf-spectacular==0.27.1 drf-spectacular==0.27.1
drf-spectacular-sidecar==2024.3.4 drf-spectacular-sidecar==2024.3.4
feedparser==6.0.11 feedparser==6.0.11
graphene-django==3.0.0
gunicorn==21.2.0 gunicorn==21.2.0
Jinja2==3.1.3 Jinja2==3.1.3
Markdown==3.5.2 Markdown==3.5.2
@ -31,6 +29,8 @@ PyYAML==6.0.1
requests==2.31.0 requests==2.31.0
social-auth-app-django==5.4.0 social-auth-app-django==5.4.0
social-auth-core[openidconnect]==4.5.3 social-auth-core[openidconnect]==4.5.3
strawberry-graphql==0.221.1
strawberry-graphql-django==0.35.1
svgwrite==1.4.3 svgwrite==1.4.3
tablib==3.5.0 tablib==3.5.0
tzdata==2024.1 tzdata==2024.1