mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-10 22:02:17 -06:00
Compare commits
11 Commits
main
...
19724-grap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76caae12fa | ||
|
|
26c91f01c6 | ||
|
|
af55da008b | ||
|
|
810d1c2418 | ||
|
|
91b2d61ea4 | ||
|
|
b7b7b00885 | ||
|
|
595b343cd0 | ||
|
|
730aee9b26 | ||
|
|
8aa1e2802b | ||
|
|
c2d19119cb | ||
|
|
0c4d0fa2e8 |
@@ -11,7 +11,7 @@ curl -H "Authorization: Token $TOKEN" \
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
http://netbox/graphql/ \
|
http://netbox/graphql/ \
|
||||||
--data '{"query": "query {circuit_list(filters:{status: STATUS_ACTIVE}) {cid provider {name}}}"}'
|
--data '{"query": "query {circuit_list(filters:{status: STATUS_ACTIVE}) {results {cid provider {name}}}}"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
The response will include the requested data formatted as JSON:
|
The response will include the requested data formatted as JSON:
|
||||||
@@ -36,6 +36,30 @@ The response will include the requested data formatted as JSON:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
If using the GraphQL API v2 the format will be:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"circuit_list": {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"cid": "1002840283",
|
||||||
|
"provider": {
|
||||||
|
"name": "CenturyLink"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cid": "1002840457",
|
||||||
|
"provider": {
|
||||||
|
"name": "CenturyLink"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
It's recommended to pass the return data through a JSON parser such as `jq` for better readability.
|
It's recommended to pass the return data through a JSON parser such as `jq` for better readability.
|
||||||
@@ -47,12 +71,15 @@ NetBox provides both a singular and plural query field for each object type:
|
|||||||
|
|
||||||
For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of filters) to fetch all devices.
|
For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of filters) to fetch all devices.
|
||||||
|
|
||||||
|
!!! note "Changed in NetBox v4.5"
|
||||||
|
If using the GraphQL API v2, List queries now return paginated results. The actual objects are contained within the `results` field of the response, along with `total_count` and `page_info` fields for pagination metadata. Prior to v4.5, list queries returned objects directly as an array.
|
||||||
|
|
||||||
For more detail on constructing GraphQL queries, see the [GraphQL queries documentation](https://graphql.org/learn/queries/). For filtering and lookup syntax, please refer to the [Strawberry Django documentation](https://strawberry.rocks/docs/django/guide/filters).
|
For more detail on constructing GraphQL queries, see the [GraphQL queries documentation](https://graphql.org/learn/queries/). For filtering and lookup syntax, please refer to the [Strawberry Django documentation](https://strawberry.rocks/docs/django/guide/filters).
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
!!! note "Changed in NetBox v4.3"
|
!!! note "Changed in NetBox v4.3"
|
||||||
The filtering syntax fo the GraphQL API has changed substantially in NetBox v4.3.
|
The filtering syntax for the GraphQL API has changed substantially in NetBox v4.3.
|
||||||
|
|
||||||
Filters can be specified as key-value pairs within parentheses immediately following the query name. For example, the following will return only active sites:
|
Filters can be specified as key-value pairs within parentheses immediately following the query name. For example, the following will return only active sites:
|
||||||
|
|
||||||
@@ -67,6 +94,21 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
If using the GraphQL API v2 the format will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
query {
|
||||||
|
site_list(
|
||||||
|
filters: {
|
||||||
|
status: STATUS_ACTIVE
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
results {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Filters can be combined with logical operators, such as `OR` and `NOT`. For example, the following will return every site that is planned _or_ assigned to a tenant named Foo:
|
Filters can be combined with logical operators, such as `OR` and `NOT`. For example, the following will return every site that is planned _or_ assigned to a tenant named Foo:
|
||||||
|
|
||||||
@@ -88,6 +130,28 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
If using the GraphQL API v2 the format will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
query {
|
||||||
|
site_list(
|
||||||
|
filters: {
|
||||||
|
status: STATUS_PLANNED,
|
||||||
|
OR: {
|
||||||
|
tenant: {
|
||||||
|
name: {
|
||||||
|
exact: "Foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
results {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Filtering can also be applied to related objects. For example, the following query will return only enabled interfaces for each device:
|
Filtering can also be applied to related objects. For example, the following query will return only enabled interfaces for each device:
|
||||||
|
|
||||||
@@ -102,6 +166,21 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
If using the GraphQL API v2 the format will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
query {
|
||||||
|
device_list {
|
||||||
|
results {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
interfaces(filters: {enabled: {exact: true}}) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Multiple Return Types
|
## Multiple Return Types
|
||||||
|
|
||||||
@@ -128,6 +207,31 @@ Certain queries can return multiple types of objects, for example cable terminat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
If using the GraphQL API v2 the format will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
cable_list {
|
||||||
|
results {
|
||||||
|
id
|
||||||
|
a_terminations {
|
||||||
|
... on CircuitTerminationType {
|
||||||
|
id
|
||||||
|
class_type
|
||||||
|
}
|
||||||
|
... on ConsolePortType {
|
||||||
|
id
|
||||||
|
class_type
|
||||||
|
}
|
||||||
|
... on ConsoleServerPortType {
|
||||||
|
id
|
||||||
|
class_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The field "class_type" is an easy way to distinguish what type of object it is when viewing the returned data, or when filtering. It contains the class name, for example "CircuitTermination" or "ConsoleServerPort".
|
The field "class_type" is an easy way to distinguish what type of object it is when viewing the returned data, or when filtering. It contains the class name, for example "CircuitTermination" or "ConsoleServerPort".
|
||||||
|
|
||||||
@@ -142,6 +246,47 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
### Pagination in GraphQL API V2
|
||||||
|
|
||||||
|
All list queries return paginated results using the `OffsetPaginated` type, which includes:
|
||||||
|
|
||||||
|
- `results`: The list of objects matching the query
|
||||||
|
- `total_count`: The total number of objects matching the filters (without pagination)
|
||||||
|
- `page_info`: Pagination metadata including `offset` and `limit`
|
||||||
|
|
||||||
|
By default, queries return up to 100 results. You can control pagination by specifying the `pagination` parameter with `offset` and `limit` values:
|
||||||
|
|
||||||
|
```
|
||||||
|
query {
|
||||||
|
device_list(pagination: { offset: 0, limit: 20 }) {
|
||||||
|
total_count
|
||||||
|
page_info {
|
||||||
|
offset
|
||||||
|
limit
|
||||||
|
}
|
||||||
|
results {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't need pagination metadata, you can simply query the `results`:
|
||||||
|
|
||||||
|
```
|
||||||
|
query {
|
||||||
|
device_list {
|
||||||
|
results {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
When not specifying the `pagination` parameter, avoid querying `page_info.limit` as it may return an undefined value. Either provide explicit pagination parameters or only query the `results` and `total_count` fields.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class CircuitsQuery:
|
class CircuitsQueryV1:
|
||||||
circuit: CircuitType = strawberry_django.field()
|
circuit: CircuitType = strawberry_django.field()
|
||||||
circuit_list: List[CircuitType] = strawberry_django.field()
|
circuit_list: List[CircuitType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -40,3 +41,41 @@ class CircuitsQuery:
|
|||||||
|
|
||||||
virtual_circuit_type: VirtualCircuitTypeType = strawberry_django.field()
|
virtual_circuit_type: VirtualCircuitTypeType = strawberry_django.field()
|
||||||
virtual_circuit_type_list: List[VirtualCircuitTypeType] = strawberry_django.field()
|
virtual_circuit_type_list: List[VirtualCircuitTypeType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class CircuitsQuery:
|
||||||
|
circuit: CircuitType = strawberry_django.field()
|
||||||
|
circuit_list: OffsetPaginated[CircuitType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
circuit_termination: CircuitTerminationType = strawberry_django.field()
|
||||||
|
circuit_termination_list: OffsetPaginated[CircuitTerminationType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
circuit_type: CircuitTypeType = strawberry_django.field()
|
||||||
|
circuit_type_list: OffsetPaginated[CircuitTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
circuit_group: CircuitGroupType = strawberry_django.field()
|
||||||
|
circuit_group_list: OffsetPaginated[CircuitGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
circuit_group_assignment: CircuitGroupAssignmentType = strawberry_django.field()
|
||||||
|
circuit_group_assignment_list: OffsetPaginated[CircuitGroupAssignmentType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
provider: ProviderType = strawberry_django.field()
|
||||||
|
provider_list: OffsetPaginated[ProviderType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
provider_account: ProviderAccountType = strawberry_django.field()
|
||||||
|
provider_account_list: OffsetPaginated[ProviderAccountType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
provider_network: ProviderNetworkType = strawberry_django.field()
|
||||||
|
provider_network_list: OffsetPaginated[ProviderNetworkType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_circuit: VirtualCircuitType = strawberry_django.field()
|
||||||
|
virtual_circuit_list: OffsetPaginated[VirtualCircuitType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_circuit_termination: VirtualCircuitTerminationType = strawberry_django.field()
|
||||||
|
virtual_circuit_termination_list: OffsetPaginated[VirtualCircuitTerminationType] = (
|
||||||
|
strawberry_django.offset_paginated()
|
||||||
|
)
|
||||||
|
|
||||||
|
virtual_circuit_type: VirtualCircuitTypeType = strawberry_django.field()
|
||||||
|
virtual_circuit_type_list: OffsetPaginated[VirtualCircuitTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,14 +2,24 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class CoreQuery:
|
class CoreQueryV1:
|
||||||
data_file: DataFileType = strawberry_django.field()
|
data_file: DataFileType = strawberry_django.field()
|
||||||
data_file_list: List[DataFileType] = strawberry_django.field()
|
data_file_list: List[DataFileType] = strawberry_django.field()
|
||||||
|
|
||||||
data_source: DataSourceType = strawberry_django.field()
|
data_source: DataSourceType = strawberry_django.field()
|
||||||
data_source_list: List[DataSourceType] = strawberry_django.field()
|
data_source_list: List[DataSourceType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class CoreQuery:
|
||||||
|
data_file: DataFileType = strawberry_django.field()
|
||||||
|
data_file_list: OffsetPaginated[DataFileType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
data_source: DataSourceType = strawberry_django.field()
|
||||||
|
data_source_list: OffsetPaginated[DataSourceType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class DCIMQuery:
|
class DCIMQueryV1:
|
||||||
cable: CableType = strawberry_django.field()
|
cable: CableType = strawberry_django.field()
|
||||||
cable_list: List[CableType] = strawberry_django.field()
|
cable_list: List[CableType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -136,3 +137,137 @@ class DCIMQuery:
|
|||||||
|
|
||||||
virtual_device_context: VirtualDeviceContextType = strawberry_django.field()
|
virtual_device_context: VirtualDeviceContextType = strawberry_django.field()
|
||||||
virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()
|
virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class DCIMQuery:
|
||||||
|
cable: CableType = strawberry_django.field()
|
||||||
|
cable_list: OffsetPaginated[CableType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
console_port: ConsolePortType = strawberry_django.field()
|
||||||
|
console_port_list: OffsetPaginated[ConsolePortType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
console_port_template: ConsolePortTemplateType = strawberry_django.field()
|
||||||
|
console_port_template_list: OffsetPaginated[ConsolePortTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
console_server_port: ConsoleServerPortType = strawberry_django.field()
|
||||||
|
console_server_port_list: OffsetPaginated[ConsoleServerPortType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
console_server_port_template: ConsoleServerPortTemplateType = strawberry_django.field()
|
||||||
|
console_server_port_template_list: OffsetPaginated[ConsoleServerPortTemplateType] = (
|
||||||
|
strawberry_django.offset_paginated()
|
||||||
|
)
|
||||||
|
|
||||||
|
device: DeviceType = strawberry_django.field()
|
||||||
|
device_list: OffsetPaginated[DeviceType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
device_bay: DeviceBayType = strawberry_django.field()
|
||||||
|
device_bay_list: OffsetPaginated[DeviceBayType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
device_bay_template: DeviceBayTemplateType = strawberry_django.field()
|
||||||
|
device_bay_template_list: OffsetPaginated[DeviceBayTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
device_role: DeviceRoleType = strawberry_django.field()
|
||||||
|
device_role_list: OffsetPaginated[DeviceRoleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
device_type: DeviceTypeType = strawberry_django.field()
|
||||||
|
device_type_list: OffsetPaginated[DeviceTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
front_port: FrontPortType = strawberry_django.field()
|
||||||
|
front_port_list: OffsetPaginated[FrontPortType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
front_port_template: FrontPortTemplateType = strawberry_django.field()
|
||||||
|
front_port_template_list: OffsetPaginated[FrontPortTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
mac_address: MACAddressType = strawberry_django.field()
|
||||||
|
mac_address_list: OffsetPaginated[MACAddressType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
interface: InterfaceType = strawberry_django.field()
|
||||||
|
interface_list: OffsetPaginated[InterfaceType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
interface_template: InterfaceTemplateType = strawberry_django.field()
|
||||||
|
interface_template_list: OffsetPaginated[InterfaceTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
inventory_item: InventoryItemType = strawberry_django.field()
|
||||||
|
inventory_item_list: OffsetPaginated[InventoryItemType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
inventory_item_role: InventoryItemRoleType = strawberry_django.field()
|
||||||
|
inventory_item_role_list: OffsetPaginated[InventoryItemRoleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
inventory_item_template: InventoryItemTemplateType = strawberry_django.field()
|
||||||
|
inventory_item_template_list: OffsetPaginated[InventoryItemTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
location: LocationType = strawberry_django.field()
|
||||||
|
location_list: OffsetPaginated[LocationType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
manufacturer: ManufacturerType = strawberry_django.field()
|
||||||
|
manufacturer_list: OffsetPaginated[ManufacturerType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
module: ModuleType = strawberry_django.field()
|
||||||
|
module_list: OffsetPaginated[ModuleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
module_bay: ModuleBayType = strawberry_django.field()
|
||||||
|
module_bay_list: OffsetPaginated[ModuleBayType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
module_bay_template: ModuleBayTemplateType = strawberry_django.field()
|
||||||
|
module_bay_template_list: OffsetPaginated[ModuleBayTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
module_type_profile: ModuleTypeProfileType = strawberry_django.field()
|
||||||
|
module_type_profile_list: OffsetPaginated[ModuleTypeProfileType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
module_type: ModuleTypeType = strawberry_django.field()
|
||||||
|
module_type_list: OffsetPaginated[ModuleTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
platform: PlatformType = strawberry_django.field()
|
||||||
|
platform_list: OffsetPaginated[PlatformType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_feed: PowerFeedType = strawberry_django.field()
|
||||||
|
power_feed_list: OffsetPaginated[PowerFeedType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_outlet: PowerOutletType = strawberry_django.field()
|
||||||
|
power_outlet_list: OffsetPaginated[PowerOutletType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_outlet_template: PowerOutletTemplateType = strawberry_django.field()
|
||||||
|
power_outlet_template_list: OffsetPaginated[PowerOutletTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_panel: PowerPanelType = strawberry_django.field()
|
||||||
|
power_panel_list: OffsetPaginated[PowerPanelType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_port: PowerPortType = strawberry_django.field()
|
||||||
|
power_port_list: OffsetPaginated[PowerPortType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
power_port_template: PowerPortTemplateType = strawberry_django.field()
|
||||||
|
power_port_template_list: OffsetPaginated[PowerPortTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rack_type: RackTypeType = strawberry_django.field()
|
||||||
|
rack_type_list: OffsetPaginated[RackTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rack: RackType = strawberry_django.field()
|
||||||
|
rack_list: OffsetPaginated[RackType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rack_reservation: RackReservationType = strawberry_django.field()
|
||||||
|
rack_reservation_list: OffsetPaginated[RackReservationType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rack_role: RackRoleType = strawberry_django.field()
|
||||||
|
rack_role_list: OffsetPaginated[RackRoleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rear_port: RearPortType = strawberry_django.field()
|
||||||
|
rear_port_list: OffsetPaginated[RearPortType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rear_port_template: RearPortTemplateType = strawberry_django.field()
|
||||||
|
rear_port_template_list: OffsetPaginated[RearPortTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
region: RegionType = strawberry_django.field()
|
||||||
|
region_list: OffsetPaginated[RegionType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
site: SiteType = strawberry_django.field()
|
||||||
|
site_list: OffsetPaginated[SiteType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
site_group: SiteGroupType = strawberry_django.field()
|
||||||
|
site_group_list: OffsetPaginated[SiteGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_chassis: VirtualChassisType = strawberry_django.field()
|
||||||
|
virtual_chassis_list: OffsetPaginated[VirtualChassisType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_device_context: VirtualDeviceContextType = strawberry_django.field()
|
||||||
|
virtual_device_context_list: OffsetPaginated[VirtualDeviceContextType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class ExtrasQuery:
|
class ExtrasQueryV1:
|
||||||
config_context: ConfigContextType = strawberry_django.field()
|
config_context: ConfigContextType = strawberry_django.field()
|
||||||
config_context_list: List[ConfigContextType] = strawberry_django.field()
|
config_context_list: List[ConfigContextType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -58,3 +59,57 @@ class ExtrasQuery:
|
|||||||
|
|
||||||
event_rule: EventRuleType = strawberry_django.field()
|
event_rule: EventRuleType = strawberry_django.field()
|
||||||
event_rule_list: List[EventRuleType] = strawberry_django.field()
|
event_rule_list: List[EventRuleType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class ExtrasQuery:
|
||||||
|
config_context: ConfigContextType = strawberry_django.field()
|
||||||
|
config_context_list: OffsetPaginated[ConfigContextType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
config_context_profile: ConfigContextProfileType = strawberry_django.field()
|
||||||
|
config_context_profile_list: OffsetPaginated[ConfigContextProfileType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
config_template: ConfigTemplateType = strawberry_django.field()
|
||||||
|
config_template_list: OffsetPaginated[ConfigTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
custom_field: CustomFieldType = strawberry_django.field()
|
||||||
|
custom_field_list: OffsetPaginated[CustomFieldType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
custom_field_choice_set: CustomFieldChoiceSetType = strawberry_django.field()
|
||||||
|
custom_field_choice_set_list: OffsetPaginated[CustomFieldChoiceSetType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
custom_link: CustomLinkType = strawberry_django.field()
|
||||||
|
custom_link_list: OffsetPaginated[CustomLinkType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
export_template: ExportTemplateType = strawberry_django.field()
|
||||||
|
export_template_list: OffsetPaginated[ExportTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
image_attachment: ImageAttachmentType = strawberry_django.field()
|
||||||
|
image_attachment_list: OffsetPaginated[ImageAttachmentType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
saved_filter: SavedFilterType = strawberry_django.field()
|
||||||
|
saved_filter_list: OffsetPaginated[SavedFilterType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
table_config: TableConfigType = strawberry_django.field()
|
||||||
|
table_config_list: OffsetPaginated[TableConfigType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
journal_entry: JournalEntryType = strawberry_django.field()
|
||||||
|
journal_entry_list: OffsetPaginated[JournalEntryType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
notification: NotificationType = strawberry_django.field()
|
||||||
|
notification_list: OffsetPaginated[NotificationType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
notification_group: NotificationGroupType = strawberry_django.field()
|
||||||
|
notification_group_list: OffsetPaginated[NotificationGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
subscription: SubscriptionType = strawberry_django.field()
|
||||||
|
subscription_list: OffsetPaginated[SubscriptionType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
tag: TagType = strawberry_django.field()
|
||||||
|
tag_list: OffsetPaginated[TagType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
webhook: WebhookType = strawberry_django.field()
|
||||||
|
webhook_list: OffsetPaginated[WebhookType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
event_rule: EventRuleType = strawberry_django.field()
|
||||||
|
event_rule_list: OffsetPaginated[EventRuleType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class IPAMQuery:
|
class IPAMQueryV1:
|
||||||
asn: ASNType = strawberry_django.field()
|
asn: ASNType = strawberry_django.field()
|
||||||
asn_list: List[ASNType] = strawberry_django.field()
|
asn_list: List[ASNType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -61,3 +62,60 @@ class IPAMQuery:
|
|||||||
|
|
||||||
vrf: VRFType = strawberry_django.field()
|
vrf: VRFType = strawberry_django.field()
|
||||||
vrf_list: List[VRFType] = strawberry_django.field()
|
vrf_list: List[VRFType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class IPAMQuery:
|
||||||
|
asn: ASNType = strawberry_django.field()
|
||||||
|
asn_list: OffsetPaginated[ASNType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
asn_range: ASNRangeType = strawberry_django.field()
|
||||||
|
asn_range_list: OffsetPaginated[ASNRangeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
aggregate: AggregateType = strawberry_django.field()
|
||||||
|
aggregate_list: OffsetPaginated[AggregateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ip_address: IPAddressType = strawberry_django.field()
|
||||||
|
ip_address_list: OffsetPaginated[IPAddressType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ip_range: IPRangeType = strawberry_django.field()
|
||||||
|
ip_range_list: OffsetPaginated[IPRangeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
prefix: PrefixType = strawberry_django.field()
|
||||||
|
prefix_list: OffsetPaginated[PrefixType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
rir: RIRType = strawberry_django.field()
|
||||||
|
rir_list: OffsetPaginated[RIRType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
role: RoleType = strawberry_django.field()
|
||||||
|
role_list: OffsetPaginated[RoleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
route_target: RouteTargetType = strawberry_django.field()
|
||||||
|
route_target_list: OffsetPaginated[RouteTargetType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
service: ServiceType = strawberry_django.field()
|
||||||
|
service_list: OffsetPaginated[ServiceType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
service_template: ServiceTemplateType = strawberry_django.field()
|
||||||
|
service_template_list: OffsetPaginated[ServiceTemplateType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
fhrp_group: FHRPGroupType = strawberry_django.field()
|
||||||
|
fhrp_group_list: OffsetPaginated[FHRPGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
fhrp_group_assignment: FHRPGroupAssignmentType = strawberry_django.field()
|
||||||
|
fhrp_group_assignment_list: OffsetPaginated[FHRPGroupAssignmentType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vlan: VLANType = strawberry_django.field()
|
||||||
|
vlan_list: OffsetPaginated[VLANType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vlan_group: VLANGroupType = strawberry_django.field()
|
||||||
|
vlan_group_list: OffsetPaginated[VLANGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vlan_translation_policy: VLANTranslationPolicyType = strawberry_django.field()
|
||||||
|
vlan_translation_policy_list: OffsetPaginated[VLANTranslationPolicyType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vlan_translation_rule: VLANTranslationRuleType = strawberry_django.field()
|
||||||
|
vlan_translation_rule_list: OffsetPaginated[VLANTranslationRuleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vrf: VRFType = strawberry_django.field()
|
||||||
|
vrf_list: OffsetPaginated[VRFType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ from strawberry_django.optimizer import DjangoOptimizerExtension
|
|||||||
from strawberry.extensions import MaxAliasesLimiter
|
from strawberry.extensions import MaxAliasesLimiter
|
||||||
from strawberry.schema.config import StrawberryConfig
|
from strawberry.schema.config import StrawberryConfig
|
||||||
|
|
||||||
from circuits.graphql.schema import CircuitsQuery
|
from circuits.graphql.schema import CircuitsQuery, CircuitsQueryV1
|
||||||
from core.graphql.schema import CoreQuery
|
from core.graphql.schema import CoreQuery, CoreQueryV1
|
||||||
from dcim.graphql.schema import DCIMQuery
|
from dcim.graphql.schema import DCIMQuery, DCIMQueryV1
|
||||||
from extras.graphql.schema import ExtrasQuery
|
from extras.graphql.schema import ExtrasQuery, ExtrasQueryV1
|
||||||
from ipam.graphql.schema import IPAMQuery
|
from ipam.graphql.schema import IPAMQuery, IPAMQueryV1
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from tenancy.graphql.schema import TenancyQuery
|
from tenancy.graphql.schema import TenancyQuery, TenancyQueryV1
|
||||||
from users.graphql.schema import UsersQuery
|
from users.graphql.schema import UsersQuery, UsersQueryV1
|
||||||
from virtualization.graphql.schema import VirtualizationQuery
|
from virtualization.graphql.schema import VirtualizationQuery, VirtualizationQueryV1
|
||||||
from vpn.graphql.schema import VPNQuery
|
from vpn.graphql.schema import VPNQuery, VPNQueryV1
|
||||||
from wireless.graphql.schema import WirelessQuery
|
from wireless.graphql.schema import WirelessQuery, WirelessQueryV1
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Query',
|
'Query',
|
||||||
@@ -27,16 +27,16 @@ __all__ = (
|
|||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class QueryV1(
|
class QueryV1(
|
||||||
UsersQuery,
|
UsersQueryV1,
|
||||||
CircuitsQuery,
|
CircuitsQueryV1,
|
||||||
CoreQuery,
|
CoreQueryV1,
|
||||||
DCIMQuery,
|
DCIMQueryV1,
|
||||||
ExtrasQuery,
|
ExtrasQueryV1,
|
||||||
IPAMQuery,
|
IPAMQueryV1,
|
||||||
TenancyQuery,
|
TenancyQueryV1,
|
||||||
VirtualizationQuery,
|
VirtualizationQueryV1,
|
||||||
VPNQuery,
|
VPNQueryV1,
|
||||||
WirelessQuery,
|
WirelessQueryV1,
|
||||||
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
||||||
):
|
):
|
||||||
"""Query class for GraphQL API v1"""
|
"""Query class for GraphQL API v1"""
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ class GraphQLTestCase(TestCase):
|
|||||||
class GraphQLAPITestCase(APITestCase):
|
class GraphQLAPITestCase(APITestCase):
|
||||||
|
|
||||||
@override_settings(LOGIN_REQUIRED=True)
|
@override_settings(LOGIN_REQUIRED=True)
|
||||||
def test_graphql_filter_objects(self):
|
def test_graphql_filter_objects_v1(self):
|
||||||
"""
|
"""
|
||||||
Test the operation of filters for GraphQL API requests.
|
Test the operation of filters for GraphQL API v1 requests (old format with List[Type]).
|
||||||
"""
|
"""
|
||||||
sites = (
|
sites = (
|
||||||
Site(name='Site 1', slug='site-1'),
|
Site(name='Site 1', slug='site-1'),
|
||||||
@@ -85,7 +85,7 @@ class GraphQLAPITestCase(APITestCase):
|
|||||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(Location))
|
obj_perm.object_types.add(ObjectType.objects.get_for_model(Location))
|
||||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(Site))
|
obj_perm.object_types.add(ObjectType.objects.get_for_model(Site))
|
||||||
|
|
||||||
url = reverse('graphql')
|
url = reverse('graphql_v1')
|
||||||
|
|
||||||
# A valid request should return the filtered list
|
# A valid request should return the filtered list
|
||||||
query = '{location_list(filters: {site_id: "' + str(sites[0].pk) + '"}) {id site {id}}}'
|
query = '{location_list(filters: {site_id: "' + str(sites[0].pk) + '"}) {id site {id}}}'
|
||||||
@@ -126,3 +126,91 @@ class GraphQLAPITestCase(APITestCase):
|
|||||||
data = json.loads(response.content)
|
data = json.loads(response.content)
|
||||||
self.assertNotIn('errors', data)
|
self.assertNotIn('errors', data)
|
||||||
self.assertEqual(len(data['data']['site']['locations']), 0)
|
self.assertEqual(len(data['data']['site']['locations']), 0)
|
||||||
|
|
||||||
|
@override_settings(LOGIN_REQUIRED=True)
|
||||||
|
def test_graphql_filter_objects(self):
|
||||||
|
"""
|
||||||
|
Test the operation of filters for GraphQL API v2 requests (new format with OffsetPaginated).
|
||||||
|
"""
|
||||||
|
sites = (
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
Site(name='Site 3', slug='site-3'),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
|
Location.objects.create(
|
||||||
|
site=sites[0],
|
||||||
|
name='Location 1',
|
||||||
|
slug='location-1',
|
||||||
|
status=LocationStatusChoices.STATUS_PLANNED
|
||||||
|
),
|
||||||
|
Location.objects.create(
|
||||||
|
site=sites[1],
|
||||||
|
name='Location 2',
|
||||||
|
slug='location-2',
|
||||||
|
status=LocationStatusChoices.STATUS_STAGING
|
||||||
|
),
|
||||||
|
Location.objects.create(
|
||||||
|
site=sites[1],
|
||||||
|
name='Location 3',
|
||||||
|
slug='location-3',
|
||||||
|
status=LocationStatusChoices.STATUS_ACTIVE
|
||||||
|
),
|
||||||
|
|
||||||
|
# Add object-level permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
|
actions=['view']
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
obj_perm.object_types.add(ObjectType.objects.get_for_model(Location))
|
||||||
|
obj_perm.object_types.add(ObjectType.objects.get_for_model(Site))
|
||||||
|
|
||||||
|
url = reverse('graphql_v2')
|
||||||
|
|
||||||
|
# A valid request should return the filtered list
|
||||||
|
query = '{location_list(filters: {site_id: "' + str(sites[0].pk) + '"}) {results {id site {id}} total_count}}'
|
||||||
|
response = self.client.post(url, data={'query': query}, format="json", **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
data = json.loads(response.content)
|
||||||
|
self.assertNotIn('errors', data)
|
||||||
|
self.assertEqual(len(data['data']['location_list']['results']), 1)
|
||||||
|
self.assertEqual(data['data']['location_list']['total_count'], 1)
|
||||||
|
self.assertIsNotNone(data['data']['location_list']['results'][0]['site'])
|
||||||
|
|
||||||
|
# Test OR logic
|
||||||
|
query = """{
|
||||||
|
location_list( filters: {
|
||||||
|
status: STATUS_PLANNED,
|
||||||
|
OR: {status: STATUS_STAGING}
|
||||||
|
}) {
|
||||||
|
results {
|
||||||
|
id site {id}
|
||||||
|
}
|
||||||
|
total_count
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
response = self.client.post(url, data={'query': query}, format="json", **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
data = json.loads(response.content)
|
||||||
|
self.assertNotIn('errors', data)
|
||||||
|
self.assertEqual(len(data['data']['location_list']['results']), 2)
|
||||||
|
self.assertEqual(data['data']['location_list']['total_count'], 2)
|
||||||
|
|
||||||
|
# An invalid request should return an empty list
|
||||||
|
query = '{location_list(filters: {site_id: "99999"}) {results {id site {id}} total_count}}' # Invalid site ID
|
||||||
|
response = self.client.post(url, data={'query': query}, format="json", **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
data = json.loads(response.content)
|
||||||
|
self.assertEqual(len(data['data']['location_list']['results']), 0)
|
||||||
|
self.assertEqual(data['data']['location_list']['total_count'], 0)
|
||||||
|
|
||||||
|
# Removing the permissions from location should result in an empty locations list
|
||||||
|
obj_perm.object_types.remove(ObjectType.objects.get_for_model(Location))
|
||||||
|
query = '{site(id: ' + str(sites[0].pk) + ') {id locations {id}}}'
|
||||||
|
response = self.client.post(url, data={'query': query}, format="json", **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
data = json.loads(response.content)
|
||||||
|
self.assertNotIn('errors', data)
|
||||||
|
self.assertEqual(len(data['data']['site']['locations']), 0)
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class TenancyQuery:
|
class TenancyQueryV1:
|
||||||
tenant: TenantType = strawberry_django.field()
|
tenant: TenantType = strawberry_django.field()
|
||||||
tenant_list: List[TenantType] = strawberry_django.field()
|
tenant_list: List[TenantType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -25,3 +26,24 @@ class TenancyQuery:
|
|||||||
|
|
||||||
contact_assignment: ContactAssignmentType = strawberry_django.field()
|
contact_assignment: ContactAssignmentType = strawberry_django.field()
|
||||||
contact_assignment_list: List[ContactAssignmentType] = strawberry_django.field()
|
contact_assignment_list: List[ContactAssignmentType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class TenancyQuery:
|
||||||
|
tenant: TenantType = strawberry_django.field()
|
||||||
|
tenant_list: OffsetPaginated[TenantType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
tenant_group: TenantGroupType = strawberry_django.field()
|
||||||
|
tenant_group_list: OffsetPaginated[TenantGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
contact: ContactType = strawberry_django.field()
|
||||||
|
contact_list: OffsetPaginated[ContactType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
contact_role: ContactRoleType = strawberry_django.field()
|
||||||
|
contact_role_list: OffsetPaginated[ContactRoleType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
contact_group: ContactGroupType = strawberry_django.field()
|
||||||
|
contact_group_list: OffsetPaginated[ContactGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
contact_assignment: ContactAssignmentType = strawberry_django.field()
|
||||||
|
contact_assignment_list: OffsetPaginated[ContactAssignmentType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,14 +2,24 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class UsersQuery:
|
class UsersQueryV1:
|
||||||
group: GroupType = strawberry_django.field()
|
group: GroupType = strawberry_django.field()
|
||||||
group_list: List[GroupType] = strawberry_django.field()
|
group_list: List[GroupType] = strawberry_django.field()
|
||||||
|
|
||||||
user: UserType = strawberry_django.field()
|
user: UserType = strawberry_django.field()
|
||||||
user_list: List[UserType] = strawberry_django.field()
|
user_list: List[UserType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class UsersQuery:
|
||||||
|
group: GroupType = strawberry_django.field()
|
||||||
|
group_list: OffsetPaginated[GroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
user: UserType = strawberry_django.field()
|
||||||
|
user_list: OffsetPaginated[UserType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -515,10 +515,15 @@ class APIViewTestCases:
|
|||||||
base_name = self.model._meta.verbose_name.lower().replace(' ', '_')
|
base_name = self.model._meta.verbose_name.lower().replace(' ', '_')
|
||||||
return getattr(self, 'graphql_base_name', base_name)
|
return getattr(self, 'graphql_base_name', base_name)
|
||||||
|
|
||||||
def _build_query_with_filter(self, name, filter_string):
|
def _build_query_with_filter(self, name, filter_string, api_version='v2'):
|
||||||
"""
|
"""
|
||||||
Called by either _build_query or _build_filtered_query - construct the actual
|
Called by either _build_query or _build_filtered_query - construct the actual
|
||||||
query given a name and filter string
|
query given a name and filter string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The query field name (e.g., 'device_list')
|
||||||
|
filter_string: Filter parameters string (e.g., '(filters: {id: "1"})')
|
||||||
|
api_version: 'v1' or 'v2' to determine response format
|
||||||
"""
|
"""
|
||||||
type_class = get_graphql_type_for_model(self.model)
|
type_class = get_graphql_type_for_model(self.model)
|
||||||
|
|
||||||
@@ -562,6 +567,30 @@ class APIViewTestCases:
|
|||||||
else:
|
else:
|
||||||
fields_string += f'{field.name}\n'
|
fields_string += f'{field.name}\n'
|
||||||
|
|
||||||
|
# Check if this is a list query (ends with '_list')
|
||||||
|
if name.endswith('_list'):
|
||||||
|
if api_version == 'v2':
|
||||||
|
# v2: Wrap fields in 'results' for paginated queries
|
||||||
|
query = f"""
|
||||||
|
{{
|
||||||
|
{name}{filter_string} {{
|
||||||
|
results {{
|
||||||
|
{fields_string}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
# v1: Return direct array (no 'results' wrapper)
|
||||||
|
query = f"""
|
||||||
|
{{
|
||||||
|
{name}{filter_string} {{
|
||||||
|
{fields_string}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
# Single object query (no pagination)
|
||||||
query = f"""
|
query = f"""
|
||||||
{{
|
{{
|
||||||
{name}{filter_string} {{
|
{name}{filter_string} {{
|
||||||
@@ -572,9 +601,14 @@ class APIViewTestCases:
|
|||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def _build_filtered_query(self, name, **filters):
|
def _build_filtered_query(self, name, api_version='v2', **filters):
|
||||||
"""
|
"""
|
||||||
Create a filtered query: i.e. device_list(filters: {name: {i_contains: "akron"}}){.
|
Create a filtered query: i.e. device_list(filters: {name: {i_contains: "akron"}}){.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The query field name
|
||||||
|
api_version: 'v1' or 'v2' to determine response format
|
||||||
|
**filters: Filter parameters
|
||||||
"""
|
"""
|
||||||
# TODO: This should be extended to support AND, OR multi-lookups
|
# TODO: This should be extended to support AND, OR multi-lookups
|
||||||
if filters:
|
if filters:
|
||||||
@@ -590,11 +624,16 @@ class APIViewTestCases:
|
|||||||
else:
|
else:
|
||||||
filter_string = ''
|
filter_string = ''
|
||||||
|
|
||||||
return self._build_query_with_filter(name, filter_string)
|
return self._build_query_with_filter(name, filter_string, api_version)
|
||||||
|
|
||||||
def _build_query(self, name, **filters):
|
def _build_query(self, name, api_version='v2', **filters):
|
||||||
"""
|
"""
|
||||||
Create a normal query - unfiltered or with a string query: i.e. site(name: "aaa"){.
|
Create a normal query - unfiltered or with a string query: i.e. site(name: "aaa"){.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The query field name
|
||||||
|
api_version: 'v1' or 'v2' to determine response format
|
||||||
|
**filters: Filter parameters
|
||||||
"""
|
"""
|
||||||
if filters:
|
if filters:
|
||||||
filter_string = ', '.join(f'{k}:{v}' for k, v in filters.items())
|
filter_string = ', '.join(f'{k}:{v}' for k, v in filters.items())
|
||||||
@@ -602,7 +641,7 @@ class APIViewTestCases:
|
|||||||
else:
|
else:
|
||||||
filter_string = ''
|
filter_string = ''
|
||||||
|
|
||||||
return self._build_query_with_filter(name, filter_string)
|
return self._build_query_with_filter(name, filter_string, api_version)
|
||||||
|
|
||||||
@override_settings(LOGIN_REQUIRED=True)
|
@override_settings(LOGIN_REQUIRED=True)
|
||||||
def test_graphql_get_object(self):
|
def test_graphql_get_object(self):
|
||||||
@@ -650,9 +689,13 @@ class APIViewTestCases:
|
|||||||
|
|
||||||
@override_settings(LOGIN_REQUIRED=True)
|
@override_settings(LOGIN_REQUIRED=True)
|
||||||
def test_graphql_list_objects(self):
|
def test_graphql_list_objects(self):
|
||||||
url = reverse('graphql')
|
|
||||||
field_name = f'{self._get_graphql_base_name()}_list'
|
field_name = f'{self._get_graphql_base_name()}_list'
|
||||||
query = self._build_query(field_name)
|
|
||||||
|
# Test both GraphQL API versions
|
||||||
|
for api_version, url_name in [('v1', 'graphql_v1'), ('v2', 'graphql_v2')]:
|
||||||
|
with self.subTest(api_version=api_version):
|
||||||
|
url = reverse(url_name)
|
||||||
|
query = self._build_query(field_name, api_version=api_version)
|
||||||
|
|
||||||
# Non-authenticated requests should fail
|
# Non-authenticated requests should fail
|
||||||
header = {
|
header = {
|
||||||
@@ -677,7 +720,13 @@ class APIViewTestCases:
|
|||||||
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)
|
||||||
|
|
||||||
|
if api_version == 'v1':
|
||||||
|
# v1 returns direct array
|
||||||
self.assertEqual(len(data['data'][field_name]), 0)
|
self.assertEqual(len(data['data'][field_name]), 0)
|
||||||
|
else:
|
||||||
|
# v2 returns paginated response with results
|
||||||
|
self.assertEqual(len(data['data'][field_name]['results']), 0)
|
||||||
|
|
||||||
# Remove permission constraint
|
# Remove permission constraint
|
||||||
obj_perm.constraints = None
|
obj_perm.constraints = None
|
||||||
@@ -688,16 +737,23 @@ class APIViewTestCases:
|
|||||||
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)
|
||||||
|
|
||||||
|
if api_version == 'v1':
|
||||||
|
# v1 returns direct array
|
||||||
self.assertEqual(len(data['data'][field_name]), self.model.objects.count())
|
self.assertEqual(len(data['data'][field_name]), self.model.objects.count())
|
||||||
|
else:
|
||||||
|
# v2 returns paginated response with results
|
||||||
|
self.assertEqual(len(data['data'][field_name]['results']), self.model.objects.count())
|
||||||
|
|
||||||
|
# Clean up permission for next iteration
|
||||||
|
obj_perm.delete()
|
||||||
|
|
||||||
@override_settings(LOGIN_REQUIRED=True)
|
@override_settings(LOGIN_REQUIRED=True)
|
||||||
def test_graphql_filter_objects(self):
|
def test_graphql_filter_objects(self):
|
||||||
if not hasattr(self, 'graphql_filter'):
|
if not hasattr(self, 'graphql_filter'):
|
||||||
return
|
return
|
||||||
|
|
||||||
url = reverse('graphql')
|
|
||||||
field_name = f'{self._get_graphql_base_name()}_list'
|
field_name = f'{self._get_graphql_base_name()}_list'
|
||||||
query = self._build_filtered_query(field_name, **self.graphql_filter)
|
|
||||||
|
|
||||||
# Add object-level permission
|
# Add object-level permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
@@ -708,11 +764,26 @@ 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))
|
||||||
|
|
||||||
|
# Test both GraphQL API versions
|
||||||
|
for api_version, url_name in [('v1', 'graphql_v1'), ('v2', 'graphql_v2')]:
|
||||||
|
with self.subTest(api_version=api_version):
|
||||||
|
url = reverse(url_name)
|
||||||
|
query = self._build_filtered_query(field_name, api_version=api_version, **self.graphql_filter)
|
||||||
|
|
||||||
response = self.client.post(url, data={'query': query}, format="json", **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)
|
||||||
|
|
||||||
|
if api_version == 'v1':
|
||||||
|
# v1 returns direct array
|
||||||
self.assertGreater(len(data['data'][field_name]), 0)
|
self.assertGreater(len(data['data'][field_name]), 0)
|
||||||
|
else:
|
||||||
|
# v2 returns paginated response with results
|
||||||
|
self.assertGreater(len(data['data'][field_name]['results']), 0)
|
||||||
|
|
||||||
|
# Clean up permission
|
||||||
|
obj_perm.delete()
|
||||||
|
|
||||||
class APIViewTestCase(
|
class APIViewTestCase(
|
||||||
GetObjectViewTestCase,
|
GetObjectViewTestCase,
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class VirtualizationQuery:
|
class VirtualizationQueryV1:
|
||||||
cluster: ClusterType = strawberry_django.field()
|
cluster: ClusterType = strawberry_django.field()
|
||||||
cluster_list: List[ClusterType] = strawberry_django.field()
|
cluster_list: List[ClusterType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -25,3 +26,24 @@ class VirtualizationQuery:
|
|||||||
|
|
||||||
virtual_disk: VirtualDiskType = strawberry_django.field()
|
virtual_disk: VirtualDiskType = strawberry_django.field()
|
||||||
virtual_disk_list: List[VirtualDiskType] = strawberry_django.field()
|
virtual_disk_list: List[VirtualDiskType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class VirtualizationQuery:
|
||||||
|
cluster: ClusterType = strawberry_django.field()
|
||||||
|
cluster_list: OffsetPaginated[ClusterType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
cluster_group: ClusterGroupType = strawberry_django.field()
|
||||||
|
cluster_group_list: OffsetPaginated[ClusterGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
cluster_type: ClusterTypeType = strawberry_django.field()
|
||||||
|
cluster_type_list: OffsetPaginated[ClusterTypeType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_machine: VirtualMachineType = strawberry_django.field()
|
||||||
|
virtual_machine_list: OffsetPaginated[VirtualMachineType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
vm_interface: VMInterfaceType = strawberry_django.field()
|
||||||
|
vm_interface_list: OffsetPaginated[VMInterfaceType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
virtual_disk: VirtualDiskType = strawberry_django.field()
|
||||||
|
virtual_disk_list: OffsetPaginated[VirtualDiskType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class VPNQuery:
|
class VPNQueryV1:
|
||||||
ike_policy: IKEPolicyType = strawberry_django.field()
|
ike_policy: IKEPolicyType = strawberry_django.field()
|
||||||
ike_policy_list: List[IKEPolicyType] = strawberry_django.field()
|
ike_policy_list: List[IKEPolicyType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -37,3 +38,36 @@ class VPNQuery:
|
|||||||
|
|
||||||
tunnel_termination: TunnelTerminationType = strawberry_django.field()
|
tunnel_termination: TunnelTerminationType = strawberry_django.field()
|
||||||
tunnel_termination_list: List[TunnelTerminationType] = strawberry_django.field()
|
tunnel_termination_list: List[TunnelTerminationType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class VPNQuery:
|
||||||
|
ike_policy: IKEPolicyType = strawberry_django.field()
|
||||||
|
ike_policy_list: OffsetPaginated[IKEPolicyType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ike_proposal: IKEProposalType = strawberry_django.field()
|
||||||
|
ike_proposal_list: OffsetPaginated[IKEProposalType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ipsec_policy: IPSecPolicyType = strawberry_django.field()
|
||||||
|
ipsec_policy_list: OffsetPaginated[IPSecPolicyType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ipsec_profile: IPSecProfileType = strawberry_django.field()
|
||||||
|
ipsec_profile_list: OffsetPaginated[IPSecProfileType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
ipsec_proposal: IPSecProposalType = strawberry_django.field()
|
||||||
|
ipsec_proposal_list: OffsetPaginated[IPSecProposalType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
l2vpn: L2VPNType = strawberry_django.field()
|
||||||
|
l2vpn_list: OffsetPaginated[L2VPNType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
l2vpn_termination: L2VPNTerminationType = strawberry_django.field()
|
||||||
|
l2vpn_termination_list: OffsetPaginated[L2VPNTerminationType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
tunnel: TunnelType = strawberry_django.field()
|
||||||
|
tunnel_list: OffsetPaginated[TunnelType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
tunnel_group: TunnelGroupType = strawberry_django.field()
|
||||||
|
tunnel_group_list: OffsetPaginated[TunnelGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
tunnel_termination: TunnelTerminationType = strawberry_django.field()
|
||||||
|
tunnel_termination_list: OffsetPaginated[TunnelTerminationType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ from typing import List
|
|||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
from strawberry_django.pagination import OffsetPaginated
|
||||||
|
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type(name="Query")
|
@strawberry.type(name="Query")
|
||||||
class WirelessQuery:
|
class WirelessQueryV1:
|
||||||
wireless_lan: WirelessLANType = strawberry_django.field()
|
wireless_lan: WirelessLANType = strawberry_django.field()
|
||||||
wireless_lan_list: List[WirelessLANType] = strawberry_django.field()
|
wireless_lan_list: List[WirelessLANType] = strawberry_django.field()
|
||||||
|
|
||||||
@@ -16,3 +17,15 @@ class WirelessQuery:
|
|||||||
|
|
||||||
wireless_link: WirelessLinkType = strawberry_django.field()
|
wireless_link: WirelessLinkType = strawberry_django.field()
|
||||||
wireless_link_list: List[WirelessLinkType] = strawberry_django.field()
|
wireless_link_list: List[WirelessLinkType] = strawberry_django.field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type(name="Query")
|
||||||
|
class WirelessQuery:
|
||||||
|
wireless_lan: WirelessLANType = strawberry_django.field()
|
||||||
|
wireless_lan_list: OffsetPaginated[WirelessLANType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
wireless_lan_group: WirelessLANGroupType = strawberry_django.field()
|
||||||
|
wireless_lan_group_list: OffsetPaginated[WirelessLANGroupType] = strawberry_django.offset_paginated()
|
||||||
|
|
||||||
|
wireless_link: WirelessLinkType = strawberry_django.field()
|
||||||
|
wireless_link_list: OffsetPaginated[WirelessLinkType] = strawberry_django.offset_paginated()
|
||||||
|
|||||||
Reference in New Issue
Block a user