mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Merge pull request #6873 from netbox-community/6829-graphql-reverse-relations
Closes #6829: GraphQL reverse generic relations
This commit is contained in:
commit
c411d2a9f1
@ -2,6 +2,10 @@
|
||||
|
||||
## v3.0-beta2 (FUTURE)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#6829](https://github.com/netbox-community/netbox/issues/6829) - Extend GraphQL API to support reverse generic relationships
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#6811](https://github.com/netbox-community/netbox/issues/6811) - Fix exception when editing users
|
||||
|
@ -1,5 +1,5 @@
|
||||
from circuits import filtersets, models
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType, TaggedObjectType
|
||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationType',
|
||||
@ -10,7 +10,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CircuitTerminationType(BaseObjectType):
|
||||
class CircuitTerminationType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitTermination
|
||||
@ -18,7 +18,7 @@ class CircuitTerminationType(BaseObjectType):
|
||||
filterset_class = filtersets.CircuitTerminationFilterSet
|
||||
|
||||
|
||||
class CircuitType(TaggedObjectType):
|
||||
class CircuitType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Circuit
|
||||
@ -26,7 +26,7 @@ class CircuitType(TaggedObjectType):
|
||||
filterset_class = filtersets.CircuitFilterSet
|
||||
|
||||
|
||||
class CircuitTypeType(ObjectType):
|
||||
class CircuitTypeType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitType
|
||||
@ -34,7 +34,7 @@ class CircuitTypeType(ObjectType):
|
||||
filterset_class = filtersets.CircuitTypeFilterSet
|
||||
|
||||
|
||||
class ProviderType(TaggedObjectType):
|
||||
class ProviderType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Provider
|
||||
@ -42,7 +42,7 @@ class ProviderType(TaggedObjectType):
|
||||
filterset_class = filtersets.ProviderFilterSet
|
||||
|
||||
|
||||
class ProviderNetworkType(TaggedObjectType):
|
||||
class ProviderNetworkType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ProviderNetwork
|
||||
|
@ -1,8 +1,11 @@
|
||||
from dcim import filtersets, models
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType, TaggedObjectType
|
||||
from extras.graphql.mixins import ChangelogMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin
|
||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'CableType',
|
||||
'ComponentObjectType',
|
||||
'ConsolePortType',
|
||||
'ConsolePortTemplateType',
|
||||
'ConsoleServerPortType',
|
||||
@ -38,7 +41,40 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CableType(TaggedObjectType):
|
||||
#
|
||||
# Base types
|
||||
#
|
||||
|
||||
|
||||
class ComponentObjectType(
|
||||
ChangelogMixin,
|
||||
CustomFieldsMixin,
|
||||
TagsMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Base type for device/VM components
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ComponentTemplateObjectType(
|
||||
ChangelogMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Base type for device/VM components
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
#
|
||||
# Model types
|
||||
#
|
||||
|
||||
class CableType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Cable
|
||||
@ -52,7 +88,7 @@ class CableType(TaggedObjectType):
|
||||
return self.length_unit or None
|
||||
|
||||
|
||||
class ConsolePortType(TaggedObjectType):
|
||||
class ConsolePortType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsolePort
|
||||
@ -63,7 +99,7 @@ class ConsolePortType(TaggedObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class ConsolePortTemplateType(BaseObjectType):
|
||||
class ConsolePortTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsolePortTemplate
|
||||
@ -74,7 +110,7 @@ class ConsolePortTemplateType(BaseObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class ConsoleServerPortType(TaggedObjectType):
|
||||
class ConsoleServerPortType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsoleServerPort
|
||||
@ -85,7 +121,7 @@ class ConsoleServerPortType(TaggedObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateType(BaseObjectType):
|
||||
class ConsoleServerPortTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsoleServerPortTemplate
|
||||
@ -96,7 +132,7 @@ class ConsoleServerPortTemplateType(BaseObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class DeviceType(TaggedObjectType):
|
||||
class DeviceType(ImageAttachmentsMixin, PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Device
|
||||
@ -107,7 +143,7 @@ class DeviceType(TaggedObjectType):
|
||||
return self.face or None
|
||||
|
||||
|
||||
class DeviceBayType(TaggedObjectType):
|
||||
class DeviceBayType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceBay
|
||||
@ -115,7 +151,7 @@ class DeviceBayType(TaggedObjectType):
|
||||
filterset_class = filtersets.DeviceBayFilterSet
|
||||
|
||||
|
||||
class DeviceBayTemplateType(BaseObjectType):
|
||||
class DeviceBayTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceBayTemplate
|
||||
@ -123,7 +159,7 @@ class DeviceBayTemplateType(BaseObjectType):
|
||||
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceRoleType(ObjectType):
|
||||
class DeviceRoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceRole
|
||||
@ -131,7 +167,7 @@ class DeviceRoleType(ObjectType):
|
||||
filterset_class = filtersets.DeviceRoleFilterSet
|
||||
|
||||
|
||||
class DeviceTypeType(TaggedObjectType):
|
||||
class DeviceTypeType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceType
|
||||
@ -142,7 +178,7 @@ class DeviceTypeType(TaggedObjectType):
|
||||
return self.subdevice_role or None
|
||||
|
||||
|
||||
class FrontPortType(TaggedObjectType):
|
||||
class FrontPortType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.FrontPort
|
||||
@ -150,7 +186,7 @@ class FrontPortType(TaggedObjectType):
|
||||
filterset_class = filtersets.FrontPortFilterSet
|
||||
|
||||
|
||||
class FrontPortTemplateType(BaseObjectType):
|
||||
class FrontPortTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.FrontPortTemplate
|
||||
@ -158,7 +194,7 @@ class FrontPortTemplateType(BaseObjectType):
|
||||
filterset_class = filtersets.FrontPortTemplateFilterSet
|
||||
|
||||
|
||||
class InterfaceType(TaggedObjectType):
|
||||
class InterfaceType(IPAddressesMixin, ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Interface
|
||||
@ -169,7 +205,7 @@ class InterfaceType(TaggedObjectType):
|
||||
return self.mode or None
|
||||
|
||||
|
||||
class InterfaceTemplateType(BaseObjectType):
|
||||
class InterfaceTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.InterfaceTemplate
|
||||
@ -177,7 +213,7 @@ class InterfaceTemplateType(BaseObjectType):
|
||||
filterset_class = filtersets.InterfaceTemplateFilterSet
|
||||
|
||||
|
||||
class InventoryItemType(TaggedObjectType):
|
||||
class InventoryItemType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.InventoryItem
|
||||
@ -185,7 +221,7 @@ class InventoryItemType(TaggedObjectType):
|
||||
filterset_class = filtersets.InventoryItemFilterSet
|
||||
|
||||
|
||||
class LocationType(ObjectType):
|
||||
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Location
|
||||
@ -193,7 +229,7 @@ class LocationType(ObjectType):
|
||||
filterset_class = filtersets.LocationFilterSet
|
||||
|
||||
|
||||
class ManufacturerType(ObjectType):
|
||||
class ManufacturerType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Manufacturer
|
||||
@ -201,7 +237,7 @@ class ManufacturerType(ObjectType):
|
||||
filterset_class = filtersets.ManufacturerFilterSet
|
||||
|
||||
|
||||
class PlatformType(ObjectType):
|
||||
class PlatformType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Platform
|
||||
@ -209,7 +245,7 @@ class PlatformType(ObjectType):
|
||||
filterset_class = filtersets.PlatformFilterSet
|
||||
|
||||
|
||||
class PowerFeedType(TaggedObjectType):
|
||||
class PowerFeedType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerFeed
|
||||
@ -217,7 +253,7 @@ class PowerFeedType(TaggedObjectType):
|
||||
filterset_class = filtersets.PowerFeedFilterSet
|
||||
|
||||
|
||||
class PowerOutletType(TaggedObjectType):
|
||||
class PowerOutletType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerOutlet
|
||||
@ -231,7 +267,7 @@ class PowerOutletType(TaggedObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class PowerOutletTemplateType(BaseObjectType):
|
||||
class PowerOutletTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerOutletTemplate
|
||||
@ -245,7 +281,7 @@ class PowerOutletTemplateType(BaseObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class PowerPanelType(TaggedObjectType):
|
||||
class PowerPanelType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPanel
|
||||
@ -253,7 +289,7 @@ class PowerPanelType(TaggedObjectType):
|
||||
filterset_class = filtersets.PowerPanelFilterSet
|
||||
|
||||
|
||||
class PowerPortType(TaggedObjectType):
|
||||
class PowerPortType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPort
|
||||
@ -264,7 +300,7 @@ class PowerPortType(TaggedObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class PowerPortTemplateType(BaseObjectType):
|
||||
class PowerPortTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPortTemplate
|
||||
@ -275,7 +311,7 @@ class PowerPortTemplateType(BaseObjectType):
|
||||
return self.type or None
|
||||
|
||||
|
||||
class RackType(TaggedObjectType):
|
||||
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Rack
|
||||
@ -289,7 +325,7 @@ class RackType(TaggedObjectType):
|
||||
return self.outer_unit or None
|
||||
|
||||
|
||||
class RackReservationType(TaggedObjectType):
|
||||
class RackReservationType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RackReservation
|
||||
@ -297,7 +333,7 @@ class RackReservationType(TaggedObjectType):
|
||||
filterset_class = filtersets.RackReservationFilterSet
|
||||
|
||||
|
||||
class RackRoleType(ObjectType):
|
||||
class RackRoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RackRole
|
||||
@ -305,7 +341,7 @@ class RackRoleType(ObjectType):
|
||||
filterset_class = filtersets.RackRoleFilterSet
|
||||
|
||||
|
||||
class RearPortType(TaggedObjectType):
|
||||
class RearPortType(ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RearPort
|
||||
@ -313,7 +349,7 @@ class RearPortType(TaggedObjectType):
|
||||
filterset_class = filtersets.RearPortFilterSet
|
||||
|
||||
|
||||
class RearPortTemplateType(BaseObjectType):
|
||||
class RearPortTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RearPortTemplate
|
||||
@ -321,7 +357,7 @@ class RearPortTemplateType(BaseObjectType):
|
||||
filterset_class = filtersets.RearPortTemplateFilterSet
|
||||
|
||||
|
||||
class RegionType(ObjectType):
|
||||
class RegionType(VLANGroupsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Region
|
||||
@ -329,7 +365,7 @@ class RegionType(ObjectType):
|
||||
filterset_class = filtersets.RegionFilterSet
|
||||
|
||||
|
||||
class SiteType(TaggedObjectType):
|
||||
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Site
|
||||
@ -337,7 +373,7 @@ class SiteType(TaggedObjectType):
|
||||
filterset_class = filtersets.SiteFilterSet
|
||||
|
||||
|
||||
class SiteGroupType(ObjectType):
|
||||
class SiteGroupType(VLANGroupsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.SiteGroup
|
||||
@ -345,7 +381,7 @@ class SiteGroupType(ObjectType):
|
||||
filterset_class = filtersets.SiteGroupFilterSet
|
||||
|
||||
|
||||
class VirtualChassisType(TaggedObjectType):
|
||||
class VirtualChassisType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VirtualChassis
|
||||
|
@ -175,6 +175,12 @@ class Rack(PrimaryModel):
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='rack'
|
||||
)
|
||||
images = GenericRelation(
|
||||
to='extras.ImageAttachment'
|
||||
)
|
||||
|
@ -53,6 +53,12 @@ class Region(NestedGroupModel):
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='region'
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:region', args=[self.pk])
|
||||
@ -95,6 +101,12 @@ class SiteGroup(NestedGroupModel):
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='site_group'
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:sitegroup', args=[self.pk])
|
||||
@ -210,6 +222,12 @@ class Site(PrimaryModel):
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='site'
|
||||
)
|
||||
images = GenericRelation(
|
||||
to='extras.ImageAttachment'
|
||||
)
|
||||
@ -267,6 +285,12 @@ class Location(NestedGroupModel):
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='location'
|
||||
)
|
||||
images = GenericRelation(
|
||||
to='extras.ImageAttachment'
|
||||
)
|
||||
|
45
netbox/extras/graphql/mixins.py
Normal file
45
netbox/extras/graphql/mixins.py
Normal file
@ -0,0 +1,45 @@
|
||||
import graphene
|
||||
from graphene.types.generic import GenericScalar
|
||||
|
||||
__all__ = (
|
||||
'ChangelogMixin',
|
||||
'CustomFieldsMixin',
|
||||
'ImageAttachmentsMixin',
|
||||
'JournalEntriesMixin',
|
||||
'TagsMixin',
|
||||
)
|
||||
|
||||
|
||||
class ChangelogMixin:
|
||||
changelog = graphene.List('extras.graphql.types.ObjectChangeType')
|
||||
|
||||
def resolve_changelog(self, info):
|
||||
return self.object_changes.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class CustomFieldsMixin:
|
||||
custom_fields = GenericScalar()
|
||||
|
||||
def resolve_custom_fields(self, info):
|
||||
return self.custom_field_data
|
||||
|
||||
|
||||
class ImageAttachmentsMixin:
|
||||
image_attachments = graphene.List('extras.graphql.types.ImageAttachmentType')
|
||||
|
||||
def resolve_image_attachments(self, info):
|
||||
return self.images.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class JournalEntriesMixin:
|
||||
journal_entries = graphene.List('extras.graphql.types.JournalEntryType')
|
||||
|
||||
def resolve_journal_entries(self, info):
|
||||
return self.journal_entries.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class TagsMixin:
|
||||
tags = graphene.List(graphene.String)
|
||||
|
||||
def resolve_tags(self, info):
|
||||
return self.tags.all()
|
@ -1,5 +1,5 @@
|
||||
from extras import filtersets, models
|
||||
from netbox.graphql.types import BaseObjectType
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextType',
|
||||
@ -8,12 +8,13 @@ __all__ = (
|
||||
'ExportTemplateType',
|
||||
'ImageAttachmentType',
|
||||
'JournalEntryType',
|
||||
'ObjectChangeType',
|
||||
'TagType',
|
||||
'WebhookType',
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextType(BaseObjectType):
|
||||
class ConfigContextType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ConfigContext
|
||||
@ -21,7 +22,7 @@ class ConfigContextType(BaseObjectType):
|
||||
filterset_class = filtersets.ConfigContextFilterSet
|
||||
|
||||
|
||||
class CustomFieldType(BaseObjectType):
|
||||
class CustomFieldType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomField
|
||||
@ -29,7 +30,7 @@ class CustomFieldType(BaseObjectType):
|
||||
filterset_class = filtersets.CustomFieldFilterSet
|
||||
|
||||
|
||||
class CustomLinkType(BaseObjectType):
|
||||
class CustomLinkType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomLink
|
||||
@ -37,7 +38,7 @@ class CustomLinkType(BaseObjectType):
|
||||
filterset_class = filtersets.CustomLinkFilterSet
|
||||
|
||||
|
||||
class ExportTemplateType(BaseObjectType):
|
||||
class ExportTemplateType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ExportTemplate
|
||||
@ -53,7 +54,7 @@ class ImageAttachmentType(BaseObjectType):
|
||||
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||
|
||||
|
||||
class JournalEntryType(BaseObjectType):
|
||||
class JournalEntryType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.JournalEntry
|
||||
@ -61,7 +62,15 @@ class JournalEntryType(BaseObjectType):
|
||||
filterset_class = filtersets.JournalEntryFilterSet
|
||||
|
||||
|
||||
class TagType(BaseObjectType):
|
||||
class ObjectChangeType(BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ObjectChange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ObjectChangeFilterSet
|
||||
|
||||
|
||||
class TagType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Tag
|
||||
@ -69,7 +78,7 @@ class TagType(BaseObjectType):
|
||||
filterset_class = filtersets.TagFilterSet
|
||||
|
||||
|
||||
class WebhookType(BaseObjectType):
|
||||
class WebhookType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Webhook
|
||||
|
20
netbox/ipam/graphql/mixins.py
Normal file
20
netbox/ipam/graphql/mixins.py
Normal file
@ -0,0 +1,20 @@
|
||||
import graphene
|
||||
|
||||
__all__ = (
|
||||
'IPAddressesMixin',
|
||||
'VLANGroupsMixin',
|
||||
)
|
||||
|
||||
|
||||
class IPAddressesMixin:
|
||||
ip_addresses = graphene.List('ipam.graphql.types.IPAddressType')
|
||||
|
||||
def resolve_ip_addresses(self, info):
|
||||
return self.ip_addresses.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class VLANGroupsMixin:
|
||||
vlan_groups = graphene.List('ipam.graphql.types.VLANGroupType')
|
||||
|
||||
def resolve_vlan_groups(self, info):
|
||||
return self.vlan_groups.restrict(info.context.user, 'view')
|
@ -1,5 +1,5 @@
|
||||
from ipam import filtersets, models
|
||||
from netbox.graphql.types import ObjectType, TaggedObjectType
|
||||
from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'AggregateType',
|
||||
@ -16,7 +16,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class AggregateType(TaggedObjectType):
|
||||
class AggregateType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Aggregate
|
||||
@ -24,7 +24,7 @@ class AggregateType(TaggedObjectType):
|
||||
filterset_class = filtersets.AggregateFilterSet
|
||||
|
||||
|
||||
class IPAddressType(TaggedObjectType):
|
||||
class IPAddressType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPAddress
|
||||
@ -35,7 +35,7 @@ class IPAddressType(TaggedObjectType):
|
||||
return self.role or None
|
||||
|
||||
|
||||
class IPRangeType(TaggedObjectType):
|
||||
class IPRangeType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPRange
|
||||
@ -46,7 +46,7 @@ class IPRangeType(TaggedObjectType):
|
||||
return self.role or None
|
||||
|
||||
|
||||
class PrefixType(TaggedObjectType):
|
||||
class PrefixType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Prefix
|
||||
@ -54,7 +54,7 @@ class PrefixType(TaggedObjectType):
|
||||
filterset_class = filtersets.PrefixFilterSet
|
||||
|
||||
|
||||
class RIRType(ObjectType):
|
||||
class RIRType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RIR
|
||||
@ -62,7 +62,7 @@ class RIRType(ObjectType):
|
||||
filterset_class = filtersets.RIRFilterSet
|
||||
|
||||
|
||||
class RoleType(ObjectType):
|
||||
class RoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Role
|
||||
@ -70,7 +70,7 @@ class RoleType(ObjectType):
|
||||
filterset_class = filtersets.RoleFilterSet
|
||||
|
||||
|
||||
class RouteTargetType(TaggedObjectType):
|
||||
class RouteTargetType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RouteTarget
|
||||
@ -78,7 +78,7 @@ class RouteTargetType(TaggedObjectType):
|
||||
filterset_class = filtersets.RouteTargetFilterSet
|
||||
|
||||
|
||||
class ServiceType(TaggedObjectType):
|
||||
class ServiceType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Service
|
||||
@ -86,7 +86,7 @@ class ServiceType(TaggedObjectType):
|
||||
filterset_class = filtersets.ServiceFilterSet
|
||||
|
||||
|
||||
class VLANType(TaggedObjectType):
|
||||
class VLANType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VLAN
|
||||
@ -94,7 +94,7 @@ class VLANType(TaggedObjectType):
|
||||
filterset_class = filtersets.VLANFilterSet
|
||||
|
||||
|
||||
class VLANGroupType(ObjectType):
|
||||
class VLANGroupType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VLANGroup
|
||||
@ -102,7 +102,7 @@ class VLANGroupType(ObjectType):
|
||||
filterset_class = filtersets.VLANGroupFilterSet
|
||||
|
||||
|
||||
class VRFType(TaggedObjectType):
|
||||
class VRFType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VRF
|
||||
|
@ -1,12 +1,12 @@
|
||||
import graphene
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from graphene.types.generic import GenericScalar
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
from extras.graphql.mixins import ChangelogMixin, CustomFieldsMixin, JournalEntriesMixin, TagsMixin
|
||||
|
||||
__all__ = (
|
||||
'BaseObjectType',
|
||||
'ObjectType',
|
||||
'TaggedObjectType',
|
||||
'OrganizationalObjectType',
|
||||
'PrimaryObjectType',
|
||||
)
|
||||
|
||||
|
||||
@ -27,30 +27,41 @@ class BaseObjectType(DjangoObjectType):
|
||||
return queryset.restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class ObjectType(BaseObjectType):
|
||||
class ObjectType(
|
||||
ChangelogMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Extends BaseObjectType with support for custom field data.
|
||||
Base GraphQL object type for unclassified models which support change logging
|
||||
"""
|
||||
custom_fields = GenericScalar()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def resolve_custom_fields(self, info):
|
||||
return self.custom_field_data
|
||||
|
||||
|
||||
class TaggedObjectType(ObjectType):
|
||||
class OrganizationalObjectType(
|
||||
ChangelogMixin,
|
||||
CustomFieldsMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Extends ObjectType with support for Tags
|
||||
Base type for organizational models
|
||||
"""
|
||||
tags = graphene.List(graphene.String)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def resolve_tags(self, info):
|
||||
return self.tags.all()
|
||||
|
||||
class PrimaryObjectType(
|
||||
ChangelogMixin,
|
||||
CustomFieldsMixin,
|
||||
JournalEntriesMixin,
|
||||
TagsMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Base type for primary models
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
#
|
||||
|
@ -40,6 +40,11 @@ class ChangeLoggingMixin(models.Model):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
object_changes = GenericRelation(
|
||||
to='extras.ObjectChange',
|
||||
content_type_field='changed_object_type',
|
||||
object_id_field='changed_object_id'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -1,5 +1,5 @@
|
||||
from tenancy import filtersets, models
|
||||
from netbox.graphql.types import ObjectType, TaggedObjectType
|
||||
from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'TenantType',
|
||||
@ -7,7 +7,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class TenantType(TaggedObjectType):
|
||||
class TenantType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Tenant
|
||||
@ -15,7 +15,7 @@ class TenantType(TaggedObjectType):
|
||||
filterset_class = filtersets.TenantFilterSet
|
||||
|
||||
|
||||
class TenantGroupType(ObjectType):
|
||||
class TenantGroupType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.TenantGroup
|
||||
|
@ -5,7 +5,7 @@ from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
from django.test import override_settings
|
||||
from graphene.types.dynamic import Dynamic
|
||||
from graphene.types import Dynamic as GQLDynamic, List as GQLList
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
@ -446,9 +446,13 @@ class APIViewTestCases:
|
||||
# Compile list of fields to include
|
||||
fields_string = ''
|
||||
for field_name, field in type_class._meta.fields.items():
|
||||
if type(field) is Dynamic:
|
||||
if type(field) is GQLDynamic:
|
||||
# Dynamic fields must specify a subselection
|
||||
fields_string += f'{field_name} {{ id }}\n'
|
||||
elif type(field.type) is GQLList and field_name not in ('tags', 'choices'):
|
||||
# TODO: Come up with something more elegant
|
||||
# Temporary hack to support automated testing of reverse generic relations
|
||||
fields_string += f'{field_name} {{ id }}\n'
|
||||
else:
|
||||
fields_string += f'{field_name}\n'
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
from dcim.graphql.types import ComponentObjectType
|
||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||
from virtualization import filtersets, models
|
||||
from netbox.graphql.types import ObjectType, TaggedObjectType
|
||||
from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType
|
||||
|
||||
__all__ = (
|
||||
'ClusterType',
|
||||
@ -10,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ClusterType(TaggedObjectType):
|
||||
class ClusterType(VLANGroupsMixin, PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Cluster
|
||||
@ -18,7 +20,7 @@ class ClusterType(TaggedObjectType):
|
||||
filterset_class = filtersets.ClusterFilterSet
|
||||
|
||||
|
||||
class ClusterGroupType(ObjectType):
|
||||
class ClusterGroupType(VLANGroupsMixin, OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ClusterGroup
|
||||
@ -26,7 +28,7 @@ class ClusterGroupType(ObjectType):
|
||||
filterset_class = filtersets.ClusterGroupFilterSet
|
||||
|
||||
|
||||
class ClusterTypeType(ObjectType):
|
||||
class ClusterTypeType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ClusterType
|
||||
@ -34,7 +36,7 @@ class ClusterTypeType(ObjectType):
|
||||
filterset_class = filtersets.ClusterTypeFilterSet
|
||||
|
||||
|
||||
class VirtualMachineType(TaggedObjectType):
|
||||
class VirtualMachineType(PrimaryObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VirtualMachine
|
||||
@ -42,7 +44,7 @@ class VirtualMachineType(TaggedObjectType):
|
||||
filterset_class = filtersets.VirtualMachineFilterSet
|
||||
|
||||
|
||||
class VMInterfaceType(TaggedObjectType):
|
||||
class VMInterfaceType(IPAddressesMixin, ComponentObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.VMInterface
|
||||
|
@ -81,6 +81,12 @@ class ClusterGroup(OrganizationalModel):
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='cluster_group'
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
@ -136,6 +142,12 @@ class Cluster(PrimaryModel):
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
content_type_field='scope_type',
|
||||
object_id_field='scope_id',
|
||||
related_query_name='cluster'
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user