text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "$ref": "#/components/schemas/BriefDataSource"
+ },
+ "data_path": {
+ "type": "string",
+ "readOnly": true,
+ "description": "Path to remote file (relative to data source root)"
+ },
+ "data_file": {
+ "$ref": "#/components/schemas/BriefDataFile"
+ },
+ "auto_sync_enabled": {
+ "type": "boolean",
+ "description": "Enable automatic synchronization of data when the data file is updated"
+ },
+ "data_synced": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true,
+ "title": "Date synced"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "data_path",
+ "data_synced",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "template_code",
+ "url"
+ ]
+ },
+ "ConfigTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "environment_params": {
+ "nullable": true,
+ "title": "Environment parameters",
+ "description": "Any additional parameters to pass when constructing the Jinja environment"
+ },
+ "template_code": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja template code."
+ },
+ "mime_type": {
+ "type": "string",
+ "description": "Defaults to text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ },
+ "auto_sync_enabled": {
+ "type": "boolean",
+ "description": "Enable automatic synchronization of data when the data file is updated"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ },
+ "required": [
+ "name",
+ "template_code"
+ ]
+ },
+ "ConsolePort": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "DE-9",
+ "DB-25",
+ "RJ-11",
+ "RJ-12",
+ "RJ-45",
+ "Mini-DIN 8",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "speed": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "x-spec-enum-id": "ab6d9635c131a378"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "1200 bps",
+ "2400 bps",
+ "4800 bps",
+ "9600 bps",
+ "19.2 kbps",
+ "38.4 kbps",
+ "57.6 kbps",
+ "115.2 kbps"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "url"
+ ]
+ },
+ "ConsolePortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "ConsolePortTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "DE-9",
+ "DB-25",
+ "RJ-11",
+ "RJ-12",
+ "RJ-45",
+ "Mini-DIN 8",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ConsolePortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "ConsoleServerPort": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "DE-9",
+ "DB-25",
+ "RJ-11",
+ "RJ-12",
+ "RJ-45",
+ "Mini-DIN 8",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "speed": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "x-spec-enum-id": "ab6d9635c131a378"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "1200 bps",
+ "2400 bps",
+ "4800 bps",
+ "9600 bps",
+ "19.2 kbps",
+ "38.4 kbps",
+ "57.6 kbps",
+ "115.2 kbps"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "url"
+ ]
+ },
+ "ConsoleServerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "ConsoleServerPortTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "DE-9",
+ "DB-25",
+ "RJ-11",
+ "RJ-12",
+ "RJ-45",
+ "Mini-DIN 8",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ConsoleServerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ ""
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "Contact": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ContactGroup"
+ }
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "phone": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "maxLength": 254
+ },
+ "address": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "link": {
+ "type": "string",
+ "format": "uri",
+ "maxLength": 200
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ContactAssignment": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "contact": {
+ "$ref": "#/components/schemas/BriefContact"
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefContactRole"
+ }
+ ],
+ "nullable": true
+ },
+ "priority": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ ""
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Primary",
+ "Secondary",
+ "Tertiary",
+ "Inactive"
+ ]
+ }
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "contact",
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "object",
+ "object_id",
+ "object_type",
+ "url"
+ ]
+ },
+ "ContactAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "contact": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefContactRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefContactRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "priority": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ ""
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "contact",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "ContactGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedContactGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "contact_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "contact_count",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "ContactGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedContactGroupRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "ContactRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "phone": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "maxLength": 254
+ },
+ "address": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "link": {
+ "type": "string",
+ "format": "uri",
+ "maxLength": 200
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "ContactRole": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "ContactRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "CustomField": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "text",
+ "longtext",
+ "integer",
+ "decimal",
+ "boolean",
+ "date",
+ "datetime",
+ "url",
+ "json",
+ "select",
+ "multiselect",
+ "object",
+ "multiobject"
+ ],
+ "type": "string",
+ "description": "* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects",
+ "x-spec-enum-id": "47c52a3d983e924c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Text",
+ "Text (long)",
+ "Integer",
+ "Decimal",
+ "Boolean (true/false)",
+ "Date",
+ "Date & time",
+ "URL",
+ "JSON",
+ "Selection",
+ "Multiple selection",
+ "Object",
+ "Multiple objects"
+ ]
+ }
+ }
+ },
+ "related_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "data_type": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "Internal field name",
+ "pattern": "^[a-z0-9_]+$",
+ "maxLength": 50
+ },
+ "label": {
+ "type": "string",
+ "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)",
+ "maxLength": 50
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Custom fields within the same group will be displayed together",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "required": {
+ "type": "boolean",
+ "description": "This field is required when creating new objects or editing an existing object."
+ },
+ "unique": {
+ "type": "boolean",
+ "title": "Must be unique",
+ "description": "The value of this field must be unique for the assigned object"
+ },
+ "search_weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored."
+ },
+ "filter_logic": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "disabled",
+ "loose",
+ "exact"
+ ],
+ "type": "string",
+ "description": "* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact",
+ "x-spec-enum-id": "d168820c798ae45a"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Disabled",
+ "Loose",
+ "Exact"
+ ]
+ }
+ }
+ },
+ "ui_visible": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "always",
+ "if-set",
+ "hidden"
+ ],
+ "type": "string",
+ "description": "* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden",
+ "x-spec-enum-id": "f32800c399b927b6"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Always",
+ "If set",
+ "Hidden"
+ ]
+ }
+ }
+ },
+ "ui_editable": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "yes",
+ "no",
+ "hidden"
+ ],
+ "type": "string",
+ "description": "* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden",
+ "x-spec-enum-id": "336f52760e62022f"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Yes",
+ "No",
+ "Hidden"
+ ]
+ }
+ }
+ },
+ "is_cloneable": {
+ "type": "boolean",
+ "description": "Replicate this value when cloning objects"
+ },
+ "default": {
+ "nullable": true,
+ "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "related_object_filter": {
+ "nullable": true,
+ "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "title": "Display weight",
+ "description": "Fields with higher weights appear lower in a form."
+ },
+ "validation_minimum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Minimum value",
+ "description": "Minimum allowed value (for numeric fields)"
+ },
+ "validation_maximum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Maximum value",
+ "description": "Maximum allowed value (for numeric fields)"
+ },
+ "validation_regex": {
+ "type": "string",
+ "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.",
+ "maxLength": 500
+ },
+ "choice_set": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCustomFieldChoiceSet"
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "data_type",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "object_types",
+ "type",
+ "url"
+ ]
+ },
+ "CustomFieldChoiceSet": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "base_choices": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "IATA",
+ "ISO_3166",
+ "UN_LOCODE"
+ ],
+ "type": "string",
+ "description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
+ "x-spec-enum-id": "cf0efb5195f85007"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IATA (Airport codes)",
+ "ISO 3166 (Country codes)",
+ "UN/LOCODE (Location codes)"
+ ]
+ }
+ }
+ },
+ "extra_choices": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {},
+ "maxItems": 2,
+ "minItems": 2
+ }
+ },
+ "order_alphabetically": {
+ "type": "boolean",
+ "description": "Choices are automatically ordered alphabetically"
+ },
+ "choices_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "choices_count",
+ "created",
+ "display",
+ "display_url",
+ "extra_choices",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "CustomFieldChoiceSetRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "base_choices": {
+ "enum": [
+ "IATA",
+ "ISO_3166",
+ "UN_LOCODE"
+ ],
+ "type": "string",
+ "description": "* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)",
+ "x-spec-enum-id": "cf0efb5195f85007"
+ },
+ "extra_choices": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {},
+ "maxItems": 2,
+ "minItems": 2
+ }
+ },
+ "order_alphabetically": {
+ "type": "boolean",
+ "description": "Choices are automatically ordered alphabetically"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "extra_choices",
+ "name"
+ ]
+ },
+ "CustomFieldRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "type": {
+ "enum": [
+ "text",
+ "longtext",
+ "integer",
+ "decimal",
+ "boolean",
+ "date",
+ "datetime",
+ "url",
+ "json",
+ "select",
+ "multiselect",
+ "object",
+ "multiobject"
+ ],
+ "type": "string",
+ "description": "* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects",
+ "x-spec-enum-id": "47c52a3d983e924c"
+ },
+ "related_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Internal field name",
+ "pattern": "^[a-z0-9_]+$",
+ "maxLength": 50
+ },
+ "label": {
+ "type": "string",
+ "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)",
+ "maxLength": 50
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Custom fields within the same group will be displayed together",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "required": {
+ "type": "boolean",
+ "description": "This field is required when creating new objects or editing an existing object."
+ },
+ "unique": {
+ "type": "boolean",
+ "title": "Must be unique",
+ "description": "The value of this field must be unique for the assigned object"
+ },
+ "search_weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored."
+ },
+ "filter_logic": {
+ "enum": [
+ "disabled",
+ "loose",
+ "exact"
+ ],
+ "type": "string",
+ "description": "* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact",
+ "x-spec-enum-id": "d168820c798ae45a"
+ },
+ "ui_visible": {
+ "enum": [
+ "always",
+ "if-set",
+ "hidden"
+ ],
+ "type": "string",
+ "description": "* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden",
+ "x-spec-enum-id": "f32800c399b927b6"
+ },
+ "ui_editable": {
+ "enum": [
+ "yes",
+ "no",
+ "hidden"
+ ],
+ "type": "string",
+ "description": "* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden",
+ "x-spec-enum-id": "336f52760e62022f"
+ },
+ "is_cloneable": {
+ "type": "boolean",
+ "description": "Replicate this value when cloning objects"
+ },
+ "default": {
+ "nullable": true,
+ "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "related_object_filter": {
+ "nullable": true,
+ "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "title": "Display weight",
+ "description": "Fields with higher weights appear lower in a form."
+ },
+ "validation_minimum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Minimum value",
+ "description": "Minimum allowed value (for numeric fields)"
+ },
+ "validation_maximum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Maximum value",
+ "description": "Maximum allowed value (for numeric fields)"
+ },
+ "validation_regex": {
+ "type": "string",
+ "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.",
+ "maxLength": 500
+ },
+ "choice_set": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCustomFieldChoiceSetRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "object_types",
+ "type"
+ ]
+ },
+ "CustomLink": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "link_text": {
+ "type": "string",
+ "description": "Jinja2 template code for link text"
+ },
+ "link_url": {
+ "type": "string",
+ "description": "Jinja2 template code for link URL"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Links with the same group will appear as a dropdown menu",
+ "maxLength": 50
+ },
+ "button_class": {
+ "enum": [
+ "default",
+ "blue",
+ "indigo",
+ "purple",
+ "pink",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "teal",
+ "cyan",
+ "gray",
+ "black",
+ "white",
+ "ghost-dark"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "5e54b3bd086685ce",
+ "description": "The class of the first link in a group will be used for the dropdown button\n\n* `default` - Default\n* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White\n* `ghost-dark` - Link"
+ },
+ "new_window": {
+ "type": "boolean",
+ "description": "Force link to open in a new window"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_text",
+ "link_url",
+ "name",
+ "object_types",
+ "url"
+ ]
+ },
+ "CustomLinkRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "link_text": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja2 template code for link text"
+ },
+ "link_url": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja2 template code for link URL"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Links with the same group will appear as a dropdown menu",
+ "maxLength": 50
+ },
+ "button_class": {
+ "enum": [
+ "default",
+ "blue",
+ "indigo",
+ "purple",
+ "pink",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "teal",
+ "cyan",
+ "gray",
+ "black",
+ "white",
+ "ghost-dark"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "5e54b3bd086685ce",
+ "description": "The class of the first link in a group will be used for the dropdown button\n\n* `default` - Default\n* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White\n* `ghost-dark` - Link"
+ },
+ "new_window": {
+ "type": "boolean",
+ "description": "Force link to open in a new window"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "link_text",
+ "link_url",
+ "name",
+ "object_types"
+ ]
+ },
+ "Dashboard": {
+ "type": "object",
+ "properties": {
+ "layout": {},
+ "config": {}
+ }
+ },
+ "DashboardRequest": {
+ "type": "object",
+ "properties": {
+ "layout": {},
+ "config": {}
+ }
+ },
+ "DataFile": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "source": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDataSource"
+ }
+ ],
+ "readOnly": true
+ },
+ "path": {
+ "type": "string",
+ "readOnly": true,
+ "description": "File path relative to the data source's root"
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "size": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "hash": {
+ "type": "string",
+ "readOnly": true,
+ "description": "SHA256 hash of the file data"
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "hash",
+ "id",
+ "last_updated",
+ "path",
+ "size",
+ "source",
+ "url"
+ ]
+ },
+ "DataSource": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ null,
+ "local",
+ "git",
+ "amazon-s3"
+ ],
+ "description": "* `None` - ---------\n* `local` - Local\n* `git` - Git\n* `amazon-s3` - Amazon S3",
+ "x-spec-enum-id": "562b613a749b34b0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "---------",
+ "Local",
+ "Git",
+ "Amazon S3"
+ ]
+ }
+ }
+ },
+ "source_url": {
+ "type": "string",
+ "title": "URL",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "new",
+ "queued",
+ "syncing",
+ "completed",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `new` - New\n* `queued` - Queued\n* `syncing` - Syncing\n* `completed` - Completed\n* `failed` - Failed",
+ "x-spec-enum-id": "97ed937d7f0040be"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "New",
+ "Queued",
+ "Syncing",
+ "Completed",
+ "Failed"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "sync_interval": {
+ "enum": [
+ 1,
+ 60,
+ 720,
+ 1440,
+ 10080,
+ 43200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days",
+ "x-spec-enum-id": "2e9f2567ecd93fbe",
+ "nullable": true,
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "parameters": {
+ "nullable": true
+ },
+ "ignore_rules": {
+ "type": "string",
+ "description": "Patterns (one per line) matching files to ignore when syncing"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_synced": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "file_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "file_count",
+ "id",
+ "last_synced",
+ "last_updated",
+ "name",
+ "source_url",
+ "status",
+ "type",
+ "url"
+ ]
+ },
+ "DataSourceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "type": {
+ "enum": [
+ null,
+ "local",
+ "git",
+ "amazon-s3"
+ ],
+ "description": "* `None` - ---------\n* `local` - Local\n* `git` - Git\n* `amazon-s3` - Amazon S3",
+ "x-spec-enum-id": "562b613a749b34b0"
+ },
+ "source_url": {
+ "type": "string",
+ "minLength": 1,
+ "title": "URL",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "sync_interval": {
+ "enum": [
+ 1,
+ 60,
+ 720,
+ 1440,
+ 10080,
+ 43200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days",
+ "x-spec-enum-id": "2e9f2567ecd93fbe",
+ "nullable": true,
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "parameters": {
+ "nullable": true
+ },
+ "ignore_rules": {
+ "type": "string",
+ "description": "Patterns (one per line) matching files to ignore when syncing"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "source_url",
+ "type"
+ ]
+ },
+ "Device": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ },
+ "device_type": {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ },
+ "role": {
+ "$ref": "#/components/schemas/BriefDeviceRole"
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatform"
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "description": "Chassis serial number, assigned by the manufacturer",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "site": {
+ "$ref": "#/components/schemas/BriefSite"
+ },
+ "location": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocation"
+ }
+ ],
+ "nullable": true
+ },
+ "rack": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRack"
+ }
+ ],
+ "nullable": true
+ },
+ "position": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.5,
+ "exclusiveMaximum": true,
+ "nullable": true,
+ "title": "Position (U)"
+ },
+ "face": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front",
+ "rear",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front",
+ "Rear"
+ ]
+ }
+ }
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "parent_device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDevice"
+ }
+ ],
+ "nullable": true,
+ "readOnly": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "inventory",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "65feb4244cc9110c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Staged",
+ "Failed",
+ "Inventory",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "airflow": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front to rear",
+ "Rear to front",
+ "Left to right",
+ "Right to left",
+ "Side to rear",
+ "Rear to side",
+ "Bottom to top",
+ "Top to bottom",
+ "Passive",
+ "Mixed"
+ ]
+ }
+ }
+ },
+ "primary_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_ip4": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "oob_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCluster"
+ }
+ ],
+ "nullable": true
+ },
+ "virtual_chassis": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualChassis"
+ }
+ ],
+ "nullable": true
+ },
+ "vc_position": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true
+ },
+ "vc_priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Virtual chassis master election priority"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "console_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "console_server_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_outlet_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "interface_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "front_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "rear_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "device_bay_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "module_bay_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "inventory_item_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "console_port_count",
+ "console_server_port_count",
+ "created",
+ "device_bay_count",
+ "device_type",
+ "display",
+ "display_url",
+ "front_port_count",
+ "id",
+ "interface_count",
+ "inventory_item_count",
+ "last_updated",
+ "module_bay_count",
+ "parent_device",
+ "power_outlet_count",
+ "power_port_count",
+ "primary_ip",
+ "rear_port_count",
+ "role",
+ "site",
+ "url"
+ ]
+ },
+ "DeviceBay": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDevice"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "DeviceBayRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "DeviceBayTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "device_type",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "DeviceBayTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "device_type",
+ "name"
+ ]
+ },
+ "DeviceRole": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "vm_role": {
+ "type": "boolean",
+ "description": "Virtual machines may be assigned to this role"
+ },
+ "config_template": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDeviceRole"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "device_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "virtualmachine_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url",
+ "virtualmachine_count"
+ ]
+ },
+ "DeviceRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "vm_role": {
+ "type": "boolean",
+ "description": "Virtual machines may be assigned to this role"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "DeviceType": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "manufacturer": {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ },
+ "default_platform": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatform"
+ }
+ ],
+ "nullable": true
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "u_height": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.0,
+ "exclusiveMaximum": true,
+ "default": 1.0,
+ "title": "Position (U)"
+ },
+ "exclude_from_utilization": {
+ "type": "boolean",
+ "description": "Devices of this type are excluded when calculating rack utilization."
+ },
+ "is_full_depth": {
+ "type": "boolean",
+ "description": "Device consumes both front and rear rack faces."
+ },
+ "subdevice_role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "parent",
+ "child",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `parent` - Parent\n* `child` - Child",
+ "x-spec-enum-id": "65a61d5e1deb4a24"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Parent",
+ "Child"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "airflow": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front to rear",
+ "Rear to front",
+ "Left to right",
+ "Right to left",
+ "Side to rear",
+ "Rear to side",
+ "Bottom to top",
+ "Top to bottom",
+ "Passive",
+ "Mixed"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Kilograms",
+ "Grams",
+ "Pounds",
+ "Ounces"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "front_image": {
+ "type": "string",
+ "format": "uri",
+ "nullable": true
+ },
+ "rear_image": {
+ "type": "string",
+ "format": "uri",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "device_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "console_port_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "console_server_port_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_port_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_outlet_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "interface_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "front_port_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "rear_port_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "device_bay_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "module_bay_template_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "inventory_item_template_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "console_port_template_count",
+ "console_server_port_template_count",
+ "created",
+ "device_bay_template_count",
+ "device_count",
+ "display",
+ "display_url",
+ "front_port_template_count",
+ "id",
+ "interface_template_count",
+ "inventory_item_template_count",
+ "last_updated",
+ "manufacturer",
+ "model",
+ "module_bay_template_count",
+ "power_outlet_template_count",
+ "power_port_template_count",
+ "rear_port_template_count",
+ "slug",
+ "url"
+ ]
+ },
+ "DeviceTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "default_platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "u_height": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.0,
+ "exclusiveMaximum": true,
+ "default": 1.0,
+ "title": "Position (U)"
+ },
+ "exclude_from_utilization": {
+ "type": "boolean",
+ "description": "Devices of this type are excluded when calculating rack utilization."
+ },
+ "is_full_depth": {
+ "type": "boolean",
+ "description": "Device consumes both front and rear rack faces."
+ },
+ "subdevice_role": {
+ "enum": [
+ "parent",
+ "child",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `parent` - Parent\n* `child` - Child",
+ "x-spec-enum-id": "65a61d5e1deb4a24",
+ "nullable": true
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "front_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "rear_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "DeviceWithConfigContext": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ },
+ "device_type": {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ },
+ "role": {
+ "$ref": "#/components/schemas/BriefDeviceRole"
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatform"
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "description": "Chassis serial number, assigned by the manufacturer",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "site": {
+ "$ref": "#/components/schemas/BriefSite"
+ },
+ "location": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocation"
+ }
+ ],
+ "nullable": true
+ },
+ "rack": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRack"
+ }
+ ],
+ "nullable": true
+ },
+ "position": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.5,
+ "exclusiveMaximum": true,
+ "nullable": true,
+ "title": "Position (U)"
+ },
+ "face": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front",
+ "rear",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front",
+ "Rear"
+ ]
+ }
+ }
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "parent_device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDevice"
+ }
+ ],
+ "nullable": true,
+ "readOnly": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "inventory",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "65feb4244cc9110c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Staged",
+ "Failed",
+ "Inventory",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "airflow": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front to rear",
+ "Rear to front",
+ "Left to right",
+ "Right to left",
+ "Side to rear",
+ "Rear to side",
+ "Bottom to top",
+ "Top to bottom",
+ "Passive",
+ "Mixed"
+ ]
+ }
+ }
+ },
+ "primary_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_ip4": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "oob_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCluster"
+ }
+ ],
+ "nullable": true
+ },
+ "virtual_chassis": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualChassis"
+ }
+ ],
+ "nullable": true
+ },
+ "vc_position": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true
+ },
+ "vc_priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Virtual chassis master election priority"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "config_context": {
+ "nullable": true,
+ "readOnly": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "console_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "console_server_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "power_outlet_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "interface_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "front_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "rear_port_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "device_bay_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "module_bay_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "inventory_item_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "config_context",
+ "console_port_count",
+ "console_server_port_count",
+ "created",
+ "device_bay_count",
+ "device_type",
+ "display",
+ "display_url",
+ "front_port_count",
+ "id",
+ "interface_count",
+ "inventory_item_count",
+ "last_updated",
+ "module_bay_count",
+ "parent_device",
+ "power_outlet_count",
+ "power_port_count",
+ "primary_ip",
+ "rear_port_count",
+ "role",
+ "site",
+ "url"
+ ]
+ },
+ "DeviceWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ },
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "description": "Chassis serial number, assigned by the manufacturer",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "position": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.5,
+ "exclusiveMaximum": true,
+ "nullable": true,
+ "title": "Position (U)"
+ },
+ "face": {
+ "enum": [
+ "front",
+ "rear",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83"
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "inventory",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "65feb4244cc9110c"
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e"
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "oob_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "virtual_chassis": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualChassisRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vc_position": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true
+ },
+ "vc_priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Virtual chassis master election priority"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device_type",
+ "role",
+ "site"
+ ]
+ },
+ "EventRule": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "event_types": {
+ "type": "array",
+ "items": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2"
+ },
+ "description": "The types of event which will trigger this rule."
+ },
+ "conditions": {
+ "nullable": true,
+ "description": "A set of conditions which determine whether the event will be generated."
+ },
+ "action_type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "webhook",
+ "script",
+ "notification"
+ ],
+ "type": "string",
+ "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification",
+ "x-spec-enum-id": "287901b937995956"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Webhook",
+ "Script",
+ "Notification"
+ ]
+ }
+ }
+ },
+ "action_object_type": {
+ "type": "string"
+ },
+ "action_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "action_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "action_object",
+ "action_object_type",
+ "action_type",
+ "created",
+ "display",
+ "display_url",
+ "event_types",
+ "id",
+ "last_updated",
+ "name",
+ "object_types",
+ "url"
+ ]
+ },
+ "EventRuleRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "event_types": {
+ "type": "array",
+ "items": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2"
+ },
+ "description": "The types of event which will trigger this rule."
+ },
+ "conditions": {
+ "nullable": true,
+ "description": "A set of conditions which determine whether the event will be generated."
+ },
+ "action_type": {
+ "enum": [
+ "webhook",
+ "script",
+ "notification"
+ ],
+ "type": "string",
+ "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification",
+ "x-spec-enum-id": "287901b937995956"
+ },
+ "action_object_type": {
+ "type": "string"
+ },
+ "action_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ },
+ "required": [
+ "action_object_type",
+ "action_type",
+ "event_types",
+ "name",
+ "object_types"
+ ]
+ },
+ "ExportTemplate": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "environment_params": {
+ "nullable": true,
+ "title": "Environment parameters",
+ "description": "Any additional parameters to pass when constructing the Jinja environment"
+ },
+ "template_code": {
+ "type": "string",
+ "description": "Jinja template code."
+ },
+ "mime_type": {
+ "type": "string",
+ "description": "Defaults to text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "$ref": "#/components/schemas/BriefDataSource"
+ },
+ "data_path": {
+ "type": "string",
+ "readOnly": true,
+ "description": "Path to remote file (relative to data source root)"
+ },
+ "data_file": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDataFile"
+ }
+ ],
+ "readOnly": true
+ },
+ "data_synced": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true,
+ "title": "Date synced"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "data_file",
+ "data_path",
+ "data_synced",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "object_types",
+ "template_code",
+ "url"
+ ]
+ },
+ "ExportTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "environment_params": {
+ "nullable": true,
+ "title": "Environment parameters",
+ "description": "Any additional parameters to pass when constructing the Jinja environment"
+ },
+ "template_code": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja template code."
+ },
+ "mime_type": {
+ "type": "string",
+ "description": "Defaults to text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "name",
+ "object_types",
+ "template_code"
+ ]
+ },
+ "FHRPGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "protocol": {
+ "enum": [
+ "vrrp2",
+ "vrrp3",
+ "carp",
+ "clusterxl",
+ "hsrp",
+ "glbp",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `vrrp2` - VRRPv2\n* `vrrp3` - VRRPv3\n* `carp` - CARP\n* `clusterxl` - ClusterXL\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `other` - Other",
+ "x-spec-enum-id": "98de93c9f65d1c65"
+ },
+ "group_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "auth_type": {
+ "enum": [
+ "plaintext",
+ "md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `plaintext` - Plaintext\n* `md5` - MD5",
+ "x-spec-enum-id": "565396e386e1542a",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_key": {
+ "type": "string",
+ "title": "Authentication key",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "ip_addresses": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ },
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "group_id",
+ "id",
+ "ip_addresses",
+ "last_updated",
+ "protocol",
+ "url"
+ ]
+ },
+ "FHRPGroupAssignment": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "group": {
+ "$ref": "#/components/schemas/BriefFHRPGroup"
+ },
+ "interface_type": {
+ "type": "string"
+ },
+ "interface_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "interface": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "group",
+ "id",
+ "interface",
+ "interface_id",
+ "interface_type",
+ "last_updated",
+ "priority",
+ "url"
+ ]
+ },
+ "FHRPGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefFHRPGroupRequest"
+ }
+ ]
+ },
+ "interface_type": {
+ "type": "string"
+ },
+ "interface_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0
+ }
+ },
+ "required": [
+ "group",
+ "interface_id",
+ "interface_type",
+ "priority"
+ ]
+ },
+ "FHRPGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "vrrp2",
+ "vrrp3",
+ "carp",
+ "clusterxl",
+ "hsrp",
+ "glbp",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `vrrp2` - VRRPv2\n* `vrrp3` - VRRPv3\n* `carp` - CARP\n* `clusterxl` - ClusterXL\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `other` - Other",
+ "x-spec-enum-id": "98de93c9f65d1c65"
+ },
+ "group_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "auth_type": {
+ "enum": [
+ "plaintext",
+ "md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `plaintext` - Plaintext\n* `md5` - MD5",
+ "x-spec-enum-id": "565396e386e1542a",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_key": {
+ "type": "string",
+ "title": "Authentication key",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "group_id",
+ "protocol"
+ ]
+ },
+ "FrontPort": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "8P8C",
+ "8P6C",
+ "8P4C",
+ "8P2C",
+ "6P6C",
+ "6P4C",
+ "6P2C",
+ "4P4C",
+ "4P2C",
+ "GG45",
+ "TERA 4P",
+ "TERA 2P",
+ "TERA 1P",
+ "110 Punch",
+ "BNC",
+ "F Connector",
+ "N Connector",
+ "MRJ21",
+ "FC",
+ "FC/PC",
+ "FC/UPC",
+ "FC/APC",
+ "LC",
+ "LC/PC",
+ "LC/UPC",
+ "LC/APC",
+ "LSH",
+ "LSH/PC",
+ "LSH/UPC",
+ "LSH/APC",
+ "LX.5",
+ "LX.5/PC",
+ "LX.5/UPC",
+ "LX.5/APC",
+ "MPO",
+ "MTRJ",
+ "SC",
+ "SC/PC",
+ "SC/UPC",
+ "SC/APC",
+ "ST",
+ "CS",
+ "SN",
+ "SMA 905",
+ "SMA 906",
+ "URM-P2",
+ "URM-P4",
+ "URM-P8",
+ "Splice",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortMapping"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "type",
+ "url"
+ ]
+ },
+ "FrontPortMapping": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "rear_port": {
+ "type": "integer"
+ },
+ "rear_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "position",
+ "rear_port"
+ ]
+ },
+ "FrontPortMappingRequest": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "rear_port": {
+ "type": "integer"
+ },
+ "rear_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "position",
+ "rear_port"
+ ]
+ },
+ "FrontPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "FrontPortTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "8P8C",
+ "8P6C",
+ "8P4C",
+ "8P2C",
+ "6P6C",
+ "6P4C",
+ "6P2C",
+ "4P4C",
+ "4P2C",
+ "GG45",
+ "TERA 4P",
+ "TERA 2P",
+ "TERA 1P",
+ "110 Punch",
+ "BNC",
+ "F Connector",
+ "N Connector",
+ "MRJ21",
+ "FC",
+ "FC/PC",
+ "FC/UPC",
+ "FC/APC",
+ "LC",
+ "LC/PC",
+ "LC/UPC",
+ "LC/APC",
+ "LSH",
+ "LSH/PC",
+ "LSH/UPC",
+ "LSH/APC",
+ "LX.5",
+ "LX.5/PC",
+ "LX.5/UPC",
+ "LX.5/APC",
+ "MPO",
+ "MTRJ",
+ "SC",
+ "SC/PC",
+ "SC/UPC",
+ "SC/APC",
+ "ST",
+ "CS",
+ "SN",
+ "SMA 905",
+ "SMA 906",
+ "URM-P2",
+ "URM-P4",
+ "URM-P8",
+ "Splice",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortTemplateMapping"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "type",
+ "url"
+ ]
+ },
+ "FrontPortTemplateMapping": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "rear_port": {
+ "type": "integer"
+ },
+ "rear_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "position",
+ "rear_port"
+ ]
+ },
+ "FrontPortTemplateMappingRequest": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "rear_port": {
+ "type": "integer"
+ },
+ "rear_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "position",
+ "rear_port"
+ ]
+ },
+ "FrontPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "GenericObject": {
+ "type": "object",
+ "description": "Minimal representation of some generic object identified by ContentType and PK.",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer"
+ },
+ "object": {
+ "nullable": true,
+ "readOnly": true
+ }
+ },
+ "required": [
+ "object",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "GenericObjectRequest": {
+ "type": "object",
+ "description": "Minimal representation of some generic object identified by ContentType and PK.",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "object_id",
+ "object_type"
+ ]
+ },
+ "Group": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ObjectPermission"
+ }
+ },
+ "user_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url",
+ "user_count"
+ ]
+ },
+ "GroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "IKEPolicy": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "version": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - IKEv1\n* `2` - IKEv2",
+ "x-spec-enum-id": "00872b77916a1fde"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IKEv1",
+ "IKEv2"
+ ]
+ }
+ }
+ },
+ "mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "aggressive",
+ "main"
+ ],
+ "type": "string",
+ "description": "* `aggressive` - Aggressive\n* `main` - Main",
+ "x-spec-enum-id": "64c1be7bdb2548ca"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Aggressive",
+ "Main"
+ ]
+ }
+ }
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IKEProposal"
+ }
+ },
+ "preshared_key": {
+ "type": "string",
+ "title": "Pre-shared key"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url",
+ "version"
+ ]
+ },
+ "IKEPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - IKEv1\n* `2` - IKEv2",
+ "x-spec-enum-id": "00872b77916a1fde"
+ },
+ "mode": {
+ "enum": [
+ "aggressive",
+ "main"
+ ],
+ "type": "string",
+ "description": "* `aggressive` - Aggressive\n* `main` - Main",
+ "x-spec-enum-id": "64c1be7bdb2548ca"
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "preshared_key": {
+ "type": "string",
+ "title": "Pre-shared key"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "version"
+ ]
+ },
+ "IKEProposal": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "authentication_method": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "preshared-keys",
+ "certificates",
+ "rsa-signatures",
+ "dsa-signatures"
+ ],
+ "type": "string",
+ "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures",
+ "x-spec-enum-id": "a21158c52d0c455a"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Pre-shared keys",
+ "Certificates",
+ "RSA signatures",
+ "DSA signatures"
+ ]
+ }
+ }
+ },
+ "encryption_algorithm": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "128-bit AES (CBC)",
+ "128-bit AES (GCM)",
+ "192-bit AES (CBC)",
+ "192-bit AES (GCM)",
+ "256-bit AES (CBC)",
+ "256-bit AES (GCM)",
+ "3DES",
+ "DES"
+ ]
+ }
+ }
+ },
+ "authentication_algorithm": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5"
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "SHA-1 HMAC",
+ "SHA-256 HMAC",
+ "SHA-384 HMAC",
+ "SHA-512 HMAC",
+ "MD5 HMAC"
+ ]
+ }
+ }
+ },
+ "group": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "x-spec-enum-id": "dbef43be795462a8"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Group 1",
+ "Group 2",
+ "Group 5",
+ "Group 14",
+ "Group 15",
+ "Group 16",
+ "Group 17",
+ "Group 18",
+ "Group 19",
+ "Group 20",
+ "Group 21",
+ "Group 22",
+ "Group 23",
+ "Group 24",
+ "Group 25",
+ "Group 26",
+ "Group 27",
+ "Group 28",
+ "Group 29",
+ "Group 30",
+ "Group 31",
+ "Group 32",
+ "Group 33",
+ "Group 34"
+ ]
+ }
+ }
+ },
+ "sa_lifetime": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Security association lifetime (in seconds)"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "authentication_method",
+ "created",
+ "display",
+ "display_url",
+ "encryption_algorithm",
+ "group",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "IKEProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "authentication_method": {
+ "enum": [
+ "preshared-keys",
+ "certificates",
+ "rsa-signatures",
+ "dsa-signatures"
+ ],
+ "type": "string",
+ "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures",
+ "x-spec-enum-id": "a21158c52d0c455a"
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5"
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7"
+ },
+ "group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "x-spec-enum-id": "dbef43be795462a8"
+ },
+ "sa_lifetime": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Security association lifetime (in seconds)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "authentication_method",
+ "encryption_algorithm",
+ "group",
+ "name"
+ ]
+ },
+ "IPAddress": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "family": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 4,
+ 6
+ ],
+ "type": "integer",
+ "description": "* `4` - IPv4\n* `6` - IPv6",
+ "x-spec-enum-id": "d72003fd1af3603d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IPv4",
+ "IPv6"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "address": {
+ "type": "string"
+ },
+ "vrf": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRF"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated",
+ "dhcp",
+ "slaac"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC",
+ "x-spec-enum-id": "c421c4c4a0fa7a2a"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Reserved",
+ "Deprecated",
+ "DHCP",
+ "SLAAC"
+ ]
+ }
+ }
+ },
+ "role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "loopback",
+ "secondary",
+ "anycast",
+ "vip",
+ "vrrp",
+ "hsrp",
+ "glbp",
+ "carp",
+ ""
+ ],
+ "type": "string",
+ "description": "* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP",
+ "x-spec-enum-id": "53dca4cddd7b344a"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Loopback",
+ "Secondary",
+ "Anycast",
+ "VIP",
+ "VRRP",
+ "HSRP",
+ "GLBP",
+ "CARP"
+ ]
+ }
+ }
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "assigned_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "nat_inside": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "nat_outside": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedIPAddress"
+ },
+ "readOnly": true
+ },
+ "dns_name": {
+ "type": "string",
+ "description": "Hostname or FQDN (not case-sensitive)",
+ "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "address",
+ "assigned_object",
+ "created",
+ "display",
+ "display_url",
+ "family",
+ "id",
+ "last_updated",
+ "nat_outside",
+ "url"
+ ]
+ },
+ "IPAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated",
+ "dhcp",
+ "slaac"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC",
+ "x-spec-enum-id": "c421c4c4a0fa7a2a"
+ },
+ "role": {
+ "enum": [
+ "loopback",
+ "secondary",
+ "anycast",
+ "vip",
+ "vrrp",
+ "hsrp",
+ "glbp",
+ "carp",
+ ""
+ ],
+ "type": "string",
+ "description": "* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP",
+ "x-spec-enum-id": "53dca4cddd7b344a"
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "nat_inside": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "dns_name": {
+ "type": "string",
+ "description": "Hostname or FQDN (not case-sensitive)",
+ "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "address"
+ ]
+ },
+ "IPRange": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "family": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 4,
+ 6
+ ],
+ "type": "integer",
+ "description": "* `4` - IPv4\n* `6` - IPv6",
+ "x-spec-enum-id": "d72003fd1af3603d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IPv4",
+ "IPv6"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "start_address": {
+ "type": "string"
+ },
+ "end_address": {
+ "type": "string"
+ },
+ "size": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "vrf": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRF"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "ca933c38b935e547"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Reserved",
+ "Deprecated"
+ ]
+ }
+ }
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRole"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "mark_populated": {
+ "type": "boolean",
+ "description": "Prevent the creation of IP addresses within this range"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Report space as fully utilized"
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "end_address",
+ "family",
+ "id",
+ "last_updated",
+ "size",
+ "start_address",
+ "url"
+ ]
+ },
+ "IPRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "start_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "end_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "ca933c38b935e547"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "mark_populated": {
+ "type": "boolean",
+ "description": "Prevent the creation of IP addresses within this range"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Report space as fully utilized"
+ }
+ },
+ "required": [
+ "end_address",
+ "start_address"
+ ]
+ },
+ "IPSecPolicy": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPSecProposal"
+ }
+ },
+ "pfs_group": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "x-spec-enum-id": "dbef43be795462a8"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Group 1",
+ "Group 2",
+ "Group 5",
+ "Group 14",
+ "Group 15",
+ "Group 16",
+ "Group 17",
+ "Group 18",
+ "Group 19",
+ "Group 20",
+ "Group 21",
+ "Group 22",
+ "Group 23",
+ "Group 24",
+ "Group 25",
+ "Group 26",
+ "Group 27",
+ "Group 28",
+ "Group 29",
+ "Group 30",
+ "Group 31",
+ "Group 32",
+ "Group 33",
+ "Group 34"
+ ]
+ }
+ }
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "IPSecPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "pfs_group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "description": "* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "x-spec-enum-id": "dbef43be795462a8"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "IPSecProfile": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "esp",
+ "ah"
+ ],
+ "type": "string",
+ "description": "* `esp` - ESP\n* `ah` - AH",
+ "x-spec-enum-id": "87ac6ada0da14ccf"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "ESP",
+ "AH"
+ ]
+ }
+ }
+ },
+ "ike_policy": {
+ "$ref": "#/components/schemas/BriefIKEPolicy"
+ },
+ "ipsec_policy": {
+ "$ref": "#/components/schemas/BriefIPSecPolicy"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "ike_policy",
+ "ipsec_policy",
+ "last_updated",
+ "mode",
+ "name",
+ "url"
+ ]
+ },
+ "IPSecProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "esp",
+ "ah"
+ ],
+ "type": "string",
+ "description": "* `esp` - ESP\n* `ah` - AH",
+ "x-spec-enum-id": "87ac6ada0da14ccf"
+ },
+ "ike_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIKEPolicyRequest"
+ }
+ ]
+ },
+ "ipsec_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIPSecPolicyRequest"
+ }
+ ]
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "ike_policy",
+ "ipsec_policy",
+ "mode",
+ "name"
+ ]
+ },
+ "IPSecProposal": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "encryption_algorithm": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "128-bit AES (CBC)",
+ "128-bit AES (GCM)",
+ "192-bit AES (CBC)",
+ "192-bit AES (GCM)",
+ "256-bit AES (CBC)",
+ "256-bit AES (GCM)",
+ "3DES",
+ "DES"
+ ]
+ }
+ }
+ },
+ "authentication_algorithm": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5"
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "SHA-1 HMAC",
+ "SHA-256 HMAC",
+ "SHA-384 HMAC",
+ "SHA-512 HMAC",
+ "MD5 HMAC"
+ ]
+ }
+ }
+ },
+ "sa_lifetime_seconds": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (seconds)",
+ "description": "Security association lifetime (seconds)"
+ },
+ "sa_lifetime_data": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (KB)",
+ "description": "Security association lifetime (in kilobytes)"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "IPSecProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5"
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7"
+ },
+ "sa_lifetime_seconds": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (seconds)",
+ "description": "Security association lifetime (seconds)"
+ },
+ "sa_lifetime_data": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (KB)",
+ "description": "Security association lifetime (in kilobytes)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "ImageAttachment": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "parent": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "image": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "image_height": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "image_width": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "image",
+ "image_height",
+ "image_width",
+ "last_updated",
+ "object_id",
+ "object_type",
+ "parent",
+ "url"
+ ]
+ },
+ "ImageAttachmentRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "image": {
+ "type": "string",
+ "format": "binary"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "image",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "IntegerRange": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "example": [
+ 10,
+ 20
+ ]
+ },
+ "IntegerRangeRequest": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "example": [
+ 10,
+ 20
+ ]
+ },
+ "Interface": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "vdcs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualDeviceContext"
+ }
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Virtual",
+ "Bridge",
+ "Link Aggregation Group (LAG)",
+ "100BASE-FX (10/100ME)",
+ "100BASE-LFX (10/100ME)",
+ "100BASE-TX (10/100ME)",
+ "100BASE-T1 (10/100ME)",
+ "1000BASE-BX10-D (1GE BiDi Down)",
+ "1000BASE-BX10-U (1GE BiDi Up)",
+ "1000BASE-CWDM (1GE)",
+ "1000BASE-CX (1GE DAC)",
+ "1000BASE-DWDM (1GE)",
+ "1000BASE-EX (1GE)",
+ "1000BASE-LSX (1GE)",
+ "1000BASE-LX (1GE)",
+ "1000BASE-LX10/LH (1GE)",
+ "1000BASE-SX (1GE)",
+ "1000BASE-T (1GE)",
+ "1000BASE-TX (1GE)",
+ "1000BASE-ZX (1GE)",
+ "2.5GBASE-T (2.5GE)",
+ "5GBASE-T (5GE)",
+ "10GBASE-BR-D (10GE BiDi Down)",
+ "10GBASE-BR-U (10GE BiDi Up)",
+ "10GBASE-CX4 (10GE DAC)",
+ "10GBASE-ER (10GE)",
+ "10GBASE-LR (10GE)",
+ "10GBASE-LRM (10GE)",
+ "10GBASE-LX4 (10GE)",
+ "10GBASE-SR (10GE)",
+ "10GBASE-T (10GE)",
+ "10GBASE-ZR (10GE)",
+ "25GBASE-CR (25GE DAC)",
+ "25GBASE-ER (25GE)",
+ "25GBASE-LR (25GE)",
+ "25GBASE-SR (25GE)",
+ "25GBASE-T (25GE)",
+ "40GBASE-CR4 (40GE DAC)",
+ "40GBASE-ER4 (40GE)",
+ "40GBASE-FR4 (40GE)",
+ "40GBASE-LR4 (40GE)",
+ "40GBASE-SR4 (40GE)",
+ "50GBASE-CR (50GE DAC)",
+ "50GBASE-ER (50GE)",
+ "50GBASE-FR (50GE)",
+ "50GBASE-LR (50GE)",
+ "50GBASE-SR (50GE)",
+ "100GBASE-CR1 (100GE DAC)",
+ "100GBASE-CR2 (100GE DAC)",
+ "100GBASE-CR4 (100GE DAC)",
+ "100GBASE-CR10 (100GE DAC)",
+ "100GBASE-CWDM4 (100GE)",
+ "100GBASE-DR (100GE)",
+ "100GBASE-ER4 (100GE)",
+ "100GBASE-FR1 (100GE)",
+ "100GBASE-LR1 (100GE)",
+ "100GBASE-LR4 (100GE)",
+ "100GBASE-SR1 (100GE)",
+ "100GBASE-SR1.2 (100GE BiDi)",
+ "100GBASE-SR2 (100GE)",
+ "100GBASE-SR4 (100GE)",
+ "100GBASE-SR10 (100GE)",
+ "100GBASE-ZR (100GE)",
+ "200GBASE-CR2 (200GE)",
+ "200GBASE-CR4 (200GE)",
+ "200GBASE-DR4 (200GE)",
+ "200GBASE-ER4 (200GE)",
+ "200GBASE-FR4 (200GE)",
+ "200GBASE-LR4 (200GE)",
+ "200GBASE-SR2 (200GE)",
+ "200GBASE-SR4 (200GE)",
+ "200GBASE-VR2 (200GE)",
+ "400GBASE-CR4 (400GE)",
+ "400GBASE-DR4 (400GE)",
+ "400GBASE-ER8 (400GE)",
+ "400GBASE-FR4 (400GE)",
+ "400GBASE-FR8 (400GE)",
+ "400GBASE-LR4 (400GE)",
+ "400GBASE-LR8 (400GE)",
+ "400GBASE-SR4 (400GE)",
+ "400GBASE-SR4.2 (400GE BiDi)",
+ "400GBASE-SR8 (400GE)",
+ "400GBASE-SR16 (400GE)",
+ "400GBASE-VR4 (400GE)",
+ "400GBASE-ZR (400GE)",
+ "800GBASE-CR8 (800GE)",
+ "800GBASE-DR8 (800GE)",
+ "800GBASE-SR8 (800GE)",
+ "800GBASE-VR8 (800GE)",
+ "SFP (100ME)",
+ "GBIC (1GE)",
+ "SFP (1GE)",
+ "SFP+ (10GE)",
+ "XENPAK (10GE)",
+ "XFP (10GE)",
+ "X2 (10GE)",
+ "SFP28 (25GE)",
+ "QSFP+ (40GE)",
+ "QSFP28 (50GE)",
+ "SFP56 (50GE)",
+ "CFP (100GE)",
+ "CFP2 (100GE)",
+ "CFP4 (100GE)",
+ "CXP (100GE)",
+ "Cisco CPAK (100GE)",
+ "DSFP (100GE)",
+ "QSFP28 (100GE)",
+ "QSFP-DD (100GE)",
+ "SFP-DD (100GE)",
+ "CFP2 (200GE)",
+ "QSFP56 (200GE)",
+ "QSFP-DD (200GE)",
+ "QSFP112 (400GE)",
+ "QSFP-DD (400GE)",
+ "CDFP (400GE)",
+ "CFP2 (400GE)",
+ "CPF8 (400GE)",
+ "OSFP (400GE)",
+ "OSFP-RHS (400GE)",
+ "OSFP (800GE)",
+ "QSFP-DD (800GE)",
+ "1000BASE-KX (1GE)",
+ "2.5GBASE-KX (2.5GE)",
+ "5GBASE-KR (5GE)",
+ "10GBASE-KR (10GE)",
+ "10GBASE-KX4 (10GE)",
+ "25GBASE-KR (25GE)",
+ "40GBASE-KR4 (40GE)",
+ "50GBASE-KR (50GE)",
+ "100GBASE-KP4 (100GE)",
+ "100GBASE-KR2 (100GE)",
+ "100GBASE-KR4 (100GE)",
+ "IEEE 802.11a",
+ "IEEE 802.11b/g",
+ "IEEE 802.11n (Wi-Fi 4)",
+ "IEEE 802.11ac (Wi-Fi 5)",
+ "IEEE 802.11ad (WiGig)",
+ "IEEE 802.11ax (Wi-Fi 6)",
+ "IEEE 802.11ay (WiGig)",
+ "IEEE 802.11be (Wi-Fi 7)",
+ "IEEE 802.15.1 (Bluetooth)",
+ "IEEE 802.15.4 (LR-WPAN)",
+ "Other (Wireless)",
+ "GSM",
+ "CDMA",
+ "LTE",
+ "4G",
+ "5G",
+ "OC-3/STM-1",
+ "OC-12/STM-4",
+ "OC-48/STM-16",
+ "OC-192/STM-64",
+ "OC-768/STM-256",
+ "OC-1920/STM-640",
+ "OC-3840/STM-1234",
+ "SFP (1GFC)",
+ "SFP (2GFC)",
+ "SFP (4GFC)",
+ "SFP+ (8GFC)",
+ "SFP+ (16GFC)",
+ "SFP28 (32GFC)",
+ "SFP+ (32GFC)",
+ "QSFP+ (64GFC)",
+ "SFP-DD (64GFC)",
+ "SFP+ (64GFC)",
+ "QSFP28 (128GFC)",
+ "SDR (2 Gbps)",
+ "DDR (4 Gbps)",
+ "QDR (8 Gbps)",
+ "FDR10 (10 Gbps)",
+ "FDR (13.5 Gbps)",
+ "EDR (25 Gbps)",
+ "HDR (50 Gbps)",
+ "NDR (100 Gbps)",
+ "XDR (250 Gbps)",
+ "T1 (1.544 Mbps)",
+ "E1 (2.048 Mbps)",
+ "T3 (45 Mbps)",
+ "E3 (34 Mbps)",
+ "xDSL",
+ "DOCSIS",
+ "MoCA",
+ "BPON (622 Mbps / 155 Mbps)",
+ "EPON (1 Gbps)",
+ "10G-EPON (10 Gbps)",
+ "GPON (2.5 Gbps / 1.25 Gbps)",
+ "XG-PON (10 Gbps / 2.5 Gbps)",
+ "XGS-PON (10 Gbps)",
+ "NG-PON2 (TWDM-PON) (4x10 Gbps)",
+ "25G-PON (25 Gbps)",
+ "50G-PON (50 Gbps)",
+ "Cisco StackWise",
+ "Cisco StackWise Plus",
+ "Cisco FlexStack",
+ "Cisco FlexStack Plus",
+ "Cisco StackWise-80",
+ "Cisco StackWise-160",
+ "Cisco StackWise-320",
+ "Cisco StackWise-480",
+ "Cisco StackWise-1T",
+ "Juniper VCP",
+ "Extreme SummitStack",
+ "Extreme SummitStack-128",
+ "Extreme SummitStack-256",
+ "Extreme SummitStack-512",
+ "Other"
+ ]
+ }
+ }
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterface"
+ }
+ ],
+ "nullable": true
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterface"
+ }
+ ],
+ "nullable": true
+ },
+ "lag": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterface"
+ }
+ ],
+ "nullable": true
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "mac_address": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "mac_addresses": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BriefMACAddress"
+ },
+ "readOnly": true,
+ "nullable": true
+ },
+ "speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Speed (Kbps)"
+ },
+ "duplex": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "half",
+ "full",
+ "auto",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto",
+ "x-spec-enum-id": "368458a2b67c916b"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Half",
+ "Full",
+ "Auto"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "wwn": {
+ "type": "string",
+ "nullable": true
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only",
+ "description": "This interface is used only for out-of-band management"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ ""
+ ],
+ "type": "string",
+ "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)",
+ "x-spec-enum-id": "84129b71b974ebe5"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Access",
+ "Tagged",
+ "Tagged (All)",
+ "Q-in-Q (802.1ad)"
+ ]
+ }
+ }
+ },
+ "rf_role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "ap",
+ "station",
+ ""
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Access point",
+ "Station"
+ ]
+ }
+ }
+ },
+ "rf_channel": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "2.4g-1-2412-22",
+ "2.4g-2-2417-22",
+ "2.4g-3-2422-22",
+ "2.4g-4-2427-22",
+ "2.4g-5-2432-22",
+ "2.4g-6-2437-22",
+ "2.4g-7-2442-22",
+ "2.4g-8-2447-22",
+ "2.4g-9-2452-22",
+ "2.4g-10-2457-22",
+ "2.4g-11-2462-22",
+ "2.4g-12-2467-22",
+ "2.4g-13-2472-22",
+ "5g-32-5160-20",
+ "5g-34-5170-40",
+ "5g-36-5180-20",
+ "5g-38-5190-40",
+ "5g-40-5200-20",
+ "5g-42-5210-80",
+ "5g-44-5220-20",
+ "5g-46-5230-40",
+ "5g-48-5240-20",
+ "5g-50-5250-160",
+ "5g-52-5260-20",
+ "5g-54-5270-40",
+ "5g-56-5280-20",
+ "5g-58-5290-80",
+ "5g-60-5300-20",
+ "5g-62-5310-40",
+ "5g-64-5320-20",
+ "5g-100-5500-20",
+ "5g-102-5510-40",
+ "5g-104-5520-20",
+ "5g-106-5530-80",
+ "5g-108-5540-20",
+ "5g-110-5550-40",
+ "5g-112-5560-20",
+ "5g-114-5570-160",
+ "5g-116-5580-20",
+ "5g-118-5590-40",
+ "5g-120-5600-20",
+ "5g-122-5610-80",
+ "5g-124-5620-20",
+ "5g-126-5630-40",
+ "5g-128-5640-20",
+ "5g-132-5660-20",
+ "5g-134-5670-40",
+ "5g-136-5680-20",
+ "5g-138-5690-80",
+ "5g-140-5700-20",
+ "5g-142-5710-40",
+ "5g-144-5720-20",
+ "5g-149-5745-20",
+ "5g-151-5755-40",
+ "5g-153-5765-20",
+ "5g-155-5775-80",
+ "5g-157-5785-20",
+ "5g-159-5795-40",
+ "5g-161-5805-20",
+ "5g-163-5815-160",
+ "5g-165-5825-20",
+ "5g-167-5835-40",
+ "5g-169-5845-20",
+ "5g-171-5855-80",
+ "5g-173-5865-20",
+ "5g-175-5875-40",
+ "5g-177-5885-20",
+ "6g-1-5955-20",
+ "6g-3-5965-40",
+ "6g-5-5975-20",
+ "6g-7-5985-80",
+ "6g-9-5995-20",
+ "6g-11-6005-40",
+ "6g-13-6015-20",
+ "6g-15-6025-160",
+ "6g-17-6035-20",
+ "6g-19-6045-40",
+ "6g-21-6055-20",
+ "6g-23-6065-80",
+ "6g-25-6075-20",
+ "6g-27-6085-40",
+ "6g-29-6095-20",
+ "6g-31-6105-320",
+ "6g-33-6115-20",
+ "6g-35-6125-40",
+ "6g-37-6135-20",
+ "6g-39-6145-80",
+ "6g-41-6155-20",
+ "6g-43-6165-40",
+ "6g-45-6175-20",
+ "6g-47-6185-160",
+ "6g-49-6195-20",
+ "6g-51-6205-40",
+ "6g-53-6215-20",
+ "6g-55-6225-80",
+ "6g-57-6235-20",
+ "6g-59-6245-40",
+ "6g-61-6255-20",
+ "6g-65-6275-20",
+ "6g-67-6285-40",
+ "6g-69-6295-20",
+ "6g-71-6305-80",
+ "6g-73-6315-20",
+ "6g-75-6325-40",
+ "6g-77-6335-20",
+ "6g-79-6345-160",
+ "6g-81-6355-20",
+ "6g-83-6365-40",
+ "6g-85-6375-20",
+ "6g-87-6385-80",
+ "6g-89-6395-20",
+ "6g-91-6405-40",
+ "6g-93-6415-20",
+ "6g-95-6425-320",
+ "6g-97-6435-20",
+ "6g-99-6445-40",
+ "6g-101-6455-20",
+ "6g-103-6465-80",
+ "6g-105-6475-20",
+ "6g-107-6485-40",
+ "6g-109-6495-20",
+ "6g-111-6505-160",
+ "6g-113-6515-20",
+ "6g-115-6525-40",
+ "6g-117-6535-20",
+ "6g-119-6545-80",
+ "6g-121-6555-20",
+ "6g-123-6565-40",
+ "6g-125-6575-20",
+ "6g-129-6595-20",
+ "6g-131-6605-40",
+ "6g-133-6615-20",
+ "6g-135-6625-80",
+ "6g-137-6635-20",
+ "6g-139-6645-40",
+ "6g-141-6655-20",
+ "6g-143-6665-160",
+ "6g-145-6675-20",
+ "6g-147-6685-40",
+ "6g-149-6695-20",
+ "6g-151-6705-80",
+ "6g-153-6715-20",
+ "6g-155-6725-40",
+ "6g-157-6735-20",
+ "6g-159-6745-320",
+ "6g-161-6755-20",
+ "6g-163-6765-40",
+ "6g-165-6775-20",
+ "6g-167-6785-80",
+ "6g-169-6795-20",
+ "6g-171-6805-40",
+ "6g-173-6815-20",
+ "6g-175-6825-160",
+ "6g-177-6835-20",
+ "6g-179-6845-40",
+ "6g-181-6855-20",
+ "6g-183-6865-80",
+ "6g-185-6875-20",
+ "6g-187-6885-40",
+ "6g-189-6895-20",
+ "6g-193-6915-20",
+ "6g-195-6925-40",
+ "6g-197-6935-20",
+ "6g-199-6945-80",
+ "6g-201-6955-20",
+ "6g-203-6965-40",
+ "6g-205-6975-20",
+ "6g-207-6985-160",
+ "6g-209-6995-20",
+ "6g-211-7005-40",
+ "6g-213-7015-20",
+ "6g-215-7025-80",
+ "6g-217-7035-20",
+ "6g-219-7045-40",
+ "6g-221-7055-20",
+ "6g-225-7075-20",
+ "6g-227-7085-40",
+ "6g-229-7095-20",
+ "6g-233-7115-20",
+ "60g-1-58320-2160",
+ "60g-2-60480-2160",
+ "60g-3-62640-2160",
+ "60g-4-64800-2160",
+ "60g-5-66960-2160",
+ "60g-6-69120-2160",
+ "60g-9-59400-4320",
+ "60g-10-61560-4320",
+ "60g-11-63720-4320",
+ "60g-12-65880-4320",
+ "60g-13-68040-4320",
+ "60g-17-60480-6480",
+ "60g-18-62640-6480",
+ "60g-19-64800-6480",
+ "60g-20-66960-6480",
+ "60g-25-61560-6480",
+ "60g-26-63720-6480",
+ "60g-27-65880-6480",
+ ""
+ ],
+ "type": "string",
+ "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)",
+ "x-spec-enum-id": "70cf66176c475063"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "1 (2412 MHz)",
+ "2 (2417 MHz)",
+ "3 (2422 MHz)",
+ "4 (2427 MHz)",
+ "5 (2432 MHz)",
+ "6 (2437 MHz)",
+ "7 (2442 MHz)",
+ "8 (2447 MHz)",
+ "9 (2452 MHz)",
+ "10 (2457 MHz)",
+ "11 (2462 MHz)",
+ "12 (2467 MHz)",
+ "13 (2472 MHz)",
+ "32 (5160/20 MHz)",
+ "34 (5170/40 MHz)",
+ "36 (5180/20 MHz)",
+ "38 (5190/40 MHz)",
+ "40 (5200/20 MHz)",
+ "42 (5210/80 MHz)",
+ "44 (5220/20 MHz)",
+ "46 (5230/40 MHz)",
+ "48 (5240/20 MHz)",
+ "50 (5250/160 MHz)",
+ "52 (5260/20 MHz)",
+ "54 (5270/40 MHz)",
+ "56 (5280/20 MHz)",
+ "58 (5290/80 MHz)",
+ "60 (5300/20 MHz)",
+ "62 (5310/40 MHz)",
+ "64 (5320/20 MHz)",
+ "100 (5500/20 MHz)",
+ "102 (5510/40 MHz)",
+ "104 (5520/20 MHz)",
+ "106 (5530/80 MHz)",
+ "108 (5540/20 MHz)",
+ "110 (5550/40 MHz)",
+ "112 (5560/20 MHz)",
+ "114 (5570/160 MHz)",
+ "116 (5580/20 MHz)",
+ "118 (5590/40 MHz)",
+ "120 (5600/20 MHz)",
+ "122 (5610/80 MHz)",
+ "124 (5620/20 MHz)",
+ "126 (5630/40 MHz)",
+ "128 (5640/20 MHz)",
+ "132 (5660/20 MHz)",
+ "134 (5670/40 MHz)",
+ "136 (5680/20 MHz)",
+ "138 (5690/80 MHz)",
+ "140 (5700/20 MHz)",
+ "142 (5710/40 MHz)",
+ "144 (5720/20 MHz)",
+ "149 (5745/20 MHz)",
+ "151 (5755/40 MHz)",
+ "153 (5765/20 MHz)",
+ "155 (5775/80 MHz)",
+ "157 (5785/20 MHz)",
+ "159 (5795/40 MHz)",
+ "161 (5805/20 MHz)",
+ "163 (5815/160 MHz)",
+ "165 (5825/20 MHz)",
+ "167 (5835/40 MHz)",
+ "169 (5845/20 MHz)",
+ "171 (5855/80 MHz)",
+ "173 (5865/20 MHz)",
+ "175 (5875/40 MHz)",
+ "177 (5885/20 MHz)",
+ "1 (5955/20 MHz)",
+ "3 (5965/40 MHz)",
+ "5 (5975/20 MHz)",
+ "7 (5985/80 MHz)",
+ "9 (5995/20 MHz)",
+ "11 (6005/40 MHz)",
+ "13 (6015/20 MHz)",
+ "15 (6025/160 MHz)",
+ "17 (6035/20 MHz)",
+ "19 (6045/40 MHz)",
+ "21 (6055/20 MHz)",
+ "23 (6065/80 MHz)",
+ "25 (6075/20 MHz)",
+ "27 (6085/40 MHz)",
+ "29 (6095/20 MHz)",
+ "31 (6105/320 MHz)",
+ "33 (6115/20 MHz)",
+ "35 (6125/40 MHz)",
+ "37 (6135/20 MHz)",
+ "39 (6145/80 MHz)",
+ "41 (6155/20 MHz)",
+ "43 (6165/40 MHz)",
+ "45 (6175/20 MHz)",
+ "47 (6185/160 MHz)",
+ "49 (6195/20 MHz)",
+ "51 (6205/40 MHz)",
+ "53 (6215/20 MHz)",
+ "55 (6225/80 MHz)",
+ "57 (6235/20 MHz)",
+ "59 (6245/40 MHz)",
+ "61 (6255/20 MHz)",
+ "65 (6275/20 MHz)",
+ "67 (6285/40 MHz)",
+ "69 (6295/20 MHz)",
+ "71 (6305/80 MHz)",
+ "73 (6315/20 MHz)",
+ "75 (6325/40 MHz)",
+ "77 (6335/20 MHz)",
+ "79 (6345/160 MHz)",
+ "81 (6355/20 MHz)",
+ "83 (6365/40 MHz)",
+ "85 (6375/20 MHz)",
+ "87 (6385/80 MHz)",
+ "89 (6395/20 MHz)",
+ "91 (6405/40 MHz)",
+ "93 (6415/20 MHz)",
+ "95 (6425/320 MHz)",
+ "97 (6435/20 MHz)",
+ "99 (6445/40 MHz)",
+ "101 (6455/20 MHz)",
+ "103 (6465/80 MHz)",
+ "105 (6475/20 MHz)",
+ "107 (6485/40 MHz)",
+ "109 (6495/20 MHz)",
+ "111 (6505/160 MHz)",
+ "113 (6515/20 MHz)",
+ "115 (6525/40 MHz)",
+ "117 (6535/20 MHz)",
+ "119 (6545/80 MHz)",
+ "121 (6555/20 MHz)",
+ "123 (6565/40 MHz)",
+ "125 (6575/20 MHz)",
+ "129 (6595/20 MHz)",
+ "131 (6605/40 MHz)",
+ "133 (6615/20 MHz)",
+ "135 (6625/80 MHz)",
+ "137 (6635/20 MHz)",
+ "139 (6645/40 MHz)",
+ "141 (6655/20 MHz)",
+ "143 (6665/160 MHz)",
+ "145 (6675/20 MHz)",
+ "147 (6685/40 MHz)",
+ "149 (6695/20 MHz)",
+ "151 (6705/80 MHz)",
+ "153 (6715/20 MHz)",
+ "155 (6725/40 MHz)",
+ "157 (6735/20 MHz)",
+ "159 (6745/320 MHz)",
+ "161 (6755/20 MHz)",
+ "163 (6765/40 MHz)",
+ "165 (6775/20 MHz)",
+ "167 (6785/80 MHz)",
+ "169 (6795/20 MHz)",
+ "171 (6805/40 MHz)",
+ "173 (6815/20 MHz)",
+ "175 (6825/160 MHz)",
+ "177 (6835/20 MHz)",
+ "179 (6845/40 MHz)",
+ "181 (6855/20 MHz)",
+ "183 (6865/80 MHz)",
+ "185 (6875/20 MHz)",
+ "187 (6885/40 MHz)",
+ "189 (6895/20 MHz)",
+ "193 (6915/20 MHz)",
+ "195 (6925/40 MHz)",
+ "197 (6935/20 MHz)",
+ "199 (6945/80 MHz)",
+ "201 (6955/20 MHz)",
+ "203 (6965/40 MHz)",
+ "205 (6975/20 MHz)",
+ "207 (6985/160 MHz)",
+ "209 (6995/20 MHz)",
+ "211 (7005/40 MHz)",
+ "213 (7015/20 MHz)",
+ "215 (7025/80 MHz)",
+ "217 (7035/20 MHz)",
+ "219 (7045/40 MHz)",
+ "221 (7055/20 MHz)",
+ "225 (7075/20 MHz)",
+ "227 (7085/40 MHz)",
+ "229 (7095/20 MHz)",
+ "233 (7115/20 MHz)",
+ "1 (58.32/2.16 GHz)",
+ "2 (60.48/2.16 GHz)",
+ "3 (62.64/2.16 GHz)",
+ "4 (64.80/2.16 GHz)",
+ "5 (66.96/2.16 GHz)",
+ "6 (69.12/2.16 GHz)",
+ "9 (59.40/4.32 GHz)",
+ "10 (61.56/4.32 GHz)",
+ "11 (63.72/4.32 GHz)",
+ "12 (65.88/4.32 GHz)",
+ "13 (68.04/4.32 GHz)",
+ "17 (60.48/6.48 GHz)",
+ "18 (62.64/6.48 GHz)",
+ "19 (64.80/6.48 GHz)",
+ "20 (66.96/6.48 GHz)",
+ "25 (61.56/8.64 GHz)",
+ "26 (63.72/8.64 GHz)",
+ "27 (65.88/8.64 GHz)"
+ ]
+ }
+ }
+ },
+ "poe_mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "pd",
+ "pse",
+ ""
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "PD",
+ "PSE"
+ ]
+ }
+ }
+ },
+ "poe_type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ ""
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "802.3af (Type 1)",
+ "802.3at (Type 2)",
+ "802.3bt (Type 3)",
+ "802.3bt (Type 4)",
+ "Passive 24V (2-pair)",
+ "Passive 24V (4-pair)",
+ "Passive 48V (2-pair)",
+ "Passive 48V (4-pair)"
+ ]
+ }
+ }
+ },
+ "rf_channel_frequency": {
+ "type": "number",
+ "format": "double",
+ "maximum": 100000,
+ "minimum": -100000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel frequency (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "rf_channel_width": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": -10000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel width (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "tx_power": {
+ "type": "integer",
+ "maximum": 127,
+ "minimum": -40,
+ "nullable": true,
+ "title": "Transmit power (dBm)"
+ },
+ "untagged_vlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLAN"
+ }
+ },
+ "qinq_svlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicy"
+ }
+ ],
+ "nullable": true
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "wireless_link": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedWirelessLink"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "wireless_lans": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WirelessLAN"
+ }
+ },
+ "vrf": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRF"
+ }
+ ],
+ "nullable": true
+ },
+ "l2vpn_termination": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefL2VPNTermination"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "count_ipaddresses": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "count_fhrp_groups": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "count_fhrp_groups",
+ "count_ipaddresses",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "l2vpn_termination",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "mac_address",
+ "mac_addresses",
+ "name",
+ "type",
+ "url",
+ "wireless_link"
+ ]
+ },
+ "InterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "vdcs": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterfaceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterfaceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "lag": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterfaceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Speed (Kbps)"
+ },
+ "duplex": {
+ "enum": [
+ "half",
+ "full",
+ "auto",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto",
+ "x-spec-enum-id": "368458a2b67c916b",
+ "nullable": true
+ },
+ "wwn": {
+ "type": "string",
+ "nullable": true
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only",
+ "description": "This interface is used only for out-of-band management"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ ""
+ ],
+ "type": "string",
+ "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)",
+ "x-spec-enum-id": "84129b71b974ebe5"
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ ""
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1"
+ },
+ "rf_channel": {
+ "enum": [
+ "2.4g-1-2412-22",
+ "2.4g-2-2417-22",
+ "2.4g-3-2422-22",
+ "2.4g-4-2427-22",
+ "2.4g-5-2432-22",
+ "2.4g-6-2437-22",
+ "2.4g-7-2442-22",
+ "2.4g-8-2447-22",
+ "2.4g-9-2452-22",
+ "2.4g-10-2457-22",
+ "2.4g-11-2462-22",
+ "2.4g-12-2467-22",
+ "2.4g-13-2472-22",
+ "5g-32-5160-20",
+ "5g-34-5170-40",
+ "5g-36-5180-20",
+ "5g-38-5190-40",
+ "5g-40-5200-20",
+ "5g-42-5210-80",
+ "5g-44-5220-20",
+ "5g-46-5230-40",
+ "5g-48-5240-20",
+ "5g-50-5250-160",
+ "5g-52-5260-20",
+ "5g-54-5270-40",
+ "5g-56-5280-20",
+ "5g-58-5290-80",
+ "5g-60-5300-20",
+ "5g-62-5310-40",
+ "5g-64-5320-20",
+ "5g-100-5500-20",
+ "5g-102-5510-40",
+ "5g-104-5520-20",
+ "5g-106-5530-80",
+ "5g-108-5540-20",
+ "5g-110-5550-40",
+ "5g-112-5560-20",
+ "5g-114-5570-160",
+ "5g-116-5580-20",
+ "5g-118-5590-40",
+ "5g-120-5600-20",
+ "5g-122-5610-80",
+ "5g-124-5620-20",
+ "5g-126-5630-40",
+ "5g-128-5640-20",
+ "5g-132-5660-20",
+ "5g-134-5670-40",
+ "5g-136-5680-20",
+ "5g-138-5690-80",
+ "5g-140-5700-20",
+ "5g-142-5710-40",
+ "5g-144-5720-20",
+ "5g-149-5745-20",
+ "5g-151-5755-40",
+ "5g-153-5765-20",
+ "5g-155-5775-80",
+ "5g-157-5785-20",
+ "5g-159-5795-40",
+ "5g-161-5805-20",
+ "5g-163-5815-160",
+ "5g-165-5825-20",
+ "5g-167-5835-40",
+ "5g-169-5845-20",
+ "5g-171-5855-80",
+ "5g-173-5865-20",
+ "5g-175-5875-40",
+ "5g-177-5885-20",
+ "6g-1-5955-20",
+ "6g-3-5965-40",
+ "6g-5-5975-20",
+ "6g-7-5985-80",
+ "6g-9-5995-20",
+ "6g-11-6005-40",
+ "6g-13-6015-20",
+ "6g-15-6025-160",
+ "6g-17-6035-20",
+ "6g-19-6045-40",
+ "6g-21-6055-20",
+ "6g-23-6065-80",
+ "6g-25-6075-20",
+ "6g-27-6085-40",
+ "6g-29-6095-20",
+ "6g-31-6105-320",
+ "6g-33-6115-20",
+ "6g-35-6125-40",
+ "6g-37-6135-20",
+ "6g-39-6145-80",
+ "6g-41-6155-20",
+ "6g-43-6165-40",
+ "6g-45-6175-20",
+ "6g-47-6185-160",
+ "6g-49-6195-20",
+ "6g-51-6205-40",
+ "6g-53-6215-20",
+ "6g-55-6225-80",
+ "6g-57-6235-20",
+ "6g-59-6245-40",
+ "6g-61-6255-20",
+ "6g-65-6275-20",
+ "6g-67-6285-40",
+ "6g-69-6295-20",
+ "6g-71-6305-80",
+ "6g-73-6315-20",
+ "6g-75-6325-40",
+ "6g-77-6335-20",
+ "6g-79-6345-160",
+ "6g-81-6355-20",
+ "6g-83-6365-40",
+ "6g-85-6375-20",
+ "6g-87-6385-80",
+ "6g-89-6395-20",
+ "6g-91-6405-40",
+ "6g-93-6415-20",
+ "6g-95-6425-320",
+ "6g-97-6435-20",
+ "6g-99-6445-40",
+ "6g-101-6455-20",
+ "6g-103-6465-80",
+ "6g-105-6475-20",
+ "6g-107-6485-40",
+ "6g-109-6495-20",
+ "6g-111-6505-160",
+ "6g-113-6515-20",
+ "6g-115-6525-40",
+ "6g-117-6535-20",
+ "6g-119-6545-80",
+ "6g-121-6555-20",
+ "6g-123-6565-40",
+ "6g-125-6575-20",
+ "6g-129-6595-20",
+ "6g-131-6605-40",
+ "6g-133-6615-20",
+ "6g-135-6625-80",
+ "6g-137-6635-20",
+ "6g-139-6645-40",
+ "6g-141-6655-20",
+ "6g-143-6665-160",
+ "6g-145-6675-20",
+ "6g-147-6685-40",
+ "6g-149-6695-20",
+ "6g-151-6705-80",
+ "6g-153-6715-20",
+ "6g-155-6725-40",
+ "6g-157-6735-20",
+ "6g-159-6745-320",
+ "6g-161-6755-20",
+ "6g-163-6765-40",
+ "6g-165-6775-20",
+ "6g-167-6785-80",
+ "6g-169-6795-20",
+ "6g-171-6805-40",
+ "6g-173-6815-20",
+ "6g-175-6825-160",
+ "6g-177-6835-20",
+ "6g-179-6845-40",
+ "6g-181-6855-20",
+ "6g-183-6865-80",
+ "6g-185-6875-20",
+ "6g-187-6885-40",
+ "6g-189-6895-20",
+ "6g-193-6915-20",
+ "6g-195-6925-40",
+ "6g-197-6935-20",
+ "6g-199-6945-80",
+ "6g-201-6955-20",
+ "6g-203-6965-40",
+ "6g-205-6975-20",
+ "6g-207-6985-160",
+ "6g-209-6995-20",
+ "6g-211-7005-40",
+ "6g-213-7015-20",
+ "6g-215-7025-80",
+ "6g-217-7035-20",
+ "6g-219-7045-40",
+ "6g-221-7055-20",
+ "6g-225-7075-20",
+ "6g-227-7085-40",
+ "6g-229-7095-20",
+ "6g-233-7115-20",
+ "60g-1-58320-2160",
+ "60g-2-60480-2160",
+ "60g-3-62640-2160",
+ "60g-4-64800-2160",
+ "60g-5-66960-2160",
+ "60g-6-69120-2160",
+ "60g-9-59400-4320",
+ "60g-10-61560-4320",
+ "60g-11-63720-4320",
+ "60g-12-65880-4320",
+ "60g-13-68040-4320",
+ "60g-17-60480-6480",
+ "60g-18-62640-6480",
+ "60g-19-64800-6480",
+ "60g-20-66960-6480",
+ "60g-25-61560-6480",
+ "60g-26-63720-6480",
+ "60g-27-65880-6480",
+ ""
+ ],
+ "type": "string",
+ "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)",
+ "x-spec-enum-id": "70cf66176c475063"
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ ""
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd"
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ ""
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab"
+ },
+ "rf_channel_frequency": {
+ "type": "number",
+ "format": "double",
+ "maximum": 100000,
+ "minimum": -100000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel frequency (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "rf_channel_width": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": -10000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel width (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "tx_power": {
+ "type": "integer",
+ "maximum": 127,
+ "minimum": -40,
+ "nullable": true,
+ "title": "Transmit power (dBm)"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "wireless_lans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "InterfaceTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Virtual",
+ "Bridge",
+ "Link Aggregation Group (LAG)",
+ "100BASE-FX (10/100ME)",
+ "100BASE-LFX (10/100ME)",
+ "100BASE-TX (10/100ME)",
+ "100BASE-T1 (10/100ME)",
+ "1000BASE-BX10-D (1GE BiDi Down)",
+ "1000BASE-BX10-U (1GE BiDi Up)",
+ "1000BASE-CWDM (1GE)",
+ "1000BASE-CX (1GE DAC)",
+ "1000BASE-DWDM (1GE)",
+ "1000BASE-EX (1GE)",
+ "1000BASE-LSX (1GE)",
+ "1000BASE-LX (1GE)",
+ "1000BASE-LX10/LH (1GE)",
+ "1000BASE-SX (1GE)",
+ "1000BASE-T (1GE)",
+ "1000BASE-TX (1GE)",
+ "1000BASE-ZX (1GE)",
+ "2.5GBASE-T (2.5GE)",
+ "5GBASE-T (5GE)",
+ "10GBASE-BR-D (10GE BiDi Down)",
+ "10GBASE-BR-U (10GE BiDi Up)",
+ "10GBASE-CX4 (10GE DAC)",
+ "10GBASE-ER (10GE)",
+ "10GBASE-LR (10GE)",
+ "10GBASE-LRM (10GE)",
+ "10GBASE-LX4 (10GE)",
+ "10GBASE-SR (10GE)",
+ "10GBASE-T (10GE)",
+ "10GBASE-ZR (10GE)",
+ "25GBASE-CR (25GE DAC)",
+ "25GBASE-ER (25GE)",
+ "25GBASE-LR (25GE)",
+ "25GBASE-SR (25GE)",
+ "25GBASE-T (25GE)",
+ "40GBASE-CR4 (40GE DAC)",
+ "40GBASE-ER4 (40GE)",
+ "40GBASE-FR4 (40GE)",
+ "40GBASE-LR4 (40GE)",
+ "40GBASE-SR4 (40GE)",
+ "50GBASE-CR (50GE DAC)",
+ "50GBASE-ER (50GE)",
+ "50GBASE-FR (50GE)",
+ "50GBASE-LR (50GE)",
+ "50GBASE-SR (50GE)",
+ "100GBASE-CR1 (100GE DAC)",
+ "100GBASE-CR2 (100GE DAC)",
+ "100GBASE-CR4 (100GE DAC)",
+ "100GBASE-CR10 (100GE DAC)",
+ "100GBASE-CWDM4 (100GE)",
+ "100GBASE-DR (100GE)",
+ "100GBASE-ER4 (100GE)",
+ "100GBASE-FR1 (100GE)",
+ "100GBASE-LR1 (100GE)",
+ "100GBASE-LR4 (100GE)",
+ "100GBASE-SR1 (100GE)",
+ "100GBASE-SR1.2 (100GE BiDi)",
+ "100GBASE-SR2 (100GE)",
+ "100GBASE-SR4 (100GE)",
+ "100GBASE-SR10 (100GE)",
+ "100GBASE-ZR (100GE)",
+ "200GBASE-CR2 (200GE)",
+ "200GBASE-CR4 (200GE)",
+ "200GBASE-DR4 (200GE)",
+ "200GBASE-ER4 (200GE)",
+ "200GBASE-FR4 (200GE)",
+ "200GBASE-LR4 (200GE)",
+ "200GBASE-SR2 (200GE)",
+ "200GBASE-SR4 (200GE)",
+ "200GBASE-VR2 (200GE)",
+ "400GBASE-CR4 (400GE)",
+ "400GBASE-DR4 (400GE)",
+ "400GBASE-ER8 (400GE)",
+ "400GBASE-FR4 (400GE)",
+ "400GBASE-FR8 (400GE)",
+ "400GBASE-LR4 (400GE)",
+ "400GBASE-LR8 (400GE)",
+ "400GBASE-SR4 (400GE)",
+ "400GBASE-SR4.2 (400GE BiDi)",
+ "400GBASE-SR8 (400GE)",
+ "400GBASE-SR16 (400GE)",
+ "400GBASE-VR4 (400GE)",
+ "400GBASE-ZR (400GE)",
+ "800GBASE-CR8 (800GE)",
+ "800GBASE-DR8 (800GE)",
+ "800GBASE-SR8 (800GE)",
+ "800GBASE-VR8 (800GE)",
+ "SFP (100ME)",
+ "GBIC (1GE)",
+ "SFP (1GE)",
+ "SFP+ (10GE)",
+ "XENPAK (10GE)",
+ "XFP (10GE)",
+ "X2 (10GE)",
+ "SFP28 (25GE)",
+ "QSFP+ (40GE)",
+ "QSFP28 (50GE)",
+ "SFP56 (50GE)",
+ "CFP (100GE)",
+ "CFP2 (100GE)",
+ "CFP4 (100GE)",
+ "CXP (100GE)",
+ "Cisco CPAK (100GE)",
+ "DSFP (100GE)",
+ "QSFP28 (100GE)",
+ "QSFP-DD (100GE)",
+ "SFP-DD (100GE)",
+ "CFP2 (200GE)",
+ "QSFP56 (200GE)",
+ "QSFP-DD (200GE)",
+ "QSFP112 (400GE)",
+ "QSFP-DD (400GE)",
+ "CDFP (400GE)",
+ "CFP2 (400GE)",
+ "CPF8 (400GE)",
+ "OSFP (400GE)",
+ "OSFP-RHS (400GE)",
+ "OSFP (800GE)",
+ "QSFP-DD (800GE)",
+ "1000BASE-KX (1GE)",
+ "2.5GBASE-KX (2.5GE)",
+ "5GBASE-KR (5GE)",
+ "10GBASE-KR (10GE)",
+ "10GBASE-KX4 (10GE)",
+ "25GBASE-KR (25GE)",
+ "40GBASE-KR4 (40GE)",
+ "50GBASE-KR (50GE)",
+ "100GBASE-KP4 (100GE)",
+ "100GBASE-KR2 (100GE)",
+ "100GBASE-KR4 (100GE)",
+ "IEEE 802.11a",
+ "IEEE 802.11b/g",
+ "IEEE 802.11n (Wi-Fi 4)",
+ "IEEE 802.11ac (Wi-Fi 5)",
+ "IEEE 802.11ad (WiGig)",
+ "IEEE 802.11ax (Wi-Fi 6)",
+ "IEEE 802.11ay (WiGig)",
+ "IEEE 802.11be (Wi-Fi 7)",
+ "IEEE 802.15.1 (Bluetooth)",
+ "IEEE 802.15.4 (LR-WPAN)",
+ "Other (Wireless)",
+ "GSM",
+ "CDMA",
+ "LTE",
+ "4G",
+ "5G",
+ "OC-3/STM-1",
+ "OC-12/STM-4",
+ "OC-48/STM-16",
+ "OC-192/STM-64",
+ "OC-768/STM-256",
+ "OC-1920/STM-640",
+ "OC-3840/STM-1234",
+ "SFP (1GFC)",
+ "SFP (2GFC)",
+ "SFP (4GFC)",
+ "SFP+ (8GFC)",
+ "SFP+ (16GFC)",
+ "SFP28 (32GFC)",
+ "SFP+ (32GFC)",
+ "QSFP+ (64GFC)",
+ "SFP-DD (64GFC)",
+ "SFP+ (64GFC)",
+ "QSFP28 (128GFC)",
+ "SDR (2 Gbps)",
+ "DDR (4 Gbps)",
+ "QDR (8 Gbps)",
+ "FDR10 (10 Gbps)",
+ "FDR (13.5 Gbps)",
+ "EDR (25 Gbps)",
+ "HDR (50 Gbps)",
+ "NDR (100 Gbps)",
+ "XDR (250 Gbps)",
+ "T1 (1.544 Mbps)",
+ "E1 (2.048 Mbps)",
+ "T3 (45 Mbps)",
+ "E3 (34 Mbps)",
+ "xDSL",
+ "DOCSIS",
+ "MoCA",
+ "BPON (622 Mbps / 155 Mbps)",
+ "EPON (1 Gbps)",
+ "10G-EPON (10 Gbps)",
+ "GPON (2.5 Gbps / 1.25 Gbps)",
+ "XG-PON (10 Gbps / 2.5 Gbps)",
+ "XGS-PON (10 Gbps)",
+ "NG-PON2 (TWDM-PON) (4x10 Gbps)",
+ "25G-PON (25 Gbps)",
+ "50G-PON (50 Gbps)",
+ "Cisco StackWise",
+ "Cisco StackWise Plus",
+ "Cisco FlexStack",
+ "Cisco FlexStack Plus",
+ "Cisco StackWise-80",
+ "Cisco StackWise-160",
+ "Cisco StackWise-320",
+ "Cisco StackWise-480",
+ "Cisco StackWise-1T",
+ "Juniper VCP",
+ "Extreme SummitStack",
+ "Extreme SummitStack-128",
+ "Extreme SummitStack-256",
+ "Extreme SummitStack-512",
+ "Other"
+ ]
+ }
+ }
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterfaceTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "poe_mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "PD",
+ "PSE"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "poe_type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "802.3af (Type 1)",
+ "802.3at (Type 2)",
+ "802.3bt (Type 3)",
+ "802.3bt (Type 4)",
+ "Passive 24V (2-pair)",
+ "Passive 24V (4-pair)",
+ "Passive 48V (2-pair)",
+ "Passive 48V (4-pair)"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "rf_role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Access point",
+ "Station"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "type",
+ "url"
+ ]
+ },
+ "InterfaceTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedInterfaceTemplateRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd",
+ "nullable": true
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab",
+ "nullable": true
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1",
+ "nullable": true
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "InventoryItem": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Staged",
+ "Failed",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRole"
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this item",
+ "maxLength": 50
+ },
+ "discovered": {
+ "type": "boolean",
+ "description": "This item was automatically discovered"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "component": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "component",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "InventoryItemRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this item",
+ "maxLength": 50
+ },
+ "discovered": {
+ "type": "boolean",
+ "description": "This item was automatically discovered"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "InventoryItemRole": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "inventoryitem_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "inventoryitem_count",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "InventoryItemRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "InventoryItemTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRole"
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "component": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "component",
+ "created",
+ "device_type",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "InventoryItemTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ }
+ },
+ "required": [
+ "device_type",
+ "name"
+ ]
+ },
+ "Job": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "object": {
+ "nullable": true,
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "pending",
+ "scheduled",
+ "running",
+ "completed",
+ "errored",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `pending` - Pending\n* `scheduled` - Scheduled\n* `running` - Running\n* `completed` - Completed\n* `errored` - Errored\n* `failed` - Failed",
+ "x-spec-enum-id": "b3049df95b935eab"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Pending",
+ "Scheduled",
+ "Running",
+ "Completed",
+ "Errored",
+ "Failed"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "scheduled": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "interval": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Recurrence interval (in minutes)"
+ },
+ "started": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "completed": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "user": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefUser"
+ }
+ ],
+ "readOnly": true
+ },
+ "data": {
+ "nullable": true
+ },
+ "error": {
+ "type": "string",
+ "readOnly": true
+ },
+ "job_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "log_entries": {
+ "type": "array",
+ "items": {}
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "error",
+ "id",
+ "job_id",
+ "name",
+ "object",
+ "object_type",
+ "status",
+ "url",
+ "user"
+ ]
+ },
+ "JournalEntry": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "assigned_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "created_by": {
+ "type": "integer",
+ "nullable": true
+ },
+ "kind": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "info",
+ "success",
+ "warning",
+ "danger"
+ ],
+ "type": "string",
+ "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger",
+ "x-spec-enum-id": "6f65abe0aab2c78c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Info",
+ "Success",
+ "Warning",
+ "Danger"
+ ]
+ }
+ }
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "assigned_object",
+ "assigned_object_id",
+ "assigned_object_type",
+ "comments",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "url"
+ ]
+ },
+ "JournalEntryRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "created_by": {
+ "type": "integer",
+ "nullable": true
+ },
+ "kind": {
+ "enum": [
+ "info",
+ "success",
+ "warning",
+ "danger"
+ ],
+ "type": "string",
+ "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger",
+ "x-spec-enum-id": "6f65abe0aab2c78c"
+ },
+ "comments": {
+ "type": "string",
+ "minLength": 1
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "assigned_object_id",
+ "assigned_object_type",
+ "comments"
+ ]
+ },
+ "L2VPN": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "identifier": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": -9223372036854775808,
+ "format": "int64",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "vpws",
+ "vpls",
+ "vxlan",
+ "vxlan-evpn",
+ "mpls-evpn",
+ "pbb-evpn",
+ "evpn-vpws",
+ "epl",
+ "evpl",
+ "ep-lan",
+ "evp-lan",
+ "ep-tree",
+ "evp-tree",
+ "spb"
+ ],
+ "type": "string",
+ "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB",
+ "x-spec-enum-id": "0a46f8056d717efc"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "VPWS",
+ "VPLS",
+ "VXLAN",
+ "VXLAN-EVPN",
+ "MPLS EVPN",
+ "PBB EVPN",
+ "EVPN VPWS",
+ "EPL",
+ "EVPL",
+ "Ethernet Private LAN",
+ "Ethernet Virtual Private LAN",
+ "Ethernet Private Tree",
+ "Ethernet Virtual Private Tree",
+ "SPB"
+ ]
+ }
+ }
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "8b9dc8efc7c3d5b0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Planned",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RouteTarget"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RouteTarget"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "L2VPNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "identifier": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": -9223372036854775808,
+ "format": "int64",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "type": {
+ "enum": [
+ "vpws",
+ "vpls",
+ "vxlan",
+ "vxlan-evpn",
+ "mpls-evpn",
+ "pbb-evpn",
+ "evpn-vpws",
+ "epl",
+ "evpl",
+ "ep-lan",
+ "evp-lan",
+ "ep-tree",
+ "evp-tree",
+ "spb"
+ ],
+ "type": "string",
+ "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB",
+ "x-spec-enum-id": "0a46f8056d717efc"
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "8b9dc8efc7c3d5b0"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "L2VPNTermination": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "l2vpn": {
+ "$ref": "#/components/schemas/BriefL2VPN"
+ },
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "assigned_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "assigned_object",
+ "assigned_object_id",
+ "assigned_object_type",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "l2vpn",
+ "last_updated",
+ "url"
+ ]
+ },
+ "L2VPNTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "l2vpn": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefL2VPNRequest"
+ }
+ ]
+ },
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "assigned_object_id",
+ "assigned_object_type",
+ "l2vpn"
+ ]
+ },
+ "Location": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "site": {
+ "$ref": "#/components/schemas/BriefSite"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedLocation"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Planned",
+ "Staging",
+ "Active",
+ "Decommissioning",
+ "Retired"
+ ]
+ }
+ }
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "rack_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "device_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "rack_count",
+ "site",
+ "slug",
+ "url"
+ ]
+ },
+ "LocationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedLocationRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "site",
+ "slug"
+ ]
+ },
+ "MACAddress": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "mac_address": {
+ "type": "string"
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "assigned_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "assigned_object",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "mac_address",
+ "url"
+ ]
+ },
+ "MACAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "mac_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "mac_address"
+ ]
+ },
+ "Manufacturer": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "devicetype_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "moduletype_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "inventoryitem_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "platform_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "devicetype_count",
+ "display",
+ "display_url",
+ "id",
+ "inventoryitem_count",
+ "last_updated",
+ "moduletype_count",
+ "name",
+ "platform_count",
+ "slug",
+ "url"
+ ]
+ },
+ "ManufacturerRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "Module": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module_bay": {
+ "$ref": "#/components/schemas/NestedModuleBay"
+ },
+ "module_type": {
+ "$ref": "#/components/schemas/BriefModuleType"
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Staged",
+ "Failed",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "module_bay",
+ "module_type",
+ "url"
+ ]
+ },
+ "ModuleBay": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "installed_module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ModuleBayRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "installed_module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "ModuleBayTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ModuleBayTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "ModuleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module_bay": {
+ "$ref": "#/components/schemas/NestedModuleBayRequest"
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "module_bay",
+ "module_type"
+ ]
+ },
+ "ModuleType": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "profile": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeProfile"
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "airflow": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "passive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive",
+ "x-spec-enum-id": "5ad4e700c656b09d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front to rear",
+ "Rear to front",
+ "Left to right",
+ "Right to left",
+ "Side to rear",
+ "Passive"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Kilograms",
+ "Grams",
+ "Pounds",
+ "Ounces"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "attributes": {
+ "nullable": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "module_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "manufacturer",
+ "model",
+ "module_count",
+ "url"
+ ]
+ },
+ "ModuleTypeProfile": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "schema": {
+ "nullable": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "ModuleTypeProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "schema": {
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "ModuleTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "passive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive",
+ "x-spec-enum-id": "5ad4e700c656b09d",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "attributes": {
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "manufacturer",
+ "model"
+ ]
+ },
+ "NestedContactGroup": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedContactGroupRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedDevice": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "url"
+ ]
+ },
+ "NestedDeviceRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ }
+ }
+ },
+ "NestedDeviceRole": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedDeviceRoleRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedGroup": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 150
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedIPAddress": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "family": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "address": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "address",
+ "display",
+ "display_url",
+ "family",
+ "id",
+ "url"
+ ]
+ },
+ "NestedIPAddressRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "address": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "address"
+ ]
+ },
+ "NestedInterface": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDevice"
+ }
+ ],
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "cable": {
+ "type": "integer",
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedInterfaceRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "cable": {
+ "type": "integer",
+ "nullable": true
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedInterfaceTemplate": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "display",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedInterfaceTemplateRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedLocation": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedLocationRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedModuleBay": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedModuleBayRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedPlatform": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedPlatformRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedProviderAccount": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "account": {
+ "type": "string",
+ "title": "Account ID",
+ "maxLength": 100
+ }
+ },
+ "required": [
+ "account",
+ "display",
+ "display_url",
+ "id",
+ "url"
+ ]
+ },
+ "NestedRegion": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedRegionRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedSiteGroup": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedSiteGroupRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedTag": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "pattern": "^[-\\w]+$",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedTagRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[-\\w]+$",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedTenantGroup": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedTenantGroupRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedUser": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "username": {
+ "type": "string",
+ "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+ "pattern": "^[\\w.@+-]+$",
+ "maxLength": 150
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "url",
+ "username"
+ ]
+ },
+ "NestedVLAN": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "display",
+ "id",
+ "name",
+ "url",
+ "vid"
+ ]
+ },
+ "NestedVLANRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name",
+ "vid"
+ ]
+ },
+ "NestedVMInterface": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "virtual_machine": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVirtualMachine"
+ }
+ ],
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url",
+ "virtual_machine"
+ ]
+ },
+ "NestedVMInterfaceRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedVirtualMachine": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NestedVirtualMachineRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NestedWirelessLANGroup": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "NestedWirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "NestedWirelessLink": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "url"
+ ]
+ },
+ "NestedWirelessLinkRequest": {
+ "type": "object",
+ "description": "Represents an object related through a ForeignKey field. On write, it accepts a primary key (PK) value or a\ndictionary of attributes which can be used to uniquely identify the related object. This class should be\nsubclassed to return a full representation of the related object on read.",
+ "properties": {
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ }
+ }
+ },
+ "Notification": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "user": {
+ "$ref": "#/components/schemas/BriefUser"
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "read": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "event_type": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2",
+ "title": "Event"
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "event_type",
+ "id",
+ "object",
+ "object_id",
+ "object_type",
+ "url",
+ "user"
+ ]
+ },
+ "NotificationGroup": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "NotificationGroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "NotificationRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "read": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "event_type": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2",
+ "title": "Event"
+ }
+ },
+ "required": [
+ "event_type",
+ "object_id",
+ "object_type",
+ "user"
+ ]
+ },
+ "ObjectChange": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "time": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "user": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefUser"
+ }
+ ],
+ "readOnly": true
+ },
+ "user_name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "request_id": {
+ "type": "string",
+ "format": "uuid",
+ "readOnly": true
+ },
+ "action": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "create",
+ "update",
+ "delete"
+ ],
+ "type": "string",
+ "description": "* `create` - Created\n* `update` - Updated\n* `delete` - Deleted",
+ "x-spec-enum-id": "36ce3d432464454d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Created",
+ "Updated",
+ "Deleted"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "changed_object_type": {
+ "type": "string",
+ "readOnly": true
+ },
+ "changed_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "changed_object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "object_repr": {
+ "type": "string",
+ "readOnly": true
+ },
+ "message": {
+ "type": "string",
+ "readOnly": true
+ },
+ "prechange_data": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "postchange_data": {
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "action",
+ "changed_object",
+ "changed_object_id",
+ "changed_object_type",
+ "display",
+ "display_url",
+ "id",
+ "message",
+ "object_repr",
+ "postchange_data",
+ "prechange_data",
+ "request_id",
+ "time",
+ "url",
+ "user",
+ "user_name"
+ ]
+ },
+ "ObjectPermission": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 30
+ },
+ "description": "The list of actions granted by this permission"
+ },
+ "constraints": {
+ "nullable": true,
+ "description": "Queryset filter matching the applicable objects of the selected type(s)"
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedGroup"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedUser"
+ }
+ }
+ },
+ "required": [
+ "actions",
+ "display",
+ "display_url",
+ "id",
+ "name",
+ "object_types",
+ "url"
+ ]
+ },
+ "ObjectPermissionRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 30
+ },
+ "description": "The list of actions granted by this permission"
+ },
+ "constraints": {
+ "nullable": true,
+ "description": "Queryset filter matching the applicable objects of the selected type(s)"
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "actions",
+ "name",
+ "object_types"
+ ]
+ },
+ "ObjectType": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "app_label": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "app_name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "model": {
+ "type": "string",
+ "title": "Python model class name",
+ "maxLength": 100
+ },
+ "model_name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "model_name_plural": {
+ "type": "string",
+ "readOnly": true
+ },
+ "public": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "features": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "readOnly": true
+ },
+ "is_plugin_model": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "rest_api_endpoint": {
+ "type": "string",
+ "readOnly": true
+ },
+ "description": {
+ "type": "string",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "app_label",
+ "app_name",
+ "description",
+ "display",
+ "features",
+ "id",
+ "is_plugin_model",
+ "model",
+ "model_name",
+ "model_name_plural",
+ "public",
+ "rest_api_endpoint",
+ "url"
+ ]
+ },
+ "Owner": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user_groups": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "group",
+ "id",
+ "name",
+ "url"
+ ]
+ },
+ "OwnerGroup": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "member_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "member_count",
+ "name",
+ "url"
+ ]
+ },
+ "OwnerGroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "OwnerRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user_groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "group",
+ "name"
+ ]
+ },
+ "PaginatedASNList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ASN"
+ }
+ }
+ }
+ },
+ "PaginatedASNRangeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ASNRange"
+ }
+ }
+ }
+ },
+ "PaginatedAggregateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Aggregate"
+ }
+ }
+ }
+ },
+ "PaginatedBookmarkList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Bookmark"
+ }
+ }
+ }
+ },
+ "PaginatedCableList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Cable"
+ }
+ }
+ }
+ },
+ "PaginatedCableTerminationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CableTermination"
+ }
+ }
+ }
+ },
+ "PaginatedCircuitGroupAssignmentList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CircuitGroupAssignment"
+ }
+ }
+ }
+ },
+ "PaginatedCircuitGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CircuitGroup"
+ }
+ }
+ }
+ },
+ "PaginatedCircuitList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Circuit"
+ }
+ }
+ }
+ },
+ "PaginatedCircuitTerminationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CircuitTermination"
+ }
+ }
+ }
+ },
+ "PaginatedCircuitTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CircuitType"
+ }
+ }
+ }
+ },
+ "PaginatedClusterGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ClusterGroup"
+ }
+ }
+ }
+ },
+ "PaginatedClusterList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Cluster"
+ }
+ }
+ }
+ },
+ "PaginatedClusterTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ClusterType"
+ }
+ }
+ }
+ },
+ "PaginatedConfigContextList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConfigContext"
+ }
+ }
+ }
+ },
+ "PaginatedConfigContextProfileList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConfigContextProfile"
+ }
+ }
+ }
+ },
+ "PaginatedConfigTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConfigTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedConsolePortList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConsolePort"
+ }
+ }
+ }
+ },
+ "PaginatedConsolePortTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConsolePortTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedConsoleServerPortList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConsoleServerPort"
+ }
+ }
+ }
+ },
+ "PaginatedConsoleServerPortTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConsoleServerPortTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedContactAssignmentList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ContactAssignment"
+ }
+ }
+ }
+ },
+ "PaginatedContactGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ContactGroup"
+ }
+ }
+ }
+ },
+ "PaginatedContactList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Contact"
+ }
+ }
+ }
+ },
+ "PaginatedContactRoleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ContactRole"
+ }
+ }
+ }
+ },
+ "PaginatedCustomFieldChoiceSetList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CustomFieldChoiceSet"
+ }
+ }
+ }
+ },
+ "PaginatedCustomFieldList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CustomField"
+ }
+ }
+ }
+ },
+ "PaginatedCustomLinkList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CustomLink"
+ }
+ }
+ }
+ },
+ "PaginatedDataFileList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DataFile"
+ }
+ }
+ }
+ },
+ "PaginatedDataSourceList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DataSource"
+ }
+ }
+ }
+ },
+ "PaginatedDeviceBayList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DeviceBay"
+ }
+ }
+ }
+ },
+ "PaginatedDeviceBayTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DeviceBayTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedDeviceRoleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DeviceRole"
+ }
+ }
+ }
+ },
+ "PaginatedDeviceTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DeviceType"
+ }
+ }
+ }
+ },
+ "PaginatedDeviceWithConfigContextList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DeviceWithConfigContext"
+ }
+ }
+ }
+ },
+ "PaginatedEventRuleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/EventRule"
+ }
+ }
+ }
+ },
+ "PaginatedExportTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ExportTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedFHRPGroupAssignmentList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FHRPGroupAssignment"
+ }
+ }
+ }
+ },
+ "PaginatedFHRPGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FHRPGroup"
+ }
+ }
+ }
+ },
+ "PaginatedFrontPortList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPort"
+ }
+ }
+ }
+ },
+ "PaginatedFrontPortTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ }
+ }
+ },
+ "PaginatedIKEPolicyList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IKEPolicy"
+ }
+ }
+ }
+ },
+ "PaginatedIKEProposalList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IKEProposal"
+ }
+ }
+ }
+ },
+ "PaginatedIPAddressList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPAddress"
+ }
+ }
+ }
+ },
+ "PaginatedIPRangeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPRange"
+ }
+ }
+ }
+ },
+ "PaginatedIPSecPolicyList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPSecPolicy"
+ }
+ }
+ }
+ },
+ "PaginatedIPSecProfileList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPSecProfile"
+ }
+ }
+ }
+ },
+ "PaginatedIPSecProposalList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPSecProposal"
+ }
+ }
+ }
+ },
+ "PaginatedImageAttachmentList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ImageAttachment"
+ }
+ }
+ }
+ },
+ "PaginatedInterfaceList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Interface"
+ }
+ }
+ }
+ },
+ "PaginatedInterfaceTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/InterfaceTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedInventoryItemList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/InventoryItem"
+ }
+ }
+ }
+ },
+ "PaginatedInventoryItemRoleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/InventoryItemRole"
+ }
+ }
+ }
+ },
+ "PaginatedInventoryItemTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/InventoryItemTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedJobList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Job"
+ }
+ }
+ }
+ },
+ "PaginatedJournalEntryList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/JournalEntry"
+ }
+ }
+ }
+ },
+ "PaginatedL2VPNList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/L2VPN"
+ }
+ }
+ }
+ },
+ "PaginatedL2VPNTerminationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/L2VPNTermination"
+ }
+ }
+ }
+ },
+ "PaginatedLocationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Location"
+ }
+ }
+ }
+ },
+ "PaginatedMACAddressList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/MACAddress"
+ }
+ }
+ }
+ },
+ "PaginatedManufacturerList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Manufacturer"
+ }
+ }
+ }
+ },
+ "PaginatedModuleBayList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ModuleBay"
+ }
+ }
+ }
+ },
+ "PaginatedModuleBayTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ModuleBayTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedModuleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Module"
+ }
+ }
+ }
+ },
+ "PaginatedModuleTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ModuleType"
+ }
+ }
+ }
+ },
+ "PaginatedModuleTypeProfileList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ModuleTypeProfile"
+ }
+ }
+ }
+ },
+ "PaginatedNotificationGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NotificationGroup"
+ }
+ }
+ }
+ },
+ "PaginatedNotificationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Notification"
+ }
+ }
+ }
+ },
+ "PaginatedObjectChangeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ObjectChange"
+ }
+ }
+ }
+ },
+ "PaginatedObjectPermissionList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ObjectPermission"
+ }
+ }
+ }
+ },
+ "PaginatedObjectTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ObjectType"
+ }
+ }
+ }
+ },
+ "PaginatedOwnerGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/OwnerGroup"
+ }
+ }
+ }
+ },
+ "PaginatedOwnerList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Owner"
+ }
+ }
+ }
+ },
+ "PaginatedPlatformList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Platform"
+ }
+ }
+ }
+ },
+ "PaginatedPowerFeedList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerFeed"
+ }
+ }
+ }
+ },
+ "PaginatedPowerOutletList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerOutlet"
+ }
+ }
+ }
+ },
+ "PaginatedPowerOutletTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerOutletTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedPowerPanelList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerPanel"
+ }
+ }
+ }
+ },
+ "PaginatedPowerPortList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerPort"
+ }
+ }
+ }
+ },
+ "PaginatedPowerPortTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PowerPortTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedPrefixList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prefix"
+ }
+ }
+ }
+ },
+ "PaginatedProviderAccountList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProviderAccount"
+ }
+ }
+ }
+ },
+ "PaginatedProviderList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Provider"
+ }
+ }
+ }
+ },
+ "PaginatedProviderNetworkList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ProviderNetwork"
+ }
+ }
+ }
+ },
+ "PaginatedRIRList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RIR"
+ }
+ }
+ }
+ },
+ "PaginatedRackList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Rack"
+ }
+ }
+ }
+ },
+ "PaginatedRackReservationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RackReservation"
+ }
+ }
+ }
+ },
+ "PaginatedRackRoleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RackRole"
+ }
+ }
+ }
+ },
+ "PaginatedRackTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RackType"
+ }
+ }
+ }
+ },
+ "PaginatedRackUnitList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RackUnit"
+ }
+ }
+ }
+ },
+ "PaginatedRearPortList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPort"
+ }
+ }
+ }
+ },
+ "PaginatedRearPortTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedRegionList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Region"
+ }
+ }
+ }
+ },
+ "PaginatedRoleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Role"
+ }
+ }
+ }
+ },
+ "PaginatedRouteTargetList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RouteTarget"
+ }
+ }
+ }
+ },
+ "PaginatedSavedFilterList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SavedFilter"
+ }
+ }
+ }
+ },
+ "PaginatedScriptList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Script"
+ }
+ }
+ }
+ },
+ "PaginatedServiceList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Service"
+ }
+ }
+ }
+ },
+ "PaginatedServiceTemplateList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ServiceTemplate"
+ }
+ }
+ }
+ },
+ "PaginatedSiteGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SiteGroup"
+ }
+ }
+ }
+ },
+ "PaginatedSiteList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Site"
+ }
+ }
+ }
+ },
+ "PaginatedSubscriptionList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Subscription"
+ }
+ }
+ }
+ },
+ "PaginatedTableConfigList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TableConfig"
+ }
+ }
+ }
+ },
+ "PaginatedTagList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Tag"
+ }
+ }
+ }
+ },
+ "PaginatedTaggedItemList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TaggedItem"
+ }
+ }
+ }
+ },
+ "PaginatedTenantGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TenantGroup"
+ }
+ }
+ }
+ },
+ "PaginatedTenantList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Tenant"
+ }
+ }
+ }
+ },
+ "PaginatedTokenList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Token"
+ }
+ }
+ }
+ },
+ "PaginatedTunnelGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TunnelGroup"
+ }
+ }
+ }
+ },
+ "PaginatedTunnelList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Tunnel"
+ }
+ }
+ }
+ },
+ "PaginatedTunnelTerminationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TunnelTermination"
+ }
+ }
+ }
+ },
+ "PaginatedUserList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "PaginatedVLANGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLANGroup"
+ }
+ }
+ }
+ },
+ "PaginatedVLANList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLAN"
+ }
+ }
+ }
+ },
+ "PaginatedVLANTranslationPolicyList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLANTranslationPolicy"
+ }
+ }
+ }
+ },
+ "PaginatedVLANTranslationRuleList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLANTranslationRule"
+ }
+ }
+ }
+ },
+ "PaginatedVMInterfaceList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VMInterface"
+ }
+ }
+ }
+ },
+ "PaginatedVRFList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VRF"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualChassisList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualChassis"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualCircuitList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualCircuit"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualCircuitTerminationList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualCircuitTermination"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualCircuitTypeList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualCircuitType"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualDeviceContextList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualDeviceContext"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualDiskList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualDisk"
+ }
+ }
+ }
+ },
+ "PaginatedVirtualMachineWithConfigContextList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VirtualMachineWithConfigContext"
+ }
+ }
+ }
+ },
+ "PaginatedWebhookList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Webhook"
+ }
+ }
+ }
+ },
+ "PaginatedWirelessLANGroupList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WirelessLANGroup"
+ }
+ }
+ }
+ },
+ "PaginatedWirelessLANList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WirelessLAN"
+ }
+ }
+ }
+ },
+ "PaginatedWirelessLinkList": {
+ "type": "object",
+ "required": [
+ "count",
+ "results"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "example": 123
+ },
+ "next": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=400&limit=100"
+ },
+ "previous": {
+ "type": "string",
+ "nullable": true,
+ "format": "uri",
+ "example": "http://api.example.org/accounts/?offset=200&limit=100"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WirelessLink"
+ }
+ }
+ }
+ },
+ "PatchedASNRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "rir": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRIRRequest"
+ }
+ ]
+ },
+ "start": {
+ "type": "integer",
+ "maximum": 4294967295,
+ "minimum": 1,
+ "format": "int64"
+ },
+ "end": {
+ "type": "integer",
+ "maximum": 4294967295,
+ "minimum": 1,
+ "format": "int64"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedASNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "asn": {
+ "type": "integer",
+ "maximum": 4294967295,
+ "minimum": 1,
+ "format": "int64",
+ "description": "16- or 32-bit autonomous system number"
+ },
+ "rir": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRIRRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedBookmarkRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ }
+ },
+ "PatchedCircuitGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "circuit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefCircuitRequest"
+ }
+ ]
+ },
+ "term_side": {
+ "enum": [
+ "A",
+ "Z"
+ ],
+ "type": "string",
+ "description": "* `A` - A\n* `Z` - Z",
+ "x-spec-enum-id": "95b8fcc737f355d0",
+ "title": "Termination side"
+ },
+ "termination_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "termination_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "port_speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Port speed (Kbps)",
+ "description": "Physical circuit speed"
+ },
+ "upstream_speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Upstream speed (Kbps)",
+ "description": "Upstream speed, if different from port speed"
+ },
+ "xconnect_id": {
+ "type": "string",
+ "title": "Cross-connect ID",
+ "description": "ID of the local cross-connect",
+ "maxLength": 50
+ },
+ "pp_info": {
+ "type": "string",
+ "title": "Patch panel/port(s)",
+ "description": "Patch panel ID and port number(s)",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedClusterGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedClusterTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedConfigContextProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "schema": {
+ "nullable": true,
+ "description": "A JSON schema specifying the structure of the context data for this profile"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ }
+ }
+ },
+ "PatchedConfigContextRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigContextProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "is_active": {
+ "type": "boolean"
+ },
+ "regions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "site_groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "sites": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "locations": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "device_types": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "platforms": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "cluster_types": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "cluster_groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "clusters": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tenant_groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tenants": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ },
+ "data": {}
+ }
+ },
+ "PatchedConfigTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "environment_params": {
+ "nullable": true,
+ "title": "Environment parameters",
+ "description": "Any additional parameters to pass when constructing the Jinja environment"
+ },
+ "template_code": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja template code."
+ },
+ "mime_type": {
+ "type": "string",
+ "description": "Defaults to text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ },
+ "auto_sync_enabled": {
+ "type": "boolean",
+ "description": "Enable automatic synchronization of data when the data file is updated"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ }
+ },
+ "PatchedContactRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "title": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "phone": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "maxLength": 254
+ },
+ "address": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "link": {
+ "type": "string",
+ "format": "uri",
+ "maxLength": 200
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedContactRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedCustomLinkRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "link_text": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja2 template code for link text"
+ },
+ "link_url": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja2 template code for link URL"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Links with the same group will appear as a dropdown menu",
+ "maxLength": 50
+ },
+ "button_class": {
+ "enum": [
+ "default",
+ "blue",
+ "indigo",
+ "purple",
+ "pink",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "teal",
+ "cyan",
+ "gray",
+ "black",
+ "white",
+ "ghost-dark"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "5e54b3bd086685ce",
+ "description": "The class of the first link in a group will be used for the dropdown button\n\n* `default` - Default\n* `blue` - Blue\n* `indigo` - Indigo\n* `purple` - Purple\n* `pink` - Pink\n* `red` - Red\n* `orange` - Orange\n* `yellow` - Yellow\n* `green` - Green\n* `teal` - Teal\n* `cyan` - Cyan\n* `gray` - Gray\n* `black` - Black\n* `white` - White\n* `ghost-dark` - Link"
+ },
+ "new_window": {
+ "type": "boolean",
+ "description": "Force link to open in a new window"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ }
+ },
+ "PatchedDashboardRequest": {
+ "type": "object",
+ "properties": {
+ "layout": {},
+ "config": {}
+ }
+ },
+ "PatchedDeviceBayRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedDeviceBayTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedExportTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "environment_params": {
+ "nullable": true,
+ "title": "Environment parameters",
+ "description": "Any additional parameters to pass when constructing the Jinja environment"
+ },
+ "template_code": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Jinja template code."
+ },
+ "mime_type": {
+ "type": "string",
+ "description": "Defaults to text/plain; charset=utf-8",
+ "maxLength": 50
+ },
+ "file_name": {
+ "type": "string",
+ "description": "Filename to give to the rendered export file",
+ "maxLength": 200
+ },
+ "file_extension": {
+ "type": "string",
+ "description": "Extension to append to the rendered filename",
+ "maxLength": 15
+ },
+ "as_attachment": {
+ "type": "boolean",
+ "description": "Download file as attachment"
+ },
+ "data_source": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDataSourceRequest"
+ }
+ ]
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ }
+ },
+ "PatchedFHRPGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefFHRPGroupRequest"
+ }
+ ]
+ },
+ "interface_type": {
+ "type": "string"
+ },
+ "interface_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0
+ }
+ }
+ },
+ "PatchedFHRPGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "vrrp2",
+ "vrrp3",
+ "carp",
+ "clusterxl",
+ "hsrp",
+ "glbp",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `vrrp2` - VRRPv2\n* `vrrp3` - VRRPv3\n* `carp` - CARP\n* `clusterxl` - ClusterXL\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `other` - Other",
+ "x-spec-enum-id": "98de93c9f65d1c65"
+ },
+ "group_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "auth_type": {
+ "enum": [
+ "plaintext",
+ "md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `plaintext` - Plaintext\n* `md5` - MD5",
+ "x-spec-enum-id": "565396e386e1542a",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_key": {
+ "type": "string",
+ "title": "Authentication key",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedGroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "PatchedImageAttachmentRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "image": {
+ "type": "string",
+ "format": "binary"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedInventoryItemRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedInventoryItemTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ }
+ }
+ },
+ "PatchedL2VPNTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "l2vpn": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefL2VPNRequest"
+ }
+ ]
+ },
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedMACAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "mac_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedManufacturerRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedModuleBayRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "installed_module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedModuleBayTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedModuleTypeProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "schema": {
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedNotificationGroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "PatchedNotificationRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "read": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "event_type": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2",
+ "title": "Event"
+ }
+ }
+ },
+ "PatchedObjectPermissionRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 30
+ },
+ "description": "The list of actions granted by this permission"
+ },
+ "constraints": {
+ "nullable": true,
+ "description": "Queryset filter matching the applicable objects of the selected type(s)"
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "PatchedOwnerGroupRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedOwnerRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user_groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "PatchedPowerPanelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedProviderAccountRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "default": "",
+ "maxLength": 100
+ },
+ "account": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Account ID",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedProviderNetworkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "service_id": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedProviderRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full name of the provider",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "accounts": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedRIRRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "is_private": {
+ "type": "boolean",
+ "title": "Private",
+ "description": "IP space managed by this RIR is considered private"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedRackRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedRouteTargetRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Route target value (formatted in accordance with RFC 4360)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedSavedFilterRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "parameters": {},
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ }
+ },
+ "PatchedScriptInputRequest": {
+ "type": "object",
+ "properties": {
+ "data": {},
+ "commit": {
+ "type": "boolean"
+ },
+ "schedule_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "interval": {
+ "type": "integer",
+ "nullable": true
+ }
+ }
+ },
+ "PatchedSubscriptionRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ }
+ },
+ "PatchedTableConfigRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "table": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ }
+ },
+ "ordering": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "nullable": true
+ }
+ }
+ },
+ "PatchedTagRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[-\\w]+$",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "PatchedTenantRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedTokenRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - v1\n* `2` - v2",
+ "x-spec-enum-id": "b5df70f0bffd12cb",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "expires": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "last_used": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Disable to temporarily revoke this token without deleting it."
+ },
+ "write_enabled": {
+ "type": "boolean",
+ "description": "Permit create/update/delete operations using this key"
+ },
+ "pepper_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "ID of the cryptographic pepper used to hash the token (v2 only)"
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "PatchedTunnelGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedUserRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "username": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+ "pattern": "^[\\w.@+-]+$",
+ "maxLength": 150
+ },
+ "password": {
+ "type": "string",
+ "writeOnly": true,
+ "minLength": 1,
+ "maxLength": 128
+ },
+ "first_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "last_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "title": "Email address",
+ "maxLength": 254
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Active",
+ "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
+ },
+ "date_joined": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_login": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "PatchedVLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "vid_ranges": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IntegerRangeRequest"
+ }
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedVLANTranslationPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedVLANTranslationRuleRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "policy": {
+ "type": "integer"
+ },
+ "local_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Local VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "remote_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Remote VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedVRFRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "rd": {
+ "type": "string",
+ "nullable": true,
+ "title": "Route distinguisher",
+ "description": "Unique route distinguisher (as defined in RFC 4364)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "enforce_unique": {
+ "type": "boolean",
+ "title": "Enforce unique space",
+ "description": "Prevent duplicate prefixes/IP addresses within this VRF"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedVirtualCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedVirtualDiskRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_machine": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "size": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "title": "Size (MB)"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWebhookRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "payload_url": {
+ "type": "string",
+ "minLength": 1,
+ "title": "URL",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "maxLength": 500
+ },
+ "http_method": {
+ "enum": [
+ "GET",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE"
+ ],
+ "type": "string",
+ "description": "* `GET` - GET\n* `POST` - POST\n* `PUT` - PUT\n* `PATCH` - PATCH\n* `DELETE` - DELETE",
+ "x-spec-enum-id": "867bf764d3b1eeaa"
+ },
+ "http_content_type": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The complete list of official content types is available here.",
+ "maxLength": 100
+ },
+ "additional_headers": {
+ "type": "string",
+ "description": "User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. Headers should be defined in the format Name: Value. Jinja2 template processing is supported with the same context as the request body (below)."
+ },
+ "body_template": {
+ "type": "string",
+ "description": "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be included. Available context data includes: event, model, timestamp, username, request_id, and data."
+ },
+ "secret": {
+ "type": "string",
+ "description": "When provided, the request will include a X-Hook-Signature header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.",
+ "maxLength": 255
+ },
+ "ssl_verification": {
+ "type": "boolean",
+ "description": "Enable SSL certificate verification. Disable with caution!"
+ },
+ "ca_file_path": {
+ "type": "string",
+ "nullable": true,
+ "description": "The specific CA certificate file to use for SSL verification. Leave blank to use the system defaults.",
+ "maxLength": 4096
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ }
+ },
+ "PatchedWritableAggregateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "minLength": 1
+ },
+ "rir": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRIRRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "date_added": {
+ "type": "string",
+ "format": "date",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableCableRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "type": {
+ "enum": [
+ "cat3",
+ "cat5",
+ "cat5e",
+ "cat6",
+ "cat6a",
+ "cat7",
+ "cat7a",
+ "cat8",
+ "mrj21-trunk",
+ "dac-active",
+ "dac-passive",
+ "coaxial",
+ "rg-6",
+ "rg-8",
+ "rg-11",
+ "rg-59",
+ "rg-62",
+ "rg-213",
+ "lmr-100",
+ "lmr-200",
+ "lmr-400",
+ "mmf",
+ "mmf-om1",
+ "mmf-om2",
+ "mmf-om3",
+ "mmf-om4",
+ "mmf-om5",
+ "smf",
+ "smf-os1",
+ "smf-os2",
+ "aoc",
+ "power",
+ "usb",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `rg-6` - RG-6\n* `rg-8` - RG-8\n* `rg-11` - RG-11\n* `rg-59` - RG-59\n* `rg-62` - RG-62\n* `rg-213` - RG-213\n* `lmr-100` - LMR-100\n* `lmr-200` - LMR-200\n* `lmr-400` - LMR-400\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB",
+ "x-spec-enum-id": "3d4d8d7ae24f7be8",
+ "nullable": true
+ },
+ "a_terminations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GenericObjectRequest"
+ }
+ },
+ "b_terminations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GenericObjectRequest"
+ }
+ },
+ "status": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "profile": {
+ "enum": [
+ "single-1c1p",
+ "single-1c2p",
+ "single-1c4p",
+ "single-1c6p",
+ "single-1c8p",
+ "single-1c12p",
+ "single-1c16p",
+ "trunk-2c1p",
+ "trunk-2c2p",
+ "trunk-2c4p",
+ "trunk-2c4p-shuffle",
+ "trunk-2c6p",
+ "trunk-2c8p",
+ "trunk-2c12p",
+ "trunk-4c1p",
+ "trunk-4c2p",
+ "trunk-4c4p",
+ "trunk-4c4p-shuffle",
+ "trunk-4c6p",
+ "trunk-4c8p",
+ "trunk-8c4p",
+ "breakout-1c4p-4c1p",
+ "breakout-1c6p-6c1p",
+ "breakout-2c4p-8c1p-shuffle",
+ ""
+ ],
+ "type": "string",
+ "description": "* `single-1c1p` - 1C1P\n* `single-1c2p` - 1C2P\n* `single-1c4p` - 1C4P\n* `single-1c6p` - 1C6P\n* `single-1c8p` - 1C8P\n* `single-1c12p` - 1C12P\n* `single-1c16p` - 1C16P\n* `trunk-2c1p` - 2C1P trunk\n* `trunk-2c2p` - 2C2P trunk\n* `trunk-2c4p` - 2C4P trunk\n* `trunk-2c4p-shuffle` - 2C4P trunk (shuffle)\n* `trunk-2c6p` - 2C6P trunk\n* `trunk-2c8p` - 2C8P trunk\n* `trunk-2c12p` - 2C12P trunk\n* `trunk-4c1p` - 4C1P trunk\n* `trunk-4c2p` - 4C2P trunk\n* `trunk-4c4p` - 4C4P trunk\n* `trunk-4c4p-shuffle` - 4C4P trunk (shuffle)\n* `trunk-4c6p` - 4C6P trunk\n* `trunk-4c8p` - 4C8P trunk\n* `trunk-8c4p` - 8C4P trunk\n* `breakout-1c4p-4c1p` - 1C4P:4C1P breakout\n* `breakout-1c6p-6c1p` - 1C6P:6C1P breakout\n* `breakout-2c4p-8c1p-shuffle` - 2C4P:8C1P breakout (shuffle)",
+ "x-spec-enum-id": "5e0f85310f0184ea"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "label": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "length": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "length_unit": {
+ "enum": [
+ "km",
+ "m",
+ "cm",
+ "mi",
+ "ft",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `cm` - Centimeters\n* `mi` - Miles\n* `ft` - Feet\n* `in` - Inches",
+ "x-spec-enum-id": "6e7645525ba02462",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableCircuitGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Base serializer for group assignments under CircuitSerializer.",
+ "properties": {
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefCircuitGroupRequest"
+ }
+ ]
+ },
+ "member_type": {
+ "type": "string"
+ },
+ "member_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "priority": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ }
+ },
+ "PatchedWritableCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "provider_account": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccountRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefCircuitTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "install_date": {
+ "type": "string",
+ "format": "date",
+ "nullable": true,
+ "title": "Installed"
+ },
+ "termination_date": {
+ "type": "string",
+ "format": "date",
+ "nullable": true,
+ "title": "Terminates"
+ },
+ "commit_rate": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Commit rate (Kbps)",
+ "description": "Committed rate"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e",
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "assignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BriefCircuitGroupAssignmentSerializer_Request"
+ }
+ }
+ }
+ },
+ "PatchedWritableClusterRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefClusterTypeRequest"
+ }
+ ]
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `offline` - Offline",
+ "x-spec-enum-id": "65a25166053759eb"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableConsolePortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true,
+ "description": "Physical port type\n\n* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true,
+ "description": "Port speed in bits per second\n\n* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "minimum": 0,
+ "maximum": 2147483647
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableConsolePortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritableConsoleServerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true,
+ "description": "Physical port type\n\n* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true,
+ "description": "Port speed in bits per second\n\n* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "minimum": 0,
+ "maximum": 2147483647
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableConsoleServerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritableContactAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "contact": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefContactRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefContactRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "priority": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableContactGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableCustomFieldChoiceSetRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "base_choices": {
+ "enum": [
+ "IATA",
+ "ISO_3166",
+ "UN_LOCODE",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "cf0efb5195f85007",
+ "nullable": true,
+ "description": "Base set of predefined choices (optional)\n\n* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)"
+ },
+ "extra_choices": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {},
+ "maxItems": 2,
+ "minItems": 2
+ }
+ },
+ "order_alphabetically": {
+ "type": "boolean",
+ "description": "Choices are automatically ordered alphabetically"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ }
+ },
+ "PatchedWritableCustomFieldRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "type": {
+ "enum": [
+ "text",
+ "longtext",
+ "integer",
+ "decimal",
+ "boolean",
+ "date",
+ "datetime",
+ "url",
+ "json",
+ "select",
+ "multiselect",
+ "object",
+ "multiobject"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "47c52a3d983e924c",
+ "description": "The type of data this custom field holds\n\n* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects"
+ },
+ "related_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Internal field name",
+ "pattern": "^[a-z0-9_]+$",
+ "maxLength": 50
+ },
+ "label": {
+ "type": "string",
+ "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)",
+ "maxLength": 50
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Custom fields within the same group will be displayed together",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "required": {
+ "type": "boolean",
+ "description": "This field is required when creating new objects or editing an existing object."
+ },
+ "unique": {
+ "type": "boolean",
+ "title": "Must be unique",
+ "description": "The value of this field must be unique for the assigned object"
+ },
+ "search_weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored."
+ },
+ "filter_logic": {
+ "enum": [
+ "disabled",
+ "loose",
+ "exact"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "d168820c798ae45a",
+ "description": "Loose matches any instance of a given string; exact matches the entire field.\n\n* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact"
+ },
+ "ui_visible": {
+ "enum": [
+ "always",
+ "if-set",
+ "hidden"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "f32800c399b927b6",
+ "description": "Specifies whether the custom field is displayed in the UI\n\n* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden"
+ },
+ "ui_editable": {
+ "enum": [
+ "yes",
+ "no",
+ "hidden"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "336f52760e62022f",
+ "description": "Specifies whether the custom field value can be edited in the UI\n\n* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden"
+ },
+ "is_cloneable": {
+ "type": "boolean",
+ "description": "Replicate this value when cloning objects"
+ },
+ "default": {
+ "nullable": true,
+ "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "related_object_filter": {
+ "nullable": true,
+ "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "title": "Display weight",
+ "description": "Fields with higher weights appear lower in a form."
+ },
+ "validation_minimum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Minimum value",
+ "description": "Minimum allowed value (for numeric fields)"
+ },
+ "validation_maximum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Maximum value",
+ "description": "Maximum allowed value (for numeric fields)"
+ },
+ "validation_regex": {
+ "type": "string",
+ "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.",
+ "maxLength": 500
+ },
+ "choice_set": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCustomFieldChoiceSetRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableDataSourceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "type": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50
+ },
+ "source_url": {
+ "type": "string",
+ "minLength": 1,
+ "title": "URL",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "sync_interval": {
+ "enum": [
+ 1,
+ 60,
+ 720,
+ 1440,
+ 10080,
+ 43200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days",
+ "x-spec-enum-id": "2e9f2567ecd93fbe",
+ "nullable": true,
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "parameters": {
+ "nullable": true
+ },
+ "ignore_rules": {
+ "type": "string",
+ "description": "Patterns (one per line) matching files to ignore when syncing"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableDeviceRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "vm_role": {
+ "type": "boolean",
+ "description": "Virtual machines may be assigned to this role"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableDeviceTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "default_platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "u_height": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.0,
+ "exclusiveMaximum": true,
+ "default": 1.0,
+ "title": "Position (U)"
+ },
+ "exclude_from_utilization": {
+ "type": "boolean",
+ "description": "Devices of this type are excluded when calculating rack utilization."
+ },
+ "is_full_depth": {
+ "type": "boolean",
+ "description": "Device consumes both front and rear rack faces."
+ },
+ "subdevice_role": {
+ "enum": [
+ "parent",
+ "child",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "65a61d5e1deb4a24",
+ "nullable": true,
+ "title": "Parent/child status",
+ "description": "Parent devices house child devices in device bays. Leave blank if this device type is neither a parent nor a child.\n\n* `parent` - Parent\n* `child` - Child"
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "front_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "rear_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableDeviceWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ },
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "description": "Chassis serial number, assigned by the manufacturer",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "position": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.5,
+ "exclusiveMaximum": true,
+ "nullable": true,
+ "title": "Position (U)"
+ },
+ "face": {
+ "enum": [
+ "front",
+ "rear",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83",
+ "nullable": true,
+ "title": "Rack face"
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "inventory",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "65feb4244cc9110c"
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e",
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "oob_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "virtual_chassis": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualChassisRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vc_position": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true
+ },
+ "vc_priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Virtual chassis master election priority"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableEventRuleRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "event_types": {
+ "type": "array",
+ "items": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2"
+ },
+ "description": "The types of event which will trigger this rule."
+ },
+ "conditions": {
+ "nullable": true,
+ "description": "A set of conditions which determine whether the event will be generated."
+ },
+ "action_type": {
+ "enum": [
+ "webhook",
+ "script",
+ "notification"
+ ],
+ "type": "string",
+ "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification",
+ "x-spec-enum-id": "287901b937995956"
+ },
+ "action_object_type": {
+ "type": "string"
+ },
+ "action_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ }
+ },
+ "PatchedWritableFrontPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableFrontPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritableIKEPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - IKEv1\n* `2` - IKEv2",
+ "x-spec-enum-id": "00872b77916a1fde",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "mode": {
+ "enum": [
+ "aggressive",
+ "main",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `aggressive` - Aggressive\n* `main` - Main",
+ "x-spec-enum-id": "64c1be7bdb2548ca",
+ "nullable": true
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "preshared_key": {
+ "type": "string",
+ "title": "Pre-shared key"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableIKEProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "authentication_method": {
+ "enum": [
+ "preshared-keys",
+ "certificates",
+ "rsa-signatures",
+ "dsa-signatures"
+ ],
+ "type": "string",
+ "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures",
+ "x-spec-enum-id": "a21158c52d0c455a"
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7",
+ "nullable": true
+ },
+ "group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "dbef43be795462a8",
+ "description": "Diffie-Hellman group ID\n\n* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "sa_lifetime": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Security association lifetime (in seconds)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableIPAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated",
+ "dhcp",
+ "slaac"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "c421c4c4a0fa7a2a",
+ "description": "The operational status of this IP\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC"
+ },
+ "role": {
+ "enum": [
+ "loopback",
+ "secondary",
+ "anycast",
+ "vip",
+ "vrrp",
+ "hsrp",
+ "glbp",
+ "carp",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "53dca4cddd7b344a",
+ "nullable": true,
+ "description": "The functional role of this IP\n\n* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP"
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "nat_inside": {
+ "type": "integer",
+ "nullable": true,
+ "title": "NAT (inside)",
+ "description": "The IP for which this address is the \"outside\" IP"
+ },
+ "dns_name": {
+ "type": "string",
+ "description": "Hostname or FQDN (not case-sensitive)",
+ "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableIPRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "start_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "end_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "ca933c38b935e547",
+ "description": "Operational status of this range\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "mark_populated": {
+ "type": "boolean",
+ "description": "Prevent the creation of IP addresses within this range"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Report space as fully utilized"
+ }
+ }
+ },
+ "PatchedWritableIPSecPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "pfs_group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "dbef43be795462a8",
+ "nullable": true,
+ "description": "Diffie-Hellman group for Perfect Forward Secrecy\n\n* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableIPSecProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "esp",
+ "ah"
+ ],
+ "type": "string",
+ "description": "* `esp` - ESP\n* `ah` - AH",
+ "x-spec-enum-id": "87ac6ada0da14ccf"
+ },
+ "ike_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIKEPolicyRequest"
+ }
+ ]
+ },
+ "ipsec_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIPSecPolicyRequest"
+ }
+ ]
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableIPSecProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2",
+ "nullable": true,
+ "title": "Encryption"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7",
+ "nullable": true,
+ "title": "Authentication"
+ },
+ "sa_lifetime_seconds": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (seconds)",
+ "description": "Security association lifetime (seconds)"
+ },
+ "sa_lifetime_data": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (KB)",
+ "description": "Security association lifetime (in kilobytes)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableInterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "vdcs": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent interface"
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "lag": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent LAG"
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Speed (Kbps)"
+ },
+ "duplex": {
+ "enum": [
+ "half",
+ "full",
+ "auto",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto",
+ "x-spec-enum-id": "368458a2b67c916b",
+ "nullable": true
+ },
+ "wwn": {
+ "type": "string",
+ "nullable": true
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only",
+ "description": "This interface is used only for out-of-band management"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "84129b71b974ebe5",
+ "nullable": true,
+ "description": "IEEE 802.1Q tagging strategy\n\n* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)"
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1",
+ "nullable": true,
+ "title": "Wireless role"
+ },
+ "rf_channel": {
+ "enum": [
+ "2.4g-1-2412-22",
+ "2.4g-2-2417-22",
+ "2.4g-3-2422-22",
+ "2.4g-4-2427-22",
+ "2.4g-5-2432-22",
+ "2.4g-6-2437-22",
+ "2.4g-7-2442-22",
+ "2.4g-8-2447-22",
+ "2.4g-9-2452-22",
+ "2.4g-10-2457-22",
+ "2.4g-11-2462-22",
+ "2.4g-12-2467-22",
+ "2.4g-13-2472-22",
+ "5g-32-5160-20",
+ "5g-34-5170-40",
+ "5g-36-5180-20",
+ "5g-38-5190-40",
+ "5g-40-5200-20",
+ "5g-42-5210-80",
+ "5g-44-5220-20",
+ "5g-46-5230-40",
+ "5g-48-5240-20",
+ "5g-50-5250-160",
+ "5g-52-5260-20",
+ "5g-54-5270-40",
+ "5g-56-5280-20",
+ "5g-58-5290-80",
+ "5g-60-5300-20",
+ "5g-62-5310-40",
+ "5g-64-5320-20",
+ "5g-100-5500-20",
+ "5g-102-5510-40",
+ "5g-104-5520-20",
+ "5g-106-5530-80",
+ "5g-108-5540-20",
+ "5g-110-5550-40",
+ "5g-112-5560-20",
+ "5g-114-5570-160",
+ "5g-116-5580-20",
+ "5g-118-5590-40",
+ "5g-120-5600-20",
+ "5g-122-5610-80",
+ "5g-124-5620-20",
+ "5g-126-5630-40",
+ "5g-128-5640-20",
+ "5g-132-5660-20",
+ "5g-134-5670-40",
+ "5g-136-5680-20",
+ "5g-138-5690-80",
+ "5g-140-5700-20",
+ "5g-142-5710-40",
+ "5g-144-5720-20",
+ "5g-149-5745-20",
+ "5g-151-5755-40",
+ "5g-153-5765-20",
+ "5g-155-5775-80",
+ "5g-157-5785-20",
+ "5g-159-5795-40",
+ "5g-161-5805-20",
+ "5g-163-5815-160",
+ "5g-165-5825-20",
+ "5g-167-5835-40",
+ "5g-169-5845-20",
+ "5g-171-5855-80",
+ "5g-173-5865-20",
+ "5g-175-5875-40",
+ "5g-177-5885-20",
+ "6g-1-5955-20",
+ "6g-3-5965-40",
+ "6g-5-5975-20",
+ "6g-7-5985-80",
+ "6g-9-5995-20",
+ "6g-11-6005-40",
+ "6g-13-6015-20",
+ "6g-15-6025-160",
+ "6g-17-6035-20",
+ "6g-19-6045-40",
+ "6g-21-6055-20",
+ "6g-23-6065-80",
+ "6g-25-6075-20",
+ "6g-27-6085-40",
+ "6g-29-6095-20",
+ "6g-31-6105-320",
+ "6g-33-6115-20",
+ "6g-35-6125-40",
+ "6g-37-6135-20",
+ "6g-39-6145-80",
+ "6g-41-6155-20",
+ "6g-43-6165-40",
+ "6g-45-6175-20",
+ "6g-47-6185-160",
+ "6g-49-6195-20",
+ "6g-51-6205-40",
+ "6g-53-6215-20",
+ "6g-55-6225-80",
+ "6g-57-6235-20",
+ "6g-59-6245-40",
+ "6g-61-6255-20",
+ "6g-65-6275-20",
+ "6g-67-6285-40",
+ "6g-69-6295-20",
+ "6g-71-6305-80",
+ "6g-73-6315-20",
+ "6g-75-6325-40",
+ "6g-77-6335-20",
+ "6g-79-6345-160",
+ "6g-81-6355-20",
+ "6g-83-6365-40",
+ "6g-85-6375-20",
+ "6g-87-6385-80",
+ "6g-89-6395-20",
+ "6g-91-6405-40",
+ "6g-93-6415-20",
+ "6g-95-6425-320",
+ "6g-97-6435-20",
+ "6g-99-6445-40",
+ "6g-101-6455-20",
+ "6g-103-6465-80",
+ "6g-105-6475-20",
+ "6g-107-6485-40",
+ "6g-109-6495-20",
+ "6g-111-6505-160",
+ "6g-113-6515-20",
+ "6g-115-6525-40",
+ "6g-117-6535-20",
+ "6g-119-6545-80",
+ "6g-121-6555-20",
+ "6g-123-6565-40",
+ "6g-125-6575-20",
+ "6g-129-6595-20",
+ "6g-131-6605-40",
+ "6g-133-6615-20",
+ "6g-135-6625-80",
+ "6g-137-6635-20",
+ "6g-139-6645-40",
+ "6g-141-6655-20",
+ "6g-143-6665-160",
+ "6g-145-6675-20",
+ "6g-147-6685-40",
+ "6g-149-6695-20",
+ "6g-151-6705-80",
+ "6g-153-6715-20",
+ "6g-155-6725-40",
+ "6g-157-6735-20",
+ "6g-159-6745-320",
+ "6g-161-6755-20",
+ "6g-163-6765-40",
+ "6g-165-6775-20",
+ "6g-167-6785-80",
+ "6g-169-6795-20",
+ "6g-171-6805-40",
+ "6g-173-6815-20",
+ "6g-175-6825-160",
+ "6g-177-6835-20",
+ "6g-179-6845-40",
+ "6g-181-6855-20",
+ "6g-183-6865-80",
+ "6g-185-6875-20",
+ "6g-187-6885-40",
+ "6g-189-6895-20",
+ "6g-193-6915-20",
+ "6g-195-6925-40",
+ "6g-197-6935-20",
+ "6g-199-6945-80",
+ "6g-201-6955-20",
+ "6g-203-6965-40",
+ "6g-205-6975-20",
+ "6g-207-6985-160",
+ "6g-209-6995-20",
+ "6g-211-7005-40",
+ "6g-213-7015-20",
+ "6g-215-7025-80",
+ "6g-217-7035-20",
+ "6g-219-7045-40",
+ "6g-221-7055-20",
+ "6g-225-7075-20",
+ "6g-227-7085-40",
+ "6g-229-7095-20",
+ "6g-233-7115-20",
+ "60g-1-58320-2160",
+ "60g-2-60480-2160",
+ "60g-3-62640-2160",
+ "60g-4-64800-2160",
+ "60g-5-66960-2160",
+ "60g-6-69120-2160",
+ "60g-9-59400-4320",
+ "60g-10-61560-4320",
+ "60g-11-63720-4320",
+ "60g-12-65880-4320",
+ "60g-13-68040-4320",
+ "60g-17-60480-6480",
+ "60g-18-62640-6480",
+ "60g-19-64800-6480",
+ "60g-20-66960-6480",
+ "60g-25-61560-6480",
+ "60g-26-63720-6480",
+ "60g-27-65880-6480",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)",
+ "x-spec-enum-id": "70cf66176c475063",
+ "nullable": true,
+ "title": "Wireless channel"
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd",
+ "nullable": true
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab",
+ "nullable": true
+ },
+ "rf_channel_frequency": {
+ "type": "number",
+ "format": "double",
+ "maximum": 100000,
+ "minimum": -100000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel frequency (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "rf_channel_width": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": -10000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel width (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "tx_power": {
+ "type": "integer",
+ "maximum": 127,
+ "minimum": -40,
+ "nullable": true,
+ "title": "Transmit power (dBm)"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "wireless_lans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableInterfaceTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd",
+ "nullable": true
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab",
+ "nullable": true
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1",
+ "nullable": true,
+ "title": "Wireless role"
+ }
+ }
+ },
+ "PatchedWritableInventoryItemRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this item",
+ "maxLength": 50
+ },
+ "discovered": {
+ "type": "boolean",
+ "description": "This item was automatically discovered"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableJournalEntryRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "created_by": {
+ "type": "integer",
+ "nullable": true
+ },
+ "kind": {
+ "enum": [
+ "info",
+ "success",
+ "warning",
+ "danger"
+ ],
+ "type": "string",
+ "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger",
+ "x-spec-enum-id": "6f65abe0aab2c78c"
+ },
+ "comments": {
+ "type": "string",
+ "minLength": 1
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableL2VPNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "identifier": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": -9223372036854775808,
+ "format": "int64",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "type": {
+ "enum": [
+ "vpws",
+ "vpls",
+ "vxlan",
+ "vxlan-evpn",
+ "mpls-evpn",
+ "pbb-evpn",
+ "evpn-vpws",
+ "epl",
+ "evpl",
+ "ep-lan",
+ "evp-lan",
+ "ep-tree",
+ "evp-tree",
+ "spb"
+ ],
+ "type": "string",
+ "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB",
+ "x-spec-enum-id": "0a46f8056d717efc"
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "8b9dc8efc7c3d5b0"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableLocationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableModuleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module_bay": {
+ "type": "integer"
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableModuleTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "passive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive",
+ "x-spec-enum-id": "5ad4e700c656b09d",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "attributes": {
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritablePlatformRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritablePowerFeedRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "power_panel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefPowerPanelRequest"
+ }
+ ]
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed",
+ "x-spec-enum-id": "ec530572dc778583"
+ },
+ "type": {
+ "enum": [
+ "primary",
+ "redundant"
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `redundant` - Redundant",
+ "x-spec-enum-id": "093a164236819eb8"
+ },
+ "supply": {
+ "enum": [
+ "ac",
+ "dc"
+ ],
+ "type": "string",
+ "description": "* `ac` - AC\n* `dc` - DC",
+ "x-spec-enum-id": "1b6d99616ca6412b"
+ },
+ "phase": {
+ "enum": [
+ "single-phase",
+ "three-phase"
+ ],
+ "type": "string",
+ "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase",
+ "x-spec-enum-id": "994bc0696f4df57f"
+ },
+ "voltage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": -32768
+ },
+ "amperage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1
+ },
+ "max_utilization": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "description": "Maximum permissible draw (percentage)"
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritablePowerOutletRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true,
+ "description": "Physical port type\n\n* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other"
+ },
+ "status": {
+ "enum": [
+ "enabled",
+ "disabled",
+ "faulty"
+ ],
+ "type": "string",
+ "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty",
+ "x-spec-enum-id": "d60dce16858f3c69"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true,
+ "description": "Phase (for three-phase feeds)\n\n* `A` - A\n* `B` - B\n* `C` - C"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritablePowerOutletTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true,
+ "description": "Phase (for three-phase feeds)\n\n* `A` - A\n* `B` - B\n* `C` - C"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritablePowerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true,
+ "description": "Physical port type\n\n* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other"
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritablePowerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritablePrefixRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "container",
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "026173ce39f2ee63",
+ "description": "Operational status of this prefix\n\n* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "is_pool": {
+ "type": "boolean",
+ "title": "Is a pool",
+ "description": "All IP addresses within this prefix are considered usable"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Treat as fully utilized"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableRackRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "facility_id": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "reserved",
+ "available",
+ "planned",
+ "active",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "76eea4eef8804bcb"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this rack",
+ "maxLength": 50
+ },
+ "rack_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841",
+ "nullable": true
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "9b322795f297a9c3",
+ "description": "Rail-to-rail width\n\n* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front",
+ "x-spec-enum-id": "a784734d07ef1b3c",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableRackReservationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ]
+ },
+ "units": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ }
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "active",
+ "stale"
+ ],
+ "type": "string",
+ "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale",
+ "x-spec-enum-id": "ed6038a4deee151c"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableRackTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical"
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841"
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "9b322795f297a9c3",
+ "description": "Rail-to-rail width\n\n* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableRearPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableRearPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ },
+ "PatchedWritableRegionRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableServiceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "parent_object_type": {
+ "type": "string"
+ },
+ "parent_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "ipaddresses": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableServiceTemplateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableSiteGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableSiteRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full name of the site",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "region": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRegionRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "time_zone": {
+ "type": "string",
+ "nullable": true,
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "physical_address": {
+ "type": "string",
+ "description": "Physical location of the building",
+ "maxLength": 200
+ },
+ "shipping_address": {
+ "type": "string",
+ "description": "If different from the physical address",
+ "maxLength": 200
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableTenantGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableTunnelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "active",
+ "disabled"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled",
+ "x-spec-enum-id": "2431ef62c418f485"
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTunnelGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "encapsulation": {
+ "enum": [
+ "ipsec-transport",
+ "ipsec-tunnel",
+ "ip-ip",
+ "gre",
+ "wireguard",
+ "openvpn",
+ "l2tp",
+ "pptp"
+ ],
+ "type": "string",
+ "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP",
+ "x-spec-enum-id": "4f3254459f0e94f0"
+ },
+ "ipsec_profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPSecProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tunnel_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableTunnelTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "tunnel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefTunnelRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "termination_type": {
+ "type": "string"
+ },
+ "termination_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "outside_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "ca933c38b935e547",
+ "description": "Operational status of this VLAN\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qinq_role": {
+ "enum": [
+ "svlan",
+ "cvlan",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "fa0abd59fb1a7312",
+ "nullable": true,
+ "title": "Q-in-Q role",
+ "description": "Customer/service VLAN designation (for Q-in-Q/IEEE 802.1ad)\n\n* `svlan` - Service\n* `cvlan` - Customer"
+ },
+ "qinq_svlan": {
+ "type": "integer",
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVMInterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_machine": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent interface"
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "84129b71b974ebe5",
+ "nullable": true,
+ "description": "IEEE 802.1Q tagging strategy\n\n* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVirtualChassisRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "domain": {
+ "type": "string",
+ "maxLength": 30
+ },
+ "master": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVirtualCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider_network": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderNetworkRequest"
+ }
+ ]
+ },
+ "provider_account": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccountRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVirtualCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_circuit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "interface": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVirtualDeviceContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "identifier": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline",
+ "x-spec-enum-id": "0e2c0919d51b83cb"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableVirtualMachineWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning",
+ "paused"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused",
+ "x-spec-enum-id": "effecc3b94e0b74b"
+ },
+ "start_on_boot": {
+ "enum": [
+ "on",
+ "off",
+ "laststate"
+ ],
+ "type": "string",
+ "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State",
+ "x-spec-enum-id": "610e33fc2fde73d6"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Memory (MB)"
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Disk (MB)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableWirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ }
+ },
+ "PatchedWritableWirelessLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "ssid": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefWirelessLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "disabled",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "e5549d7370ce2e6c"
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c",
+ "nullable": true,
+ "title": "Authentication cipher"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "PatchedWritableWirelessLinkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "interface_a": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "interface_b": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "status": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c",
+ "nullable": true,
+ "title": "Authentication cipher"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "Platform": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedPlatform"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "manufacturer": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ }
+ ],
+ "nullable": true
+ },
+ "config_template": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "device_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "virtualmachine_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url",
+ "virtualmachine_count"
+ ]
+ },
+ "PlatformRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedPlatformRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "PowerFeed": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "power_panel": {
+ "$ref": "#/components/schemas/BriefPowerPanel"
+ },
+ "rack": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRack"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed",
+ "x-spec-enum-id": "ec530572dc778583"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Failed"
+ ]
+ }
+ }
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "primary",
+ "redundant"
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `redundant` - Redundant",
+ "x-spec-enum-id": "093a164236819eb8"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Primary",
+ "Redundant"
+ ]
+ }
+ }
+ },
+ "supply": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "ac",
+ "dc"
+ ],
+ "type": "string",
+ "description": "* `ac` - AC\n* `dc` - DC",
+ "x-spec-enum-id": "1b6d99616ca6412b"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "AC",
+ "DC"
+ ]
+ }
+ }
+ },
+ "phase": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "single-phase",
+ "three-phase"
+ ],
+ "type": "string",
+ "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase",
+ "x-spec-enum-id": "994bc0696f4df57f"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Single phase",
+ "Three-phase"
+ ]
+ }
+ }
+ },
+ "voltage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": -32768
+ },
+ "amperage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1
+ },
+ "max_utilization": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "description": "Maximum permissible draw (percentage)"
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "power_panel",
+ "url"
+ ]
+ },
+ "PowerFeedRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "power_panel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefPowerPanelRequest"
+ }
+ ]
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed",
+ "x-spec-enum-id": "ec530572dc778583"
+ },
+ "type": {
+ "enum": [
+ "primary",
+ "redundant"
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `redundant` - Redundant",
+ "x-spec-enum-id": "093a164236819eb8"
+ },
+ "supply": {
+ "enum": [
+ "ac",
+ "dc"
+ ],
+ "type": "string",
+ "description": "* `ac` - AC\n* `dc` - DC",
+ "x-spec-enum-id": "1b6d99616ca6412b"
+ },
+ "phase": {
+ "enum": [
+ "single-phase",
+ "three-phase"
+ ],
+ "type": "string",
+ "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase",
+ "x-spec-enum-id": "994bc0696f4df57f"
+ },
+ "voltage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": -32768
+ },
+ "amperage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1
+ },
+ "max_utilization": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "description": "Maximum permissible draw (percentage)"
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "power_panel"
+ ]
+ },
+ "PowerOutlet": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "C5",
+ "C7",
+ "C13",
+ "C15",
+ "C17",
+ "C19",
+ "C21",
+ "P+N+E 4H",
+ "P+N+E 6H",
+ "P+N+E 9H",
+ "2P+E 4H",
+ "2P+E 6H",
+ "2P+E 9H",
+ "3P+E 4H",
+ "3P+E 6H",
+ "3P+E 9H",
+ "3P+N+E 4H",
+ "3P+N+E 6H",
+ "3P+N+E 9H",
+ "IEC 60906-1",
+ "2P+T 10A (NBR 14136)",
+ "2P+T 20A (NBR 14136)",
+ "NEMA 1-15R",
+ "NEMA 5-15R",
+ "NEMA 5-20R",
+ "NEMA 5-30R",
+ "NEMA 5-50R",
+ "NEMA 6-15R",
+ "NEMA 6-20R",
+ "NEMA 6-30R",
+ "NEMA 6-50R",
+ "NEMA 10-30R",
+ "NEMA 10-50R",
+ "NEMA 14-20R",
+ "NEMA 14-30R",
+ "NEMA 14-50R",
+ "NEMA 14-60R",
+ "NEMA 15-15R",
+ "NEMA 15-20R",
+ "NEMA 15-30R",
+ "NEMA 15-50R",
+ "NEMA 15-60R",
+ "NEMA L1-15R",
+ "NEMA L5-15R",
+ "NEMA L5-20R",
+ "NEMA L5-30R",
+ "NEMA L5-50R",
+ "NEMA L6-15R",
+ "NEMA L6-20R",
+ "NEMA L6-30R",
+ "NEMA L6-50R",
+ "NEMA L10-30R",
+ "NEMA L14-20R",
+ "NEMA L14-30R",
+ "NEMA L14-50R",
+ "NEMA L14-60R",
+ "NEMA L15-20R",
+ "NEMA L15-30R",
+ "NEMA L15-50R",
+ "NEMA L15-60R",
+ "NEMA L21-20R",
+ "NEMA L21-30R",
+ "NEMA L22-20R",
+ "NEMA L22-30R",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ITA Type E (CEE 7/5)",
+ "ITA Type F (CEE 7/3)",
+ "ITA Type G (BS 1363)",
+ "ITA Type H",
+ "ITA Type I",
+ "ITA Type J",
+ "ITA Type K",
+ "ITA Type L (CEI 23-50)",
+ "ITA Type M (BS 546)",
+ "ITA Type N",
+ "ITA Type O",
+ "ITA Multistandard",
+ "USB Type A",
+ "USB Micro B",
+ "USB Type C",
+ "Molex Micro-Fit 1x2",
+ "Molex Micro-Fit 2x2",
+ "Molex Micro-Fit 2x3",
+ "Molex Micro-Fit 2x4",
+ "DC Terminal",
+ "Eaton C39",
+ "HDOT Cx",
+ "Saf-D-Grid",
+ "Neutrik powerCON (20A)",
+ "Neutrik powerCON (32A)",
+ "Neutrik powerCON TRUE1",
+ "Neutrik powerCON TRUE1 TOP",
+ "Ubiquiti SmartPower",
+ "Hardwired",
+ "Other"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "enabled",
+ "disabled",
+ "faulty"
+ ],
+ "type": "string",
+ "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty",
+ "x-spec-enum-id": "d60dce16858f3c69"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Enabled",
+ "Disabled",
+ "Faulty"
+ ]
+ }
+ }
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPort"
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `A` - A\n* `B` - B\n* `C` - C",
+ "x-spec-enum-id": "a4902339df0b7c06"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "A",
+ "B",
+ "C"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "url"
+ ]
+ },
+ "PowerOutletRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "enabled",
+ "disabled",
+ "faulty"
+ ],
+ "type": "string",
+ "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty",
+ "x-spec-enum-id": "d60dce16858f3c69"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `A` - A\n* `B` - B\n* `C` - C",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "PowerOutletTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "C5",
+ "C7",
+ "C13",
+ "C15",
+ "C17",
+ "C19",
+ "C21",
+ "P+N+E 4H",
+ "P+N+E 6H",
+ "P+N+E 9H",
+ "2P+E 4H",
+ "2P+E 6H",
+ "2P+E 9H",
+ "3P+E 4H",
+ "3P+E 6H",
+ "3P+E 9H",
+ "3P+N+E 4H",
+ "3P+N+E 6H",
+ "3P+N+E 9H",
+ "IEC 60906-1",
+ "2P+T 10A (NBR 14136)",
+ "2P+T 20A (NBR 14136)",
+ "NEMA 1-15R",
+ "NEMA 5-15R",
+ "NEMA 5-20R",
+ "NEMA 5-30R",
+ "NEMA 5-50R",
+ "NEMA 6-15R",
+ "NEMA 6-20R",
+ "NEMA 6-30R",
+ "NEMA 6-50R",
+ "NEMA 10-30R",
+ "NEMA 10-50R",
+ "NEMA 14-20R",
+ "NEMA 14-30R",
+ "NEMA 14-50R",
+ "NEMA 14-60R",
+ "NEMA 15-15R",
+ "NEMA 15-20R",
+ "NEMA 15-30R",
+ "NEMA 15-50R",
+ "NEMA 15-60R",
+ "NEMA L1-15R",
+ "NEMA L5-15R",
+ "NEMA L5-20R",
+ "NEMA L5-30R",
+ "NEMA L5-50R",
+ "NEMA L6-15R",
+ "NEMA L6-20R",
+ "NEMA L6-30R",
+ "NEMA L6-50R",
+ "NEMA L10-30R",
+ "NEMA L14-20R",
+ "NEMA L14-30R",
+ "NEMA L14-50R",
+ "NEMA L14-60R",
+ "NEMA L15-20R",
+ "NEMA L15-30R",
+ "NEMA L15-50R",
+ "NEMA L15-60R",
+ "NEMA L21-20R",
+ "NEMA L21-30R",
+ "NEMA L22-20R",
+ "NEMA L22-30R",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ITA Type E (CEE 7/5)",
+ "ITA Type F (CEE 7/3)",
+ "ITA Type G (BS 1363)",
+ "ITA Type H",
+ "ITA Type I",
+ "ITA Type J",
+ "ITA Type K",
+ "ITA Type L (CEI 23-50)",
+ "ITA Type M (BS 546)",
+ "ITA Type N",
+ "ITA Type O",
+ "ITA Multistandard",
+ "USB Type A",
+ "USB Micro B",
+ "USB Type C",
+ "Molex Micro-Fit 1x2",
+ "Molex Micro-Fit 2x2",
+ "Molex Micro-Fit 2x3",
+ "Molex Micro-Fit 2x4",
+ "DC Terminal",
+ "Eaton C39",
+ "HDOT Cx",
+ "Saf-D-Grid",
+ "Neutrik powerCON (20A)",
+ "Neutrik powerCON (32A)",
+ "Neutrik powerCON TRUE1",
+ "Neutrik powerCON TRUE1 TOP",
+ "Ubiquiti SmartPower",
+ "Hardwired",
+ "Other"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `A` - A\n* `B` - B\n* `C` - C",
+ "x-spec-enum-id": "a4902339df0b7c06"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "A",
+ "B",
+ "C"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "PowerOutletTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `A` - A\n* `B` - B\n* `C` - C",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "PowerPanel": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "site": {
+ "$ref": "#/components/schemas/BriefSite"
+ },
+ "location": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocation"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "powerfeed_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "powerfeed_count",
+ "site",
+ "url"
+ ]
+ },
+ "PowerPanelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "site"
+ ]
+ },
+ "PowerPort": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "C6",
+ "C8",
+ "C14",
+ "C16",
+ "C18",
+ "C20",
+ "C22",
+ "P+N+E 4H",
+ "P+N+E 6H",
+ "P+N+E 9H",
+ "2P+E 4H",
+ "2P+E 6H",
+ "2P+E 9H",
+ "3P+E 4H",
+ "3P+E 6H",
+ "3P+E 9H",
+ "3P+N+E 4H",
+ "3P+N+E 6H",
+ "3P+N+E 9H",
+ "IEC 60906-1",
+ "2P+T 10A (NBR 14136)",
+ "2P+T 20A (NBR 14136)",
+ "NEMA 1-15P",
+ "NEMA 5-15P",
+ "NEMA 5-20P",
+ "NEMA 5-30P",
+ "NEMA 5-50P",
+ "NEMA 6-15P",
+ "NEMA 6-20P",
+ "NEMA 6-30P",
+ "NEMA 6-50P",
+ "NEMA 10-30P",
+ "NEMA 10-50P",
+ "NEMA 14-20P",
+ "NEMA 14-30P",
+ "NEMA 14-50P",
+ "NEMA 14-60P",
+ "NEMA 15-15P",
+ "NEMA 15-20P",
+ "NEMA 15-30P",
+ "NEMA 15-50P",
+ "NEMA 15-60P",
+ "NEMA L1-15P",
+ "NEMA L5-15P",
+ "NEMA L5-20P",
+ "NEMA L5-30P",
+ "NEMA L5-50P",
+ "NEMA L6-15P",
+ "NEMA L6-20P",
+ "NEMA L6-30P",
+ "NEMA L6-50P",
+ "NEMA L10-30P",
+ "NEMA L14-20P",
+ "NEMA L14-30P",
+ "NEMA L14-50P",
+ "NEMA L14-60P",
+ "NEMA L15-20P",
+ "NEMA L15-30P",
+ "NEMA L15-50P",
+ "NEMA L15-60P",
+ "NEMA L21-20P",
+ "NEMA L21-30P",
+ "NEMA L22-20P",
+ "NEMA L22-30P",
+ "CS6361C",
+ "CS6365C",
+ "CS8165C",
+ "CS8265C",
+ "CS8365C",
+ "CS8465C",
+ "ITA Type C (CEE 7/16)",
+ "ITA Type E (CEE 7/6)",
+ "ITA Type F (CEE 7/4)",
+ "ITA Type E/F (CEE 7/7)",
+ "ITA Type G (BS 1363)",
+ "ITA Type H",
+ "ITA Type I",
+ "ITA Type J",
+ "ITA Type K",
+ "ITA Type L (CEI 23-50)",
+ "ITA Type M (BS 546)",
+ "ITA Type N",
+ "ITA Type O",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "USB 3.0 Type B",
+ "USB 3.0 Micro B",
+ "Molex Micro-Fit 1x2",
+ "Molex Micro-Fit 2x2",
+ "Molex Micro-Fit 2x3",
+ "Molex Micro-Fit 2x4",
+ "DC Terminal",
+ "Saf-D-Grid",
+ "Neutrik powerCON (20A)",
+ "Neutrik powerCON (32A)",
+ "Neutrik powerCON TRUE1",
+ "Neutrik powerCON TRUE1 TOP",
+ "Ubiquiti SmartPower",
+ "Hardwired",
+ "Other"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints": {
+ "type": "array",
+ "items": {},
+ "nullable": true,
+ "readOnly": true
+ },
+ "connected_endpoints_type": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "connected_endpoints_reachable": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "connected_endpoints",
+ "connected_endpoints_reachable",
+ "connected_endpoints_type",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "url"
+ ]
+ },
+ "PowerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "PowerPortTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "C6",
+ "C8",
+ "C14",
+ "C16",
+ "C18",
+ "C20",
+ "C22",
+ "P+N+E 4H",
+ "P+N+E 6H",
+ "P+N+E 9H",
+ "2P+E 4H",
+ "2P+E 6H",
+ "2P+E 9H",
+ "3P+E 4H",
+ "3P+E 6H",
+ "3P+E 9H",
+ "3P+N+E 4H",
+ "3P+N+E 6H",
+ "3P+N+E 9H",
+ "IEC 60906-1",
+ "2P+T 10A (NBR 14136)",
+ "2P+T 20A (NBR 14136)",
+ "NEMA 1-15P",
+ "NEMA 5-15P",
+ "NEMA 5-20P",
+ "NEMA 5-30P",
+ "NEMA 5-50P",
+ "NEMA 6-15P",
+ "NEMA 6-20P",
+ "NEMA 6-30P",
+ "NEMA 6-50P",
+ "NEMA 10-30P",
+ "NEMA 10-50P",
+ "NEMA 14-20P",
+ "NEMA 14-30P",
+ "NEMA 14-50P",
+ "NEMA 14-60P",
+ "NEMA 15-15P",
+ "NEMA 15-20P",
+ "NEMA 15-30P",
+ "NEMA 15-50P",
+ "NEMA 15-60P",
+ "NEMA L1-15P",
+ "NEMA L5-15P",
+ "NEMA L5-20P",
+ "NEMA L5-30P",
+ "NEMA L5-50P",
+ "NEMA L6-15P",
+ "NEMA L6-20P",
+ "NEMA L6-30P",
+ "NEMA L6-50P",
+ "NEMA L10-30P",
+ "NEMA L14-20P",
+ "NEMA L14-30P",
+ "NEMA L14-50P",
+ "NEMA L14-60P",
+ "NEMA L15-20P",
+ "NEMA L15-30P",
+ "NEMA L15-50P",
+ "NEMA L15-60P",
+ "NEMA L21-20P",
+ "NEMA L21-30P",
+ "NEMA L22-20P",
+ "NEMA L22-30P",
+ "CS6361C",
+ "CS6365C",
+ "CS8165C",
+ "CS8265C",
+ "CS8365C",
+ "CS8465C",
+ "ITA Type C (CEE 7/16)",
+ "ITA Type E (CEE 7/6)",
+ "ITA Type F (CEE 7/4)",
+ "ITA Type E/F (CEE 7/7)",
+ "ITA Type G (BS 1363)",
+ "ITA Type H",
+ "ITA Type I",
+ "ITA Type J",
+ "ITA Type K",
+ "ITA Type L (CEI 23-50)",
+ "ITA Type M (BS 546)",
+ "ITA Type N",
+ "ITA Type O",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "USB 3.0 Type B",
+ "USB 3.0 Micro B",
+ "Molex Micro-Fit 1x2",
+ "Molex Micro-Fit 2x2",
+ "Molex Micro-Fit 2x3",
+ "Molex Micro-Fit 2x4",
+ "DC Terminal",
+ "Saf-D-Grid",
+ "Neutrik powerCON (20A)",
+ "Neutrik powerCON (32A)",
+ "Neutrik powerCON TRUE1",
+ "Neutrik powerCON TRUE1 TOP",
+ "Ubiquiti SmartPower",
+ "Hardwired",
+ "Other"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "PowerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "Prefix": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "family": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 4,
+ 6
+ ],
+ "type": "integer",
+ "description": "* `4` - IPv4\n* `6` - IPv6",
+ "x-spec-enum-id": "d72003fd1af3603d"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IPv4",
+ "IPv6"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "prefix": {
+ "type": "string"
+ },
+ "vrf": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRF"
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "scope": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "vlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "container",
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "026173ce39f2ee63"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Container",
+ "Active",
+ "Reserved",
+ "Deprecated"
+ ]
+ }
+ }
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRole"
+ }
+ ],
+ "nullable": true
+ },
+ "is_pool": {
+ "type": "boolean",
+ "title": "Is a pool",
+ "description": "All IP addresses within this prefix are considered usable"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Treat as fully utilized"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "children": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "children",
+ "created",
+ "display",
+ "display_url",
+ "family",
+ "id",
+ "last_updated",
+ "prefix",
+ "scope",
+ "url"
+ ]
+ },
+ "PrefixRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "container",
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "026173ce39f2ee63"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "is_pool": {
+ "type": "boolean",
+ "title": "Is a pool",
+ "description": "All IP addresses within this prefix are considered usable"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Treat as fully utilized"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "prefix"
+ ]
+ },
+ "Provider": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "Full name of the provider",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "accounts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedProviderAccount"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ASN"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "circuit_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "circuit_count",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "ProviderAccount": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "provider": {
+ "$ref": "#/components/schemas/BriefProvider"
+ },
+ "name": {
+ "type": "string",
+ "default": "",
+ "maxLength": 100
+ },
+ "account": {
+ "type": "string",
+ "title": "Account ID",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "account",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "provider",
+ "url"
+ ]
+ },
+ "ProviderAccountRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "default": "",
+ "maxLength": 100
+ },
+ "account": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Account ID",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "account",
+ "provider"
+ ]
+ },
+ "ProviderNetwork": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "provider": {
+ "$ref": "#/components/schemas/BriefProvider"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "service_id": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "provider",
+ "url"
+ ]
+ },
+ "ProviderNetworkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "service_id": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "provider"
+ ]
+ },
+ "ProviderRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full name of the provider",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "accounts": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "RIR": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "is_private": {
+ "type": "boolean",
+ "title": "Private",
+ "description": "IP space managed by this RIR is considered private"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "aggregate_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "aggregate_count",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url"
+ ]
+ },
+ "RIRRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "is_private": {
+ "type": "boolean",
+ "title": "Private",
+ "description": "IP space managed by this RIR is considered private"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "Rack": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "facility_id": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 50
+ },
+ "site": {
+ "$ref": "#/components/schemas/BriefSite"
+ },
+ "location": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocation"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "reserved",
+ "available",
+ "planned",
+ "active",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "76eea4eef8804bcb"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Reserved",
+ "Available",
+ "Planned",
+ "Active",
+ "Deprecated"
+ ]
+ }
+ }
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRole"
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this rack",
+ "maxLength": 50
+ },
+ "rack_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackType"
+ }
+ ],
+ "nullable": true
+ },
+ "form_factor": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "2-post frame",
+ "4-post frame",
+ "4-post cabinet",
+ "Wall-mounted frame",
+ "Wall-mounted frame (vertical)",
+ "Wall-mounted cabinet",
+ "Wall-mounted cabinet (vertical)"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "width": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "x-spec-enum-id": "9b322795f297a9c3"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "10 inches",
+ "19 inches",
+ "21 inches",
+ "23 inches"
+ ]
+ }
+ }
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Kilograms",
+ "Grams",
+ "Pounds",
+ "Ounces"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Millimeters",
+ "Inches"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "airflow": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front",
+ "x-spec-enum-id": "a784734d07ef1b3c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front to rear",
+ "Rear to front"
+ ]
+ }
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "device_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "powerfeed_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "powerfeed_count",
+ "site",
+ "url"
+ ]
+ },
+ "RackRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "facility_id": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "reserved",
+ "available",
+ "planned",
+ "active",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "76eea4eef8804bcb"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this rack",
+ "maxLength": 50
+ },
+ "rack_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841",
+ "nullable": true
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "x-spec-enum-id": "9b322795f297a9c3"
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ ""
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front",
+ "x-spec-enum-id": "a784734d07ef1b3c"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "site"
+ ]
+ },
+ "RackReservation": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "rack": {
+ "$ref": "#/components/schemas/BriefRack"
+ },
+ "units": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ }
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "pending",
+ "active",
+ "stale"
+ ],
+ "type": "string",
+ "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale",
+ "x-spec-enum-id": "ed6038a4deee151c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Pending",
+ "Active",
+ "Stale"
+ ]
+ }
+ }
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "user": {
+ "$ref": "#/components/schemas/BriefUser"
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "created",
+ "description",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "rack",
+ "units",
+ "url",
+ "user"
+ ]
+ },
+ "RackReservationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ]
+ },
+ "units": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ }
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "active",
+ "stale"
+ ],
+ "type": "string",
+ "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale",
+ "x-spec-enum-id": "ed6038a4deee151c"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "description",
+ "rack",
+ "units",
+ "user"
+ ]
+ },
+ "RackRole": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "rack_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "rack_count",
+ "slug",
+ "url"
+ ]
+ },
+ "RackRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "RackType": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "manufacturer": {
+ "$ref": "#/components/schemas/BriefManufacturer"
+ },
+ "model": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "form_factor": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "2-post frame",
+ "4-post frame",
+ "4-post cabinet",
+ "Wall-mounted frame",
+ "Wall-mounted frame (vertical)",
+ "Wall-mounted cabinet",
+ "Wall-mounted cabinet (vertical)"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "width": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "x-spec-enum-id": "9b322795f297a9c3"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "10 inches",
+ "19 inches",
+ "21 inches",
+ "23 inches"
+ ]
+ }
+ }
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Millimeters",
+ "Inches"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Kilograms",
+ "Grams",
+ "Pounds",
+ "Ounces"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "rack_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "manufacturer",
+ "model",
+ "rack_count",
+ "slug",
+ "url"
+ ]
+ },
+ "RackTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841",
+ "nullable": true
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "description": "* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "x-spec-enum-id": "9b322795f297a9c3"
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "RackUnit": {
+ "type": "object",
+ "description": "A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.",
+ "properties": {
+ "id": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": -1000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "face": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "front",
+ "rear"
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Front",
+ "Rear"
+ ]
+ }
+ },
+ "readOnly": true
+ },
+ "device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDevice"
+ }
+ ],
+ "readOnly": true
+ },
+ "occupied": {
+ "type": "boolean",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "device",
+ "display",
+ "face",
+ "id",
+ "name",
+ "occupied"
+ ]
+ },
+ "RearPort": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "module": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModule"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "8P8C",
+ "8P6C",
+ "8P4C",
+ "8P2C",
+ "6P6C",
+ "6P4C",
+ "6P2C",
+ "4P4C",
+ "4P2C",
+ "GG45",
+ "TERA 4P",
+ "TERA 2P",
+ "TERA 1P",
+ "110 Punch",
+ "BNC",
+ "F Connector",
+ "N Connector",
+ "MRJ21",
+ "FC",
+ "FC/PC",
+ "FC/UPC",
+ "FC/APC",
+ "LC",
+ "LC/PC",
+ "LC/UPC",
+ "LC/APC",
+ "LSH",
+ "LSH/PC",
+ "LSH/UPC",
+ "LSH/APC",
+ "LX.5",
+ "LX.5/PC",
+ "LX.5/UPC",
+ "LX.5/APC",
+ "MPO",
+ "MTRJ",
+ "SC",
+ "SC/PC",
+ "SC/UPC",
+ "SC/APC",
+ "ST",
+ "CS",
+ "SN",
+ "SMA 905",
+ "SMA 906",
+ "URM-P2",
+ "URM-P4",
+ "URM-P8",
+ "Splice",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortMapping"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "cable": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCable"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "cable_end": {
+ "type": "string",
+ "readOnly": true
+ },
+ "link_peers": {
+ "type": "array",
+ "items": {},
+ "readOnly": true
+ },
+ "link_peers_type": {
+ "type": "string",
+ "description": "Return the type of the peer link terminations, or None.",
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "_occupied": {
+ "type": "boolean",
+ "readOnly": true,
+ "title": " occupied"
+ }
+ },
+ "required": [
+ "_occupied",
+ "cable",
+ "cable_end",
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "link_peers",
+ "link_peers_type",
+ "name",
+ "type",
+ "url"
+ ]
+ },
+ "RearPortMapping": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "front_port": {
+ "type": "integer"
+ },
+ "front_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "front_port",
+ "position"
+ ]
+ },
+ "RearPortMappingRequest": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "front_port": {
+ "type": "integer"
+ },
+ "front_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "front_port",
+ "position"
+ ]
+ },
+ "RearPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "RearPortTemplate": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "device_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceType"
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleType"
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "8P8C",
+ "8P6C",
+ "8P4C",
+ "8P2C",
+ "6P6C",
+ "6P4C",
+ "6P2C",
+ "4P4C",
+ "4P2C",
+ "GG45",
+ "TERA 4P",
+ "TERA 2P",
+ "TERA 1P",
+ "110 Punch",
+ "BNC",
+ "F Connector",
+ "N Connector",
+ "MRJ21",
+ "FC",
+ "FC/PC",
+ "FC/UPC",
+ "FC/APC",
+ "LC",
+ "LC/PC",
+ "LC/UPC",
+ "LC/APC",
+ "LSH",
+ "LSH/PC",
+ "LSH/UPC",
+ "LSH/APC",
+ "LX.5",
+ "LX.5/PC",
+ "LX.5/UPC",
+ "LX.5/APC",
+ "MPO",
+ "MTRJ",
+ "SC",
+ "SC/PC",
+ "SC/UPC",
+ "SC/APC",
+ "ST",
+ "CS",
+ "SN",
+ "SMA 905",
+ "SMA 906",
+ "URM-P2",
+ "URM-P4",
+ "URM-P8",
+ "Splice",
+ "USB Type A",
+ "USB Type B",
+ "USB Type C",
+ "USB Mini A",
+ "USB Mini B",
+ "USB Micro A",
+ "USB Micro B",
+ "USB Micro AB",
+ "Other"
+ ]
+ }
+ }
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortTemplateMapping"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "last_updated",
+ "name",
+ "type",
+ "url"
+ ]
+ },
+ "RearPortTemplateMapping": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "front_port": {
+ "type": "integer"
+ },
+ "front_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "front_port",
+ "position"
+ ]
+ },
+ "RearPortTemplateMappingRequest": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "integer"
+ },
+ "front_port": {
+ "type": "integer"
+ },
+ "front_port_position": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ "required": [
+ "front_port",
+ "position"
+ ]
+ },
+ "RearPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "Region": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedRegion"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "site_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "site_count",
+ "slug",
+ "url"
+ ]
+ },
+ "RegionRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedRegionRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "Role": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "vlan_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "slug",
+ "url",
+ "vlan_count"
+ ]
+ },
+ "RoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "RouteTarget": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "Route target value (formatted in accordance with RFC 4360)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "url"
+ ]
+ },
+ "RouteTargetRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Route target value (formatted in accordance with RFC 4360)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "SavedFilter": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "parameters": {},
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "object_types",
+ "parameters",
+ "slug",
+ "url"
+ ]
+ },
+ "SavedFilterRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "parameters": {},
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "name",
+ "object_types",
+ "parameters",
+ "slug"
+ ]
+ },
+ "Script": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "module": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "readOnly": true
+ },
+ "description": {
+ "type": "string",
+ "nullable": true,
+ "readOnly": true
+ },
+ "vars": {
+ "nullable": true,
+ "readOnly": true
+ },
+ "result": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefJob"
+ }
+ ],
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "is_executable": {
+ "type": "boolean",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "description",
+ "display",
+ "display_url",
+ "id",
+ "is_executable",
+ "module",
+ "name",
+ "result",
+ "url",
+ "vars"
+ ]
+ },
+ "ScriptInputRequest": {
+ "type": "object",
+ "properties": {
+ "data": {},
+ "commit": {
+ "type": "boolean"
+ },
+ "schedule_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "interval": {
+ "type": "integer",
+ "nullable": true
+ }
+ },
+ "required": [
+ "commit",
+ "data"
+ ]
+ },
+ "Service": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "parent_object_type": {
+ "type": "string"
+ },
+ "parent_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "parent": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "protocol": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "TCP",
+ "UDP",
+ "SCTP"
+ ]
+ }
+ }
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "ipaddresses": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IPAddress"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "parent",
+ "parent_object_id",
+ "parent_object_type",
+ "ports",
+ "url"
+ ]
+ },
+ "ServiceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "parent_object_type": {
+ "type": "string"
+ },
+ "parent_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "ipaddresses": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "parent_object_id",
+ "parent_object_type",
+ "ports"
+ ]
+ },
+ "ServiceTemplate": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "protocol": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "TCP",
+ "UDP",
+ "SCTP"
+ ]
+ }
+ }
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "ports",
+ "url"
+ ]
+ },
+ "ServiceTemplateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "ports"
+ ]
+ },
+ "Site": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "description": "Full name of the site",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Planned",
+ "Staging",
+ "Active",
+ "Decommissioning",
+ "Retired"
+ ]
+ }
+ }
+ },
+ "region": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRegion"
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "time_zone": {
+ "type": "string",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "physical_address": {
+ "type": "string",
+ "description": "Physical location of the building",
+ "maxLength": 200
+ },
+ "shipping_address": {
+ "type": "string",
+ "description": "If different from the physical address",
+ "maxLength": 200
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ASN"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "circuit_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "device_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "rack_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "virtualmachine_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "vlan_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "circuit_count",
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "rack_count",
+ "slug",
+ "url",
+ "virtualmachine_count",
+ "vlan_count"
+ ]
+ },
+ "SiteGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedSiteGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "site_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "site_count",
+ "slug",
+ "url"
+ ]
+ },
+ "SiteGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedSiteGroupRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "SiteRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full name of the site",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "region": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRegionRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "time_zone": {
+ "type": "string",
+ "nullable": true,
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "physical_address": {
+ "type": "string",
+ "description": "Physical location of the building",
+ "maxLength": 200
+ },
+ "shipping_address": {
+ "type": "string",
+ "description": "If different from the physical address",
+ "maxLength": 200
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "Subscription": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "object": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "user": {
+ "$ref": "#/components/schemas/BriefUser"
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "id",
+ "object",
+ "object_id",
+ "object_type",
+ "url",
+ "user"
+ ]
+ },
+ "SubscriptionRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ },
+ "required": [
+ "object_id",
+ "object_type",
+ "user"
+ ]
+ },
+ "TableConfig": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "table": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 100
+ }
+ },
+ "ordering": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "nullable": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "columns",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "object_type",
+ "table",
+ "url"
+ ]
+ },
+ "TableConfigRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "table": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "user": {
+ "type": "integer",
+ "nullable": true
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "shared": {
+ "type": "boolean"
+ },
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ }
+ },
+ "ordering": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "nullable": true
+ }
+ },
+ "required": [
+ "columns",
+ "name",
+ "object_type",
+ "table"
+ ]
+ },
+ "Tag": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "pattern": "^[-\\w]+$",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "tagged_items": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "tagged_items",
+ "url"
+ ]
+ },
+ "TagRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[-\\w]+$",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ },
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "TaggedItem": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_type": {
+ "type": "string",
+ "readOnly": true
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": -2147483648
+ },
+ "object": {
+ "readOnly": true
+ },
+ "tag": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTag"
+ }
+ ],
+ "readOnly": true
+ }
+ },
+ "required": [
+ "display",
+ "id",
+ "object",
+ "object_id",
+ "object_type",
+ "tag",
+ "url"
+ ]
+ },
+ "Tenant": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "circuit_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "device_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "ipaddress_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "rack_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "site_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "virtualmachine_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "vlan_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "vrf_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "cluster_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "circuit_count",
+ "cluster_count",
+ "created",
+ "device_count",
+ "display",
+ "display_url",
+ "id",
+ "ipaddress_count",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "rack_count",
+ "site_count",
+ "slug",
+ "url",
+ "virtualmachine_count",
+ "vlan_count",
+ "vrf_count"
+ ]
+ },
+ "TenantGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedTenantGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "tenant_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "tenant_count",
+ "url"
+ ]
+ },
+ "TenantGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedTenantGroupRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "TenantRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "Token": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - v1\n* `2` - v2",
+ "x-spec-enum-id": "b5df70f0bffd12cb",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "key": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true,
+ "description": "v2 token identification key"
+ },
+ "user": {
+ "$ref": "#/components/schemas/BriefUser"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "expires": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "last_used": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Disable to temporarily revoke this token without deleting it."
+ },
+ "write_enabled": {
+ "type": "boolean",
+ "description": "Permit create/update/delete operations using this key"
+ },
+ "pepper_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "ID of the cryptographic pepper used to hash the token (v2 only)"
+ },
+ "token": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "key",
+ "url",
+ "user"
+ ]
+ },
+ "TokenProvision": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - v1\n* `2` - v2",
+ "x-spec-enum-id": "b5df70f0bffd12cb",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "user": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefUser"
+ }
+ ],
+ "readOnly": true
+ },
+ "key": {
+ "type": "string",
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "expires": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "last_used": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Disable to temporarily revoke this token without deleting it."
+ },
+ "write_enabled": {
+ "type": "boolean",
+ "description": "Permit create/update/delete operations using this key"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "token": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "key",
+ "last_used",
+ "url",
+ "user"
+ ]
+ },
+ "TokenProvisionRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - v1\n* `2` - v2",
+ "x-spec-enum-id": "b5df70f0bffd12cb",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "expires": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Disable to temporarily revoke this token without deleting it."
+ },
+ "write_enabled": {
+ "type": "boolean",
+ "description": "Permit create/update/delete operations using this key"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "username": {
+ "type": "string",
+ "writeOnly": true,
+ "minLength": 1
+ },
+ "password": {
+ "type": "string",
+ "writeOnly": true,
+ "minLength": 1
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "password",
+ "username"
+ ]
+ },
+ "TokenRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - v1\n* `2` - v2",
+ "x-spec-enum-id": "b5df70f0bffd12cb",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "expires": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "last_used": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Disable to temporarily revoke this token without deleting it."
+ },
+ "write_enabled": {
+ "type": "boolean",
+ "description": "Permit create/update/delete operations using this key"
+ },
+ "pepper_id": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "ID of the cryptographic pepper used to hash the token (v2 only)"
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "user"
+ ]
+ },
+ "Tunnel": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "planned",
+ "active",
+ "disabled"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled",
+ "x-spec-enum-id": "2431ef62c418f485"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Planned",
+ "Active",
+ "Disabled"
+ ]
+ }
+ }
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTunnelGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "encapsulation": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "ipsec-transport",
+ "ipsec-tunnel",
+ "ip-ip",
+ "gre",
+ "wireguard",
+ "openvpn",
+ "l2tp",
+ "pptp"
+ ],
+ "type": "string",
+ "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP",
+ "x-spec-enum-id": "4f3254459f0e94f0"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "IPsec - Transport",
+ "IPsec - Tunnel",
+ "IP-in-IP",
+ "GRE",
+ "WireGuard",
+ "OpenVPN",
+ "L2TP",
+ "PPTP"
+ ]
+ }
+ }
+ },
+ "ipsec_profile": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPSecProfile"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "tunnel_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "terminations_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "encapsulation",
+ "id",
+ "last_updated",
+ "name",
+ "status",
+ "terminations_count",
+ "url"
+ ]
+ },
+ "TunnelGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "tunnel_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "tunnel_count",
+ "url"
+ ]
+ },
+ "TunnelGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "TunnelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "active",
+ "disabled"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled",
+ "x-spec-enum-id": "2431ef62c418f485"
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTunnelGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "encapsulation": {
+ "enum": [
+ "ipsec-transport",
+ "ipsec-tunnel",
+ "ip-ip",
+ "gre",
+ "wireguard",
+ "openvpn",
+ "l2tp",
+ "pptp"
+ ],
+ "type": "string",
+ "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP",
+ "x-spec-enum-id": "4f3254459f0e94f0"
+ },
+ "ipsec_profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPSecProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tunnel_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "encapsulation",
+ "name",
+ "status"
+ ]
+ },
+ "TunnelTermination": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "tunnel": {
+ "$ref": "#/components/schemas/BriefTunnel"
+ },
+ "role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Peer",
+ "Hub",
+ "Spoke"
+ ]
+ }
+ }
+ },
+ "termination_type": {
+ "type": "string"
+ },
+ "termination_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "termination": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "outside_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "role",
+ "termination",
+ "termination_type",
+ "tunnel",
+ "url"
+ ]
+ },
+ "TunnelTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "tunnel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefTunnelRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "termination_type": {
+ "type": "string"
+ },
+ "termination_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "outside_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "role",
+ "termination_type",
+ "tunnel"
+ ]
+ },
+ "User": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "username": {
+ "type": "string",
+ "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+ "pattern": "^[\\w.@+-]+$",
+ "maxLength": 150
+ },
+ "first_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "last_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "title": "Email address",
+ "maxLength": 254
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Active",
+ "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
+ },
+ "date_joined": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_login": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Group"
+ }
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ObjectPermission"
+ }
+ }
+ },
+ "required": [
+ "display",
+ "display_url",
+ "id",
+ "url",
+ "username"
+ ]
+ },
+ "UserRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "username": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+ "pattern": "^[\\w.@+-]+$",
+ "maxLength": 150
+ },
+ "password": {
+ "type": "string",
+ "writeOnly": true,
+ "minLength": 1,
+ "maxLength": 128
+ },
+ "first_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "last_name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "title": "Email address",
+ "maxLength": 254
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Active",
+ "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
+ },
+ "date_joined": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_login": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true
+ },
+ "groups": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "password",
+ "username"
+ ]
+ },
+ "VLAN": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "site": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSite"
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "ca933c38b935e547"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Reserved",
+ "Deprecated"
+ ]
+ }
+ }
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRole"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qinq_role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "svlan",
+ "cvlan",
+ null
+ ],
+ "type": "string",
+ "description": "* `svlan` - Service\n* `cvlan` - Customer",
+ "x-spec-enum-id": "fa0abd59fb1a7312"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Service",
+ "Customer"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "qinq_svlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "l2vpn_termination": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefL2VPNTermination"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "l2vpn_termination",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "url",
+ "vid"
+ ]
+ },
+ "VLANGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "scope": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "vid_ranges": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IntegerRange"
+ }
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "vlan_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "utilization": {
+ "type": "string",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "scope",
+ "slug",
+ "url",
+ "utilization",
+ "vlan_count"
+ ]
+ },
+ "VLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "vid_ranges": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/IntegerRangeRequest"
+ }
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "VLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "ca933c38b935e547"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qinq_role": {
+ "enum": [
+ "svlan",
+ "cvlan",
+ null
+ ],
+ "type": "string",
+ "description": "* `svlan` - Service\n* `cvlan` - Customer",
+ "x-spec-enum-id": "fa0abd59fb1a7312",
+ "nullable": true
+ },
+ "qinq_svlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVLANRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "vid"
+ ]
+ },
+ "VLANTranslationPolicy": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLANTranslationRule"
+ },
+ "readOnly": true
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "display",
+ "id",
+ "name",
+ "rules",
+ "url"
+ ]
+ },
+ "VLANTranslationPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "VLANTranslationRule": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "policy": {
+ "type": "integer"
+ },
+ "local_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Local VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "remote_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Remote VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "display",
+ "id",
+ "local_vid",
+ "policy",
+ "remote_vid",
+ "url"
+ ]
+ },
+ "VLANTranslationRuleRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "policy": {
+ "type": "integer"
+ },
+ "local_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Local VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "remote_vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "Remote VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "local_vid",
+ "policy",
+ "remote_vid"
+ ]
+ },
+ "VMInterface": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "virtual_machine": {
+ "$ref": "#/components/schemas/BriefVirtualMachine"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVMInterface"
+ }
+ ],
+ "nullable": true
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVMInterface"
+ }
+ ],
+ "nullable": true
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "mac_address": {
+ "type": "string",
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "mac_addresses": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BriefMACAddress"
+ },
+ "readOnly": true,
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ ""
+ ],
+ "type": "string",
+ "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)",
+ "x-spec-enum-id": "84129b71b974ebe5"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Access",
+ "Tagged",
+ "Tagged (All)",
+ "Q-in-Q (802.1ad)"
+ ]
+ }
+ }
+ },
+ "untagged_vlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/VLAN"
+ }
+ },
+ "qinq_svlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicy"
+ }
+ ],
+ "nullable": true
+ },
+ "vrf": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRF"
+ }
+ ],
+ "nullable": true
+ },
+ "l2vpn_termination": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefL2VPNTermination"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "count_ipaddresses": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "count_fhrp_groups": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "count_fhrp_groups",
+ "count_ipaddresses",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "l2vpn_termination",
+ "last_updated",
+ "mac_address",
+ "mac_addresses",
+ "name",
+ "url",
+ "virtual_machine"
+ ]
+ },
+ "VMInterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_machine": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVMInterfaceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "bridge": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedVMInterfaceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ ""
+ ],
+ "type": "string",
+ "description": "* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)",
+ "x-spec-enum-id": "84129b71b974ebe5"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "virtual_machine"
+ ]
+ },
+ "VRF": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "rd": {
+ "type": "string",
+ "nullable": true,
+ "title": "Route distinguisher",
+ "description": "Unique route distinguisher (as defined in RFC 4364)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "enforce_unique": {
+ "type": "boolean",
+ "title": "Enforce unique space",
+ "description": "Prevent duplicate prefixes/IP addresses within this VRF"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RouteTarget"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RouteTarget"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "ipaddress_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ },
+ "prefix_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "ipaddress_count",
+ "last_updated",
+ "name",
+ "prefix_count",
+ "url"
+ ]
+ },
+ "VRFRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "rd": {
+ "type": "string",
+ "nullable": true,
+ "title": "Route distinguisher",
+ "description": "Unique route distinguisher (as defined in RFC 4364)",
+ "maxLength": 21
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "enforce_unique": {
+ "type": "boolean",
+ "title": "Enforce unique space",
+ "description": "Prevent duplicate prefixes/IP addresses within this VRF"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "VirtualChassis": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "domain": {
+ "type": "string",
+ "maxLength": 30
+ },
+ "master": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDevice"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "member_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "members": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedDevice"
+ },
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "member_count",
+ "members",
+ "name",
+ "url"
+ ]
+ },
+ "VirtualChassisRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "domain": {
+ "type": "string",
+ "maxLength": 30
+ },
+ "master": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedDeviceRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "VirtualCircuit": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "cid": {
+ "type": "string",
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider_network": {
+ "$ref": "#/components/schemas/BriefProviderNetwork"
+ },
+ "provider_account": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccount"
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "$ref": "#/components/schemas/BriefVirtualCircuitType"
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Planned",
+ "Provisioning",
+ "Active",
+ "Offline",
+ "Deprovisioning",
+ "Decommissioned"
+ ]
+ }
+ }
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "cid",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "provider_network",
+ "type",
+ "url"
+ ]
+ },
+ "VirtualCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider_network": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderNetworkRequest"
+ }
+ ]
+ },
+ "provider_account": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccountRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "cid",
+ "provider_network",
+ "type"
+ ]
+ },
+ "VirtualCircuitTermination": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "virtual_circuit": {
+ "$ref": "#/components/schemas/BriefVirtualCircuit"
+ },
+ "role": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Peer",
+ "Hub",
+ "Spoke"
+ ]
+ }
+ }
+ },
+ "interface": {
+ "$ref": "#/components/schemas/BriefInterface"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "interface",
+ "last_updated",
+ "url",
+ "virtual_circuit"
+ ]
+ },
+ "VirtualCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_circuit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "interface": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "interface",
+ "virtual_circuit"
+ ]
+ },
+ "VirtualCircuitType": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "virtual_circuit_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url",
+ "virtual_circuit_count"
+ ]
+ },
+ "VirtualCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "VirtualDeviceContext": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "device": {
+ "$ref": "#/components/schemas/BriefDevice"
+ },
+ "identifier": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_ip4": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "planned",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline",
+ "x-spec-enum-id": "0e2c0919d51b83cb"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Planned",
+ "Offline"
+ ]
+ }
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "interface_count": {
+ "type": "integer",
+ "format": "int64",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "created",
+ "device",
+ "display",
+ "display_url",
+ "id",
+ "interface_count",
+ "last_updated",
+ "name",
+ "primary_ip",
+ "status",
+ "url"
+ ]
+ },
+ "VirtualDeviceContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "identifier": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline",
+ "x-spec-enum-id": "0e2c0919d51b83cb"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "status"
+ ]
+ },
+ "VirtualDisk": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "virtual_machine": {
+ "$ref": "#/components/schemas/BriefVirtualMachine"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "size": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "title": "Size (MB)"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "size",
+ "url",
+ "virtual_machine"
+ ]
+ },
+ "VirtualDiskRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_machine": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "size": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "title": "Size (MB)"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "size",
+ "virtual_machine"
+ ]
+ },
+ "VirtualMachineWithConfigContext": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning",
+ "paused"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused",
+ "x-spec-enum-id": "effecc3b94e0b74b"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Offline",
+ "Active",
+ "Planned",
+ "Staged",
+ "Failed",
+ "Decommissioning",
+ "Paused"
+ ]
+ }
+ }
+ },
+ "start_on_boot": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "on",
+ "off",
+ "laststate"
+ ],
+ "type": "string",
+ "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State",
+ "x-spec-enum-id": "610e33fc2fde73d6"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "On",
+ "Off",
+ "Last State"
+ ]
+ }
+ }
+ },
+ "site": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSite"
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCluster"
+ }
+ ],
+ "nullable": true
+ },
+ "device": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDevice"
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "role": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRole"
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatform"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "readOnly": true,
+ "nullable": true
+ },
+ "primary_ip4": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddress"
+ }
+ ],
+ "nullable": true
+ },
+ "vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Memory (MB)"
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Disk (MB)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplate"
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "config_context": {
+ "nullable": true,
+ "readOnly": true
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "interface_count": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "virtual_disk_count": {
+ "type": "integer",
+ "readOnly": true
+ }
+ },
+ "required": [
+ "config_context",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "interface_count",
+ "last_updated",
+ "name",
+ "primary_ip",
+ "url",
+ "virtual_disk_count"
+ ]
+ },
+ "VirtualMachineWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning",
+ "paused"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused",
+ "x-spec-enum-id": "effecc3b94e0b74b"
+ },
+ "start_on_boot": {
+ "enum": [
+ "on",
+ "off",
+ "laststate"
+ ],
+ "type": "string",
+ "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State",
+ "x-spec-enum-id": "610e33fc2fde73d6"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Memory (MB)"
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Disk (MB)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "Webhook": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "payload_url": {
+ "type": "string",
+ "title": "URL",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "maxLength": 500
+ },
+ "http_method": {
+ "enum": [
+ "GET",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE"
+ ],
+ "type": "string",
+ "description": "* `GET` - GET\n* `POST` - POST\n* `PUT` - PUT\n* `PATCH` - PATCH\n* `DELETE` - DELETE",
+ "x-spec-enum-id": "867bf764d3b1eeaa"
+ },
+ "http_content_type": {
+ "type": "string",
+ "description": "The complete list of official content types is available here.",
+ "maxLength": 100
+ },
+ "additional_headers": {
+ "type": "string",
+ "description": "User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. Headers should be defined in the format Name: Value. Jinja2 template processing is supported with the same context as the request body (below)."
+ },
+ "body_template": {
+ "type": "string",
+ "description": "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be included. Available context data includes: event, model, timestamp, username, request_id, and data."
+ },
+ "secret": {
+ "type": "string",
+ "description": "When provided, the request will include a X-Hook-Signature header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.",
+ "maxLength": 255
+ },
+ "ssl_verification": {
+ "type": "boolean",
+ "description": "Enable SSL certificate verification. Disable with caution!"
+ },
+ "ca_file_path": {
+ "type": "string",
+ "nullable": true,
+ "description": "The specific CA certificate file to use for SSL verification. Leave blank to use the system defaults.",
+ "maxLength": 4096
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "payload_url",
+ "url"
+ ]
+ },
+ "WebhookRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "payload_url": {
+ "type": "string",
+ "minLength": 1,
+ "title": "URL",
+ "description": "This URL will be called using the HTTP method defined when the webhook is called. Jinja2 template processing is supported with the same context as the request body.",
+ "maxLength": 500
+ },
+ "http_method": {
+ "enum": [
+ "GET",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE"
+ ],
+ "type": "string",
+ "description": "* `GET` - GET\n* `POST` - POST\n* `PUT` - PUT\n* `PATCH` - PATCH\n* `DELETE` - DELETE",
+ "x-spec-enum-id": "867bf764d3b1eeaa"
+ },
+ "http_content_type": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The complete list of official content types is available here.",
+ "maxLength": 100
+ },
+ "additional_headers": {
+ "type": "string",
+ "description": "User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. Headers should be defined in the format Name: Value. Jinja2 template processing is supported with the same context as the request body (below)."
+ },
+ "body_template": {
+ "type": "string",
+ "description": "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be included. Available context data includes: event, model, timestamp, username, request_id, and data."
+ },
+ "secret": {
+ "type": "string",
+ "description": "When provided, the request will include a X-Hook-Signature header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.",
+ "maxLength": 255
+ },
+ "ssl_verification": {
+ "type": "boolean",
+ "description": "Enable SSL certificate verification. Disable with caution!"
+ },
+ "ca_file_path": {
+ "type": "string",
+ "nullable": true,
+ "description": "The specific CA certificate file to use for SSL verification. Leave blank to use the system defaults.",
+ "maxLength": 4096
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ },
+ "required": [
+ "name",
+ "payload_url"
+ ]
+ },
+ "WirelessLAN": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "group": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefWirelessLANGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "active",
+ "reserved",
+ "disabled",
+ "deprecated",
+ ""
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "e5549d7370ce2e6c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Active",
+ "Reserved",
+ "Disabled",
+ "Deprecated"
+ ]
+ }
+ }
+ },
+ "vlan": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLAN"
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "scope": {
+ "readOnly": true,
+ "nullable": true
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ ""
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Open",
+ "WEP",
+ "WPA Personal (PSK)",
+ "WPA Enterprise"
+ ]
+ }
+ }
+ },
+ "auth_cipher": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ ""
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Auto",
+ "TKIP",
+ "AES"
+ ]
+ }
+ }
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "scope",
+ "ssid",
+ "url"
+ ]
+ },
+ "WirelessLANGroup": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedWirelessLANGroup"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "wirelesslan_count": {
+ "type": "integer",
+ "readOnly": true,
+ "default": 0
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "_depth": {
+ "type": "integer",
+ "readOnly": true,
+ "title": " depth"
+ }
+ },
+ "required": [
+ "_depth",
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "last_updated",
+ "name",
+ "slug",
+ "url",
+ "wirelesslan_count"
+ ]
+ },
+ "WirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NestedWirelessLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WirelessLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "ssid": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefWirelessLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "disabled",
+ "deprecated",
+ ""
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "e5549d7370ce2e6c"
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ ""
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ ""
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "ssid"
+ ]
+ },
+ "WirelessLink": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "readOnly": true
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display_url": {
+ "type": "string",
+ "format": "uri",
+ "readOnly": true
+ },
+ "display": {
+ "type": "string",
+ "readOnly": true
+ },
+ "interface_a": {
+ "$ref": "#/components/schemas/BriefInterface"
+ },
+ "interface_b": {
+ "$ref": "#/components/schemas/BriefInterface"
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "status": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Connected",
+ "Planned",
+ "Decommissioning"
+ ]
+ }
+ }
+ },
+ "tenant": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenant"
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ ""
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Open",
+ "WEP",
+ "WPA Personal (PSK)",
+ "WPA Enterprise"
+ ]
+ }
+ }
+ },
+ "auth_cipher": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ ""
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Auto",
+ "TKIP",
+ "AES"
+ ]
+ }
+ }
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e"
+ },
+ "label": {
+ "type": "string",
+ "enum": [
+ "Kilometers",
+ "Meters",
+ "Miles",
+ "Feet"
+ ]
+ }
+ },
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwner"
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTag"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ },
+ "last_updated": {
+ "type": "string",
+ "format": "date-time",
+ "readOnly": true,
+ "nullable": true
+ }
+ },
+ "required": [
+ "created",
+ "display",
+ "display_url",
+ "id",
+ "interface_a",
+ "interface_b",
+ "last_updated",
+ "url"
+ ]
+ },
+ "WirelessLinkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "interface_a": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "interface_b": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "status": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ ""
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ ""
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "interface_a",
+ "interface_b"
+ ]
+ },
+ "WritableAggregateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "minLength": 1
+ },
+ "rir": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRIRRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "date_added": {
+ "type": "string",
+ "format": "date",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "prefix",
+ "rir"
+ ]
+ },
+ "WritableCableRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "type": {
+ "enum": [
+ "cat3",
+ "cat5",
+ "cat5e",
+ "cat6",
+ "cat6a",
+ "cat7",
+ "cat7a",
+ "cat8",
+ "mrj21-trunk",
+ "dac-active",
+ "dac-passive",
+ "coaxial",
+ "rg-6",
+ "rg-8",
+ "rg-11",
+ "rg-59",
+ "rg-62",
+ "rg-213",
+ "lmr-100",
+ "lmr-200",
+ "lmr-400",
+ "mmf",
+ "mmf-om1",
+ "mmf-om2",
+ "mmf-om3",
+ "mmf-om4",
+ "mmf-om5",
+ "smf",
+ "smf-os1",
+ "smf-os2",
+ "aoc",
+ "power",
+ "usb",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `cat3` - CAT3\n* `cat5` - CAT5\n* `cat5e` - CAT5e\n* `cat6` - CAT6\n* `cat6a` - CAT6a\n* `cat7` - CAT7\n* `cat7a` - CAT7a\n* `cat8` - CAT8\n* `mrj21-trunk` - MRJ21 Trunk\n* `dac-active` - Direct Attach Copper (Active)\n* `dac-passive` - Direct Attach Copper (Passive)\n* `coaxial` - Coaxial\n* `rg-6` - RG-6\n* `rg-8` - RG-8\n* `rg-11` - RG-11\n* `rg-59` - RG-59\n* `rg-62` - RG-62\n* `rg-213` - RG-213\n* `lmr-100` - LMR-100\n* `lmr-200` - LMR-200\n* `lmr-400` - LMR-400\n* `mmf` - Multimode Fiber\n* `mmf-om1` - Multimode Fiber (OM1)\n* `mmf-om2` - Multimode Fiber (OM2)\n* `mmf-om3` - Multimode Fiber (OM3)\n* `mmf-om4` - Multimode Fiber (OM4)\n* `mmf-om5` - Multimode Fiber (OM5)\n* `smf` - Single-mode Fiber\n* `smf-os1` - Single-mode Fiber (OS1)\n* `smf-os2` - Single-mode Fiber (OS2)\n* `aoc` - Active Optical Cabling (AOC)\n* `power` - Power\n* `usb` - USB",
+ "x-spec-enum-id": "3d4d8d7ae24f7be8",
+ "nullable": true
+ },
+ "a_terminations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GenericObjectRequest"
+ }
+ },
+ "b_terminations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GenericObjectRequest"
+ }
+ },
+ "status": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "profile": {
+ "enum": [
+ "single-1c1p",
+ "single-1c2p",
+ "single-1c4p",
+ "single-1c6p",
+ "single-1c8p",
+ "single-1c12p",
+ "single-1c16p",
+ "trunk-2c1p",
+ "trunk-2c2p",
+ "trunk-2c4p",
+ "trunk-2c4p-shuffle",
+ "trunk-2c6p",
+ "trunk-2c8p",
+ "trunk-2c12p",
+ "trunk-4c1p",
+ "trunk-4c2p",
+ "trunk-4c4p",
+ "trunk-4c4p-shuffle",
+ "trunk-4c6p",
+ "trunk-4c8p",
+ "trunk-8c4p",
+ "breakout-1c4p-4c1p",
+ "breakout-1c6p-6c1p",
+ "breakout-2c4p-8c1p-shuffle",
+ ""
+ ],
+ "type": "string",
+ "description": "* `single-1c1p` - 1C1P\n* `single-1c2p` - 1C2P\n* `single-1c4p` - 1C4P\n* `single-1c6p` - 1C6P\n* `single-1c8p` - 1C8P\n* `single-1c12p` - 1C12P\n* `single-1c16p` - 1C16P\n* `trunk-2c1p` - 2C1P trunk\n* `trunk-2c2p` - 2C2P trunk\n* `trunk-2c4p` - 2C4P trunk\n* `trunk-2c4p-shuffle` - 2C4P trunk (shuffle)\n* `trunk-2c6p` - 2C6P trunk\n* `trunk-2c8p` - 2C8P trunk\n* `trunk-2c12p` - 2C12P trunk\n* `trunk-4c1p` - 4C1P trunk\n* `trunk-4c2p` - 4C2P trunk\n* `trunk-4c4p` - 4C4P trunk\n* `trunk-4c4p-shuffle` - 4C4P trunk (shuffle)\n* `trunk-4c6p` - 4C6P trunk\n* `trunk-4c8p` - 4C8P trunk\n* `trunk-8c4p` - 8C4P trunk\n* `breakout-1c4p-4c1p` - 1C4P:4C1P breakout\n* `breakout-1c6p-6c1p` - 1C6P:6C1P breakout\n* `breakout-2c4p-8c1p-shuffle` - 2C4P:8C1P breakout (shuffle)",
+ "x-spec-enum-id": "5e0f85310f0184ea"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "label": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "length": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "length_unit": {
+ "enum": [
+ "km",
+ "m",
+ "cm",
+ "mi",
+ "ft",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `cm` - Centimeters\n* `mi` - Miles\n* `ft` - Feet\n* `in` - Inches",
+ "x-spec-enum-id": "6e7645525ba02462",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ },
+ "WritableCircuitGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Base serializer for group assignments under CircuitSerializer.",
+ "properties": {
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefCircuitGroupRequest"
+ }
+ ]
+ },
+ "member_type": {
+ "type": "string"
+ },
+ "member_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "priority": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ },
+ "required": [
+ "group",
+ "member_id",
+ "member_type"
+ ]
+ },
+ "WritableCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderRequest"
+ }
+ ]
+ },
+ "provider_account": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccountRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefCircuitTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "install_date": {
+ "type": "string",
+ "format": "date",
+ "nullable": true,
+ "title": "Installed"
+ },
+ "termination_date": {
+ "type": "string",
+ "format": "date",
+ "nullable": true,
+ "title": "Terminates"
+ },
+ "commit_rate": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Commit rate (Kbps)",
+ "description": "Committed rate"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e",
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "assignments": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BriefCircuitGroupAssignmentSerializer_Request"
+ }
+ }
+ },
+ "required": [
+ "cid",
+ "provider",
+ "type"
+ ]
+ },
+ "WritableClusterRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefClusterTypeRequest"
+ }
+ ]
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `offline` - Offline",
+ "x-spec-enum-id": "65a25166053759eb"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "WritableConsolePortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true,
+ "description": "Physical port type\n\n* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true,
+ "description": "Port speed in bits per second\n\n* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "minimum": 0,
+ "maximum": 2147483647
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "WritableConsolePortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableConsoleServerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true,
+ "description": "Physical port type\n\n* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other"
+ },
+ "speed": {
+ "enum": [
+ 1200,
+ 2400,
+ 4800,
+ 9600,
+ 19200,
+ 38400,
+ 57600,
+ 115200,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "ab6d9635c131a378",
+ "nullable": true,
+ "description": "Port speed in bits per second\n\n* `1200` - 1200 bps\n* `2400` - 2400 bps\n* `4800` - 4800 bps\n* `9600` - 9600 bps\n* `19200` - 19.2 kbps\n* `38400` - 38.4 kbps\n* `57600` - 57.6 kbps\n* `115200` - 115.2 kbps",
+ "minimum": 0,
+ "maximum": 2147483647
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "WritableConsoleServerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "de-9",
+ "db-25",
+ "rj-11",
+ "rj-12",
+ "rj-45",
+ "mini-din-8",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `de-9` - DE-9\n* `db-25` - DB-25\n* `rj-11` - RJ-11\n* `rj-12` - RJ-12\n* `rj-45` - RJ-45\n* `mini-din-8` - Mini-DIN 8\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "7b8d0e83a4bb5178",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableContactAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "contact": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefContactRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefContactRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "priority": {
+ "enum": [
+ "primary",
+ "secondary",
+ "tertiary",
+ "inactive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `secondary` - Secondary\n* `tertiary` - Tertiary\n* `inactive` - Inactive",
+ "x-spec-enum-id": "0548fc537440bf9d",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "contact",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "WritableContactGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableCustomFieldChoiceSetRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "base_choices": {
+ "enum": [
+ "IATA",
+ "ISO_3166",
+ "UN_LOCODE",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "cf0efb5195f85007",
+ "nullable": true,
+ "description": "Base set of predefined choices (optional)\n\n* `IATA` - IATA (Airport codes)\n* `ISO_3166` - ISO 3166 (Country codes)\n* `UN_LOCODE` - UN/LOCODE (Location codes)"
+ },
+ "extra_choices": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {},
+ "maxItems": 2,
+ "minItems": 2
+ }
+ },
+ "order_alphabetically": {
+ "type": "boolean",
+ "description": "Choices are automatically ordered alphabetically"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "extra_choices",
+ "name"
+ ]
+ },
+ "WritableCustomFieldRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "type": {
+ "enum": [
+ "text",
+ "longtext",
+ "integer",
+ "decimal",
+ "boolean",
+ "date",
+ "datetime",
+ "url",
+ "json",
+ "select",
+ "multiselect",
+ "object",
+ "multiobject"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "47c52a3d983e924c",
+ "description": "The type of data this custom field holds\n\n* `text` - Text\n* `longtext` - Text (long)\n* `integer` - Integer\n* `decimal` - Decimal\n* `boolean` - Boolean (true/false)\n* `date` - Date\n* `datetime` - Date & time\n* `url` - URL\n* `json` - JSON\n* `select` - Selection\n* `multiselect` - Multiple selection\n* `object` - Object\n* `multiobject` - Multiple objects"
+ },
+ "related_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Internal field name",
+ "pattern": "^[a-z0-9_]+$",
+ "maxLength": 50
+ },
+ "label": {
+ "type": "string",
+ "description": "Name of the field as displayed to users (if not provided, 'the field's name will be used)",
+ "maxLength": 50
+ },
+ "group_name": {
+ "type": "string",
+ "description": "Custom fields within the same group will be displayed together",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "required": {
+ "type": "boolean",
+ "description": "This field is required when creating new objects or editing an existing object."
+ },
+ "unique": {
+ "type": "boolean",
+ "title": "Must be unique",
+ "description": "The value of this field must be unique for the assigned object"
+ },
+ "search_weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "description": "Weighting for search. Lower values are considered more important. Fields with a search weight of zero will be ignored."
+ },
+ "filter_logic": {
+ "enum": [
+ "disabled",
+ "loose",
+ "exact"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "d168820c798ae45a",
+ "description": "Loose matches any instance of a given string; exact matches the entire field.\n\n* `disabled` - Disabled\n* `loose` - Loose\n* `exact` - Exact"
+ },
+ "ui_visible": {
+ "enum": [
+ "always",
+ "if-set",
+ "hidden"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "f32800c399b927b6",
+ "description": "Specifies whether the custom field is displayed in the UI\n\n* `always` - Always\n* `if-set` - If set\n* `hidden` - Hidden"
+ },
+ "ui_editable": {
+ "enum": [
+ "yes",
+ "no",
+ "hidden"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "336f52760e62022f",
+ "description": "Specifies whether the custom field value can be edited in the UI\n\n* `yes` - Yes\n* `no` - No\n* `hidden` - Hidden"
+ },
+ "is_cloneable": {
+ "type": "boolean",
+ "description": "Replicate this value when cloning objects"
+ },
+ "default": {
+ "nullable": true,
+ "description": "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "related_object_filter": {
+ "nullable": true,
+ "description": "Filter the object selection choices using a query_params dict (must be a JSON value).Encapsulate strings with double quotes (e.g. \"Foo\")."
+ },
+ "weight": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "title": "Display weight",
+ "description": "Fields with higher weights appear lower in a form."
+ },
+ "validation_minimum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Minimum value",
+ "description": "Minimum allowed value (for numeric fields)"
+ },
+ "validation_maximum": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000000000,
+ "minimum": -1000000000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Maximum value",
+ "description": "Maximum allowed value (for numeric fields)"
+ },
+ "validation_regex": {
+ "type": "string",
+ "description": "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.",
+ "maxLength": 500
+ },
+ "choice_set": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCustomFieldChoiceSetRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "object_types"
+ ]
+ },
+ "WritableDataSourceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "type": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50
+ },
+ "source_url": {
+ "type": "string",
+ "minLength": 1,
+ "title": "URL",
+ "maxLength": 200
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "sync_interval": {
+ "enum": [
+ 1,
+ 60,
+ 720,
+ 1440,
+ 10080,
+ 43200,
+ null
+ ],
+ "type": "integer",
+ "description": "* `1` - Minutely\n* `60` - Hourly\n* `720` - 12 hours\n* `1440` - Daily\n* `10080` - Weekly\n* `43200` - 30 days",
+ "x-spec-enum-id": "2e9f2567ecd93fbe",
+ "nullable": true,
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "parameters": {
+ "nullable": true
+ },
+ "ignore_rules": {
+ "type": "string",
+ "description": "Patterns (one per line) matching files to ignore when syncing"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "source_url",
+ "type"
+ ]
+ },
+ "WritableDeviceRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "color": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "vm_role": {
+ "type": "boolean",
+ "description": "Virtual machines may be assigned to this role"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableDeviceTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "default_platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "u_height": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.0,
+ "exclusiveMaximum": true,
+ "default": 1.0,
+ "title": "Position (U)"
+ },
+ "exclude_from_utilization": {
+ "type": "boolean",
+ "description": "Devices of this type are excluded when calculating rack utilization."
+ },
+ "is_full_depth": {
+ "type": "boolean",
+ "description": "Device consumes both front and rear rack faces."
+ },
+ "subdevice_role": {
+ "enum": [
+ "parent",
+ "child",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "65a61d5e1deb4a24",
+ "nullable": true,
+ "title": "Parent/child status",
+ "description": "Parent devices house child devices in device bays. Leave blank if this device type is neither a parent nor a child.\n\n* `parent` - Parent\n* `child` - Child"
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "front_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "rear_image": {
+ "type": "string",
+ "format": "binary",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "WritableDeviceWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 64
+ },
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ]
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "description": "Chassis serial number, assigned by the manufacturer",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "position": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000,
+ "minimum": 0.5,
+ "exclusiveMaximum": true,
+ "nullable": true,
+ "title": "Position (U)"
+ },
+ "face": {
+ "enum": [
+ "front",
+ "rear",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front` - Front\n* `rear` - Rear",
+ "x-spec-enum-id": "d2fb9b3f75158b83",
+ "nullable": true,
+ "title": "Rack face"
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "inventory",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `inventory` - Inventory\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "65feb4244cc9110c"
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "rear-to-side",
+ "bottom-to-top",
+ "top-to-bottom",
+ "passive",
+ "mixed",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `rear-to-side` - Rear to side\n* `bottom-to-top` - Bottom to top\n* `top-to-bottom` - Top to bottom\n* `passive` - Passive\n* `mixed` - Mixed",
+ "x-spec-enum-id": "11cb3d363b41ba9e",
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "oob_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "virtual_chassis": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualChassisRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vc_position": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true
+ },
+ "vc_priority": {
+ "type": "integer",
+ "maximum": 255,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Virtual chassis master election priority"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device_type",
+ "role",
+ "site"
+ ]
+ },
+ "WritableEventRuleRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "object_types": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "event_types": {
+ "type": "array",
+ "items": {
+ "enum": [
+ "object_created",
+ "object_updated",
+ "object_deleted",
+ "job_started",
+ "job_completed",
+ "job_failed",
+ "job_errored"
+ ],
+ "type": "string",
+ "description": "* `object_created` - Object created\n* `object_updated` - Object updated\n* `object_deleted` - Object deleted\n* `job_started` - Job started\n* `job_completed` - Job completed\n* `job_failed` - Job failed\n* `job_errored` - Job errored",
+ "x-spec-enum-id": "01e557313a5c7bd2"
+ },
+ "description": "The types of event which will trigger this rule."
+ },
+ "conditions": {
+ "nullable": true,
+ "description": "A set of conditions which determine whether the event will be generated."
+ },
+ "action_type": {
+ "enum": [
+ "webhook",
+ "script",
+ "notification"
+ ],
+ "type": "string",
+ "description": "* `webhook` - Webhook\n* `script` - Script\n* `notification` - Notification",
+ "x-spec-enum-id": "287901b937995956"
+ },
+ "action_object_type": {
+ "type": "string"
+ },
+ "action_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ }
+ },
+ "required": [
+ "action_object_type",
+ "event_types",
+ "name",
+ "object_types"
+ ]
+ },
+ "WritableFrontPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "WritableFrontPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "rear_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FrontPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "WritableIKEPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "version": {
+ "enum": [
+ 1,
+ 2
+ ],
+ "type": "integer",
+ "description": "* `1` - IKEv1\n* `2` - IKEv2",
+ "x-spec-enum-id": "00872b77916a1fde",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "mode": {
+ "enum": [
+ "aggressive",
+ "main",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `aggressive` - Aggressive\n* `main` - Main",
+ "x-spec-enum-id": "64c1be7bdb2548ca",
+ "nullable": true
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "preshared_key": {
+ "type": "string",
+ "title": "Pre-shared key"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableIKEProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "authentication_method": {
+ "enum": [
+ "preshared-keys",
+ "certificates",
+ "rsa-signatures",
+ "dsa-signatures"
+ ],
+ "type": "string",
+ "description": "* `preshared-keys` - Pre-shared keys\n* `certificates` - Certificates\n* `rsa-signatures` - RSA signatures\n* `dsa-signatures` - DSA signatures",
+ "x-spec-enum-id": "a21158c52d0c455a"
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc"
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7",
+ "nullable": true
+ },
+ "group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "dbef43be795462a8",
+ "description": "Diffie-Hellman group ID\n\n* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "sa_lifetime": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Security association lifetime (in seconds)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "authentication_method",
+ "encryption_algorithm",
+ "group",
+ "name"
+ ]
+ },
+ "WritableIPAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated",
+ "dhcp",
+ "slaac"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "c421c4c4a0fa7a2a",
+ "description": "The operational status of this IP\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated\n* `dhcp` - DHCP\n* `slaac` - SLAAC"
+ },
+ "role": {
+ "enum": [
+ "loopback",
+ "secondary",
+ "anycast",
+ "vip",
+ "vrrp",
+ "hsrp",
+ "glbp",
+ "carp",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "53dca4cddd7b344a",
+ "nullable": true,
+ "description": "The functional role of this IP\n\n* `loopback` - Loopback\n* `secondary` - Secondary\n* `anycast` - Anycast\n* `vip` - VIP\n* `vrrp` - VRRP\n* `hsrp` - HSRP\n* `glbp` - GLBP\n* `carp` - CARP"
+ },
+ "assigned_object_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "nat_inside": {
+ "type": "integer",
+ "nullable": true,
+ "title": "NAT (inside)",
+ "description": "The IP for which this address is the \"outside\" IP"
+ },
+ "dns_name": {
+ "type": "string",
+ "description": "Hostname or FQDN (not case-sensitive)",
+ "pattern": "^([0-9A-Za-z_-]+|\\*)(\\.[0-9A-Za-z_-]+)*\\.?$",
+ "maxLength": 255
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "address"
+ ]
+ },
+ "WritableIPRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "start_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "end_address": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "ca933c38b935e547",
+ "description": "Operational status of this range\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "mark_populated": {
+ "type": "boolean",
+ "description": "Prevent the creation of IP addresses within this range"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Report space as fully utilized"
+ }
+ },
+ "required": [
+ "end_address",
+ "start_address"
+ ]
+ },
+ "WritableIPSecPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "proposals": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "pfs_group": {
+ "enum": [
+ 1,
+ 2,
+ 5,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ null
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "dbef43be795462a8",
+ "nullable": true,
+ "description": "Diffie-Hellman group for Perfect Forward Secrecy\n\n* `1` - Group 1\n* `2` - Group 2\n* `5` - Group 5\n* `14` - Group 14\n* `15` - Group 15\n* `16` - Group 16\n* `17` - Group 17\n* `18` - Group 18\n* `19` - Group 19\n* `20` - Group 20\n* `21` - Group 21\n* `22` - Group 22\n* `23` - Group 23\n* `24` - Group 24\n* `25` - Group 25\n* `26` - Group 26\n* `27` - Group 27\n* `28` - Group 28\n* `29` - Group 29\n* `30` - Group 30\n* `31` - Group 31\n* `32` - Group 32\n* `33` - Group 33\n* `34` - Group 34",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableIPSecProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "esp",
+ "ah"
+ ],
+ "type": "string",
+ "description": "* `esp` - ESP\n* `ah` - AH",
+ "x-spec-enum-id": "87ac6ada0da14ccf"
+ },
+ "ike_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIKEPolicyRequest"
+ }
+ ]
+ },
+ "ipsec_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefIPSecPolicyRequest"
+ }
+ ]
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "ike_policy",
+ "ipsec_policy",
+ "mode",
+ "name"
+ ]
+ },
+ "WritableIPSecProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "encryption_algorithm": {
+ "enum": [
+ "aes-128-cbc",
+ "aes-128-gcm",
+ "aes-192-cbc",
+ "aes-192-gcm",
+ "aes-256-cbc",
+ "aes-256-gcm",
+ "3des-cbc",
+ "des-cbc",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `aes-128-cbc` - 128-bit AES (CBC)\n* `aes-128-gcm` - 128-bit AES (GCM)\n* `aes-192-cbc` - 192-bit AES (CBC)\n* `aes-192-gcm` - 192-bit AES (GCM)\n* `aes-256-cbc` - 256-bit AES (CBC)\n* `aes-256-gcm` - 256-bit AES (GCM)\n* `3des-cbc` - 3DES\n* `des-cbc` - DES",
+ "x-spec-enum-id": "ae3dabd7b2b3cba2",
+ "nullable": true,
+ "title": "Encryption"
+ },
+ "authentication_algorithm": {
+ "enum": [
+ "hmac-sha1",
+ "hmac-sha256",
+ "hmac-sha384",
+ "hmac-sha512",
+ "hmac-md5",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `hmac-sha1` - SHA-1 HMAC\n* `hmac-sha256` - SHA-256 HMAC\n* `hmac-sha384` - SHA-384 HMAC\n* `hmac-sha512` - SHA-512 HMAC\n* `hmac-md5` - MD5 HMAC",
+ "x-spec-enum-id": "0a7ca69695b483a7",
+ "nullable": true,
+ "title": "Authentication"
+ },
+ "sa_lifetime_seconds": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (seconds)",
+ "description": "Security association lifetime (seconds)"
+ },
+ "sa_lifetime_data": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "SA lifetime (KB)",
+ "description": "Security association lifetime (in kilobytes)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableInterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "vdcs": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent interface"
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "lag": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent LAG"
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "speed": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Speed (Kbps)"
+ },
+ "duplex": {
+ "enum": [
+ "half",
+ "full",
+ "auto",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `half` - Half\n* `full` - Full\n* `auto` - Auto",
+ "x-spec-enum-id": "368458a2b67c916b",
+ "nullable": true
+ },
+ "wwn": {
+ "type": "string",
+ "nullable": true
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only",
+ "description": "This interface is used only for out-of-band management"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "84129b71b974ebe5",
+ "nullable": true,
+ "description": "IEEE 802.1Q tagging strategy\n\n* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)"
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1",
+ "nullable": true,
+ "title": "Wireless role"
+ },
+ "rf_channel": {
+ "enum": [
+ "2.4g-1-2412-22",
+ "2.4g-2-2417-22",
+ "2.4g-3-2422-22",
+ "2.4g-4-2427-22",
+ "2.4g-5-2432-22",
+ "2.4g-6-2437-22",
+ "2.4g-7-2442-22",
+ "2.4g-8-2447-22",
+ "2.4g-9-2452-22",
+ "2.4g-10-2457-22",
+ "2.4g-11-2462-22",
+ "2.4g-12-2467-22",
+ "2.4g-13-2472-22",
+ "5g-32-5160-20",
+ "5g-34-5170-40",
+ "5g-36-5180-20",
+ "5g-38-5190-40",
+ "5g-40-5200-20",
+ "5g-42-5210-80",
+ "5g-44-5220-20",
+ "5g-46-5230-40",
+ "5g-48-5240-20",
+ "5g-50-5250-160",
+ "5g-52-5260-20",
+ "5g-54-5270-40",
+ "5g-56-5280-20",
+ "5g-58-5290-80",
+ "5g-60-5300-20",
+ "5g-62-5310-40",
+ "5g-64-5320-20",
+ "5g-100-5500-20",
+ "5g-102-5510-40",
+ "5g-104-5520-20",
+ "5g-106-5530-80",
+ "5g-108-5540-20",
+ "5g-110-5550-40",
+ "5g-112-5560-20",
+ "5g-114-5570-160",
+ "5g-116-5580-20",
+ "5g-118-5590-40",
+ "5g-120-5600-20",
+ "5g-122-5610-80",
+ "5g-124-5620-20",
+ "5g-126-5630-40",
+ "5g-128-5640-20",
+ "5g-132-5660-20",
+ "5g-134-5670-40",
+ "5g-136-5680-20",
+ "5g-138-5690-80",
+ "5g-140-5700-20",
+ "5g-142-5710-40",
+ "5g-144-5720-20",
+ "5g-149-5745-20",
+ "5g-151-5755-40",
+ "5g-153-5765-20",
+ "5g-155-5775-80",
+ "5g-157-5785-20",
+ "5g-159-5795-40",
+ "5g-161-5805-20",
+ "5g-163-5815-160",
+ "5g-165-5825-20",
+ "5g-167-5835-40",
+ "5g-169-5845-20",
+ "5g-171-5855-80",
+ "5g-173-5865-20",
+ "5g-175-5875-40",
+ "5g-177-5885-20",
+ "6g-1-5955-20",
+ "6g-3-5965-40",
+ "6g-5-5975-20",
+ "6g-7-5985-80",
+ "6g-9-5995-20",
+ "6g-11-6005-40",
+ "6g-13-6015-20",
+ "6g-15-6025-160",
+ "6g-17-6035-20",
+ "6g-19-6045-40",
+ "6g-21-6055-20",
+ "6g-23-6065-80",
+ "6g-25-6075-20",
+ "6g-27-6085-40",
+ "6g-29-6095-20",
+ "6g-31-6105-320",
+ "6g-33-6115-20",
+ "6g-35-6125-40",
+ "6g-37-6135-20",
+ "6g-39-6145-80",
+ "6g-41-6155-20",
+ "6g-43-6165-40",
+ "6g-45-6175-20",
+ "6g-47-6185-160",
+ "6g-49-6195-20",
+ "6g-51-6205-40",
+ "6g-53-6215-20",
+ "6g-55-6225-80",
+ "6g-57-6235-20",
+ "6g-59-6245-40",
+ "6g-61-6255-20",
+ "6g-65-6275-20",
+ "6g-67-6285-40",
+ "6g-69-6295-20",
+ "6g-71-6305-80",
+ "6g-73-6315-20",
+ "6g-75-6325-40",
+ "6g-77-6335-20",
+ "6g-79-6345-160",
+ "6g-81-6355-20",
+ "6g-83-6365-40",
+ "6g-85-6375-20",
+ "6g-87-6385-80",
+ "6g-89-6395-20",
+ "6g-91-6405-40",
+ "6g-93-6415-20",
+ "6g-95-6425-320",
+ "6g-97-6435-20",
+ "6g-99-6445-40",
+ "6g-101-6455-20",
+ "6g-103-6465-80",
+ "6g-105-6475-20",
+ "6g-107-6485-40",
+ "6g-109-6495-20",
+ "6g-111-6505-160",
+ "6g-113-6515-20",
+ "6g-115-6525-40",
+ "6g-117-6535-20",
+ "6g-119-6545-80",
+ "6g-121-6555-20",
+ "6g-123-6565-40",
+ "6g-125-6575-20",
+ "6g-129-6595-20",
+ "6g-131-6605-40",
+ "6g-133-6615-20",
+ "6g-135-6625-80",
+ "6g-137-6635-20",
+ "6g-139-6645-40",
+ "6g-141-6655-20",
+ "6g-143-6665-160",
+ "6g-145-6675-20",
+ "6g-147-6685-40",
+ "6g-149-6695-20",
+ "6g-151-6705-80",
+ "6g-153-6715-20",
+ "6g-155-6725-40",
+ "6g-157-6735-20",
+ "6g-159-6745-320",
+ "6g-161-6755-20",
+ "6g-163-6765-40",
+ "6g-165-6775-20",
+ "6g-167-6785-80",
+ "6g-169-6795-20",
+ "6g-171-6805-40",
+ "6g-173-6815-20",
+ "6g-175-6825-160",
+ "6g-177-6835-20",
+ "6g-179-6845-40",
+ "6g-181-6855-20",
+ "6g-183-6865-80",
+ "6g-185-6875-20",
+ "6g-187-6885-40",
+ "6g-189-6895-20",
+ "6g-193-6915-20",
+ "6g-195-6925-40",
+ "6g-197-6935-20",
+ "6g-199-6945-80",
+ "6g-201-6955-20",
+ "6g-203-6965-40",
+ "6g-205-6975-20",
+ "6g-207-6985-160",
+ "6g-209-6995-20",
+ "6g-211-7005-40",
+ "6g-213-7015-20",
+ "6g-215-7025-80",
+ "6g-217-7035-20",
+ "6g-219-7045-40",
+ "6g-221-7055-20",
+ "6g-225-7075-20",
+ "6g-227-7085-40",
+ "6g-229-7095-20",
+ "6g-233-7115-20",
+ "60g-1-58320-2160",
+ "60g-2-60480-2160",
+ "60g-3-62640-2160",
+ "60g-4-64800-2160",
+ "60g-5-66960-2160",
+ "60g-6-69120-2160",
+ "60g-9-59400-4320",
+ "60g-10-61560-4320",
+ "60g-11-63720-4320",
+ "60g-12-65880-4320",
+ "60g-13-68040-4320",
+ "60g-17-60480-6480",
+ "60g-18-62640-6480",
+ "60g-19-64800-6480",
+ "60g-20-66960-6480",
+ "60g-25-61560-6480",
+ "60g-26-63720-6480",
+ "60g-27-65880-6480",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2.4g-1-2412-22` - 1 (2412 MHz)\n* `2.4g-2-2417-22` - 2 (2417 MHz)\n* `2.4g-3-2422-22` - 3 (2422 MHz)\n* `2.4g-4-2427-22` - 4 (2427 MHz)\n* `2.4g-5-2432-22` - 5 (2432 MHz)\n* `2.4g-6-2437-22` - 6 (2437 MHz)\n* `2.4g-7-2442-22` - 7 (2442 MHz)\n* `2.4g-8-2447-22` - 8 (2447 MHz)\n* `2.4g-9-2452-22` - 9 (2452 MHz)\n* `2.4g-10-2457-22` - 10 (2457 MHz)\n* `2.4g-11-2462-22` - 11 (2462 MHz)\n* `2.4g-12-2467-22` - 12 (2467 MHz)\n* `2.4g-13-2472-22` - 13 (2472 MHz)\n* `5g-32-5160-20` - 32 (5160/20 MHz)\n* `5g-34-5170-40` - 34 (5170/40 MHz)\n* `5g-36-5180-20` - 36 (5180/20 MHz)\n* `5g-38-5190-40` - 38 (5190/40 MHz)\n* `5g-40-5200-20` - 40 (5200/20 MHz)\n* `5g-42-5210-80` - 42 (5210/80 MHz)\n* `5g-44-5220-20` - 44 (5220/20 MHz)\n* `5g-46-5230-40` - 46 (5230/40 MHz)\n* `5g-48-5240-20` - 48 (5240/20 MHz)\n* `5g-50-5250-160` - 50 (5250/160 MHz)\n* `5g-52-5260-20` - 52 (5260/20 MHz)\n* `5g-54-5270-40` - 54 (5270/40 MHz)\n* `5g-56-5280-20` - 56 (5280/20 MHz)\n* `5g-58-5290-80` - 58 (5290/80 MHz)\n* `5g-60-5300-20` - 60 (5300/20 MHz)\n* `5g-62-5310-40` - 62 (5310/40 MHz)\n* `5g-64-5320-20` - 64 (5320/20 MHz)\n* `5g-100-5500-20` - 100 (5500/20 MHz)\n* `5g-102-5510-40` - 102 (5510/40 MHz)\n* `5g-104-5520-20` - 104 (5520/20 MHz)\n* `5g-106-5530-80` - 106 (5530/80 MHz)\n* `5g-108-5540-20` - 108 (5540/20 MHz)\n* `5g-110-5550-40` - 110 (5550/40 MHz)\n* `5g-112-5560-20` - 112 (5560/20 MHz)\n* `5g-114-5570-160` - 114 (5570/160 MHz)\n* `5g-116-5580-20` - 116 (5580/20 MHz)\n* `5g-118-5590-40` - 118 (5590/40 MHz)\n* `5g-120-5600-20` - 120 (5600/20 MHz)\n* `5g-122-5610-80` - 122 (5610/80 MHz)\n* `5g-124-5620-20` - 124 (5620/20 MHz)\n* `5g-126-5630-40` - 126 (5630/40 MHz)\n* `5g-128-5640-20` - 128 (5640/20 MHz)\n* `5g-132-5660-20` - 132 (5660/20 MHz)\n* `5g-134-5670-40` - 134 (5670/40 MHz)\n* `5g-136-5680-20` - 136 (5680/20 MHz)\n* `5g-138-5690-80` - 138 (5690/80 MHz)\n* `5g-140-5700-20` - 140 (5700/20 MHz)\n* `5g-142-5710-40` - 142 (5710/40 MHz)\n* `5g-144-5720-20` - 144 (5720/20 MHz)\n* `5g-149-5745-20` - 149 (5745/20 MHz)\n* `5g-151-5755-40` - 151 (5755/40 MHz)\n* `5g-153-5765-20` - 153 (5765/20 MHz)\n* `5g-155-5775-80` - 155 (5775/80 MHz)\n* `5g-157-5785-20` - 157 (5785/20 MHz)\n* `5g-159-5795-40` - 159 (5795/40 MHz)\n* `5g-161-5805-20` - 161 (5805/20 MHz)\n* `5g-163-5815-160` - 163 (5815/160 MHz)\n* `5g-165-5825-20` - 165 (5825/20 MHz)\n* `5g-167-5835-40` - 167 (5835/40 MHz)\n* `5g-169-5845-20` - 169 (5845/20 MHz)\n* `5g-171-5855-80` - 171 (5855/80 MHz)\n* `5g-173-5865-20` - 173 (5865/20 MHz)\n* `5g-175-5875-40` - 175 (5875/40 MHz)\n* `5g-177-5885-20` - 177 (5885/20 MHz)\n* `6g-1-5955-20` - 1 (5955/20 MHz)\n* `6g-3-5965-40` - 3 (5965/40 MHz)\n* `6g-5-5975-20` - 5 (5975/20 MHz)\n* `6g-7-5985-80` - 7 (5985/80 MHz)\n* `6g-9-5995-20` - 9 (5995/20 MHz)\n* `6g-11-6005-40` - 11 (6005/40 MHz)\n* `6g-13-6015-20` - 13 (6015/20 MHz)\n* `6g-15-6025-160` - 15 (6025/160 MHz)\n* `6g-17-6035-20` - 17 (6035/20 MHz)\n* `6g-19-6045-40` - 19 (6045/40 MHz)\n* `6g-21-6055-20` - 21 (6055/20 MHz)\n* `6g-23-6065-80` - 23 (6065/80 MHz)\n* `6g-25-6075-20` - 25 (6075/20 MHz)\n* `6g-27-6085-40` - 27 (6085/40 MHz)\n* `6g-29-6095-20` - 29 (6095/20 MHz)\n* `6g-31-6105-320` - 31 (6105/320 MHz)\n* `6g-33-6115-20` - 33 (6115/20 MHz)\n* `6g-35-6125-40` - 35 (6125/40 MHz)\n* `6g-37-6135-20` - 37 (6135/20 MHz)\n* `6g-39-6145-80` - 39 (6145/80 MHz)\n* `6g-41-6155-20` - 41 (6155/20 MHz)\n* `6g-43-6165-40` - 43 (6165/40 MHz)\n* `6g-45-6175-20` - 45 (6175/20 MHz)\n* `6g-47-6185-160` - 47 (6185/160 MHz)\n* `6g-49-6195-20` - 49 (6195/20 MHz)\n* `6g-51-6205-40` - 51 (6205/40 MHz)\n* `6g-53-6215-20` - 53 (6215/20 MHz)\n* `6g-55-6225-80` - 55 (6225/80 MHz)\n* `6g-57-6235-20` - 57 (6235/20 MHz)\n* `6g-59-6245-40` - 59 (6245/40 MHz)\n* `6g-61-6255-20` - 61 (6255/20 MHz)\n* `6g-65-6275-20` - 65 (6275/20 MHz)\n* `6g-67-6285-40` - 67 (6285/40 MHz)\n* `6g-69-6295-20` - 69 (6295/20 MHz)\n* `6g-71-6305-80` - 71 (6305/80 MHz)\n* `6g-73-6315-20` - 73 (6315/20 MHz)\n* `6g-75-6325-40` - 75 (6325/40 MHz)\n* `6g-77-6335-20` - 77 (6335/20 MHz)\n* `6g-79-6345-160` - 79 (6345/160 MHz)\n* `6g-81-6355-20` - 81 (6355/20 MHz)\n* `6g-83-6365-40` - 83 (6365/40 MHz)\n* `6g-85-6375-20` - 85 (6375/20 MHz)\n* `6g-87-6385-80` - 87 (6385/80 MHz)\n* `6g-89-6395-20` - 89 (6395/20 MHz)\n* `6g-91-6405-40` - 91 (6405/40 MHz)\n* `6g-93-6415-20` - 93 (6415/20 MHz)\n* `6g-95-6425-320` - 95 (6425/320 MHz)\n* `6g-97-6435-20` - 97 (6435/20 MHz)\n* `6g-99-6445-40` - 99 (6445/40 MHz)\n* `6g-101-6455-20` - 101 (6455/20 MHz)\n* `6g-103-6465-80` - 103 (6465/80 MHz)\n* `6g-105-6475-20` - 105 (6475/20 MHz)\n* `6g-107-6485-40` - 107 (6485/40 MHz)\n* `6g-109-6495-20` - 109 (6495/20 MHz)\n* `6g-111-6505-160` - 111 (6505/160 MHz)\n* `6g-113-6515-20` - 113 (6515/20 MHz)\n* `6g-115-6525-40` - 115 (6525/40 MHz)\n* `6g-117-6535-20` - 117 (6535/20 MHz)\n* `6g-119-6545-80` - 119 (6545/80 MHz)\n* `6g-121-6555-20` - 121 (6555/20 MHz)\n* `6g-123-6565-40` - 123 (6565/40 MHz)\n* `6g-125-6575-20` - 125 (6575/20 MHz)\n* `6g-129-6595-20` - 129 (6595/20 MHz)\n* `6g-131-6605-40` - 131 (6605/40 MHz)\n* `6g-133-6615-20` - 133 (6615/20 MHz)\n* `6g-135-6625-80` - 135 (6625/80 MHz)\n* `6g-137-6635-20` - 137 (6635/20 MHz)\n* `6g-139-6645-40` - 139 (6645/40 MHz)\n* `6g-141-6655-20` - 141 (6655/20 MHz)\n* `6g-143-6665-160` - 143 (6665/160 MHz)\n* `6g-145-6675-20` - 145 (6675/20 MHz)\n* `6g-147-6685-40` - 147 (6685/40 MHz)\n* `6g-149-6695-20` - 149 (6695/20 MHz)\n* `6g-151-6705-80` - 151 (6705/80 MHz)\n* `6g-153-6715-20` - 153 (6715/20 MHz)\n* `6g-155-6725-40` - 155 (6725/40 MHz)\n* `6g-157-6735-20` - 157 (6735/20 MHz)\n* `6g-159-6745-320` - 159 (6745/320 MHz)\n* `6g-161-6755-20` - 161 (6755/20 MHz)\n* `6g-163-6765-40` - 163 (6765/40 MHz)\n* `6g-165-6775-20` - 165 (6775/20 MHz)\n* `6g-167-6785-80` - 167 (6785/80 MHz)\n* `6g-169-6795-20` - 169 (6795/20 MHz)\n* `6g-171-6805-40` - 171 (6805/40 MHz)\n* `6g-173-6815-20` - 173 (6815/20 MHz)\n* `6g-175-6825-160` - 175 (6825/160 MHz)\n* `6g-177-6835-20` - 177 (6835/20 MHz)\n* `6g-179-6845-40` - 179 (6845/40 MHz)\n* `6g-181-6855-20` - 181 (6855/20 MHz)\n* `6g-183-6865-80` - 183 (6865/80 MHz)\n* `6g-185-6875-20` - 185 (6875/20 MHz)\n* `6g-187-6885-40` - 187 (6885/40 MHz)\n* `6g-189-6895-20` - 189 (6895/20 MHz)\n* `6g-193-6915-20` - 193 (6915/20 MHz)\n* `6g-195-6925-40` - 195 (6925/40 MHz)\n* `6g-197-6935-20` - 197 (6935/20 MHz)\n* `6g-199-6945-80` - 199 (6945/80 MHz)\n* `6g-201-6955-20` - 201 (6955/20 MHz)\n* `6g-203-6965-40` - 203 (6965/40 MHz)\n* `6g-205-6975-20` - 205 (6975/20 MHz)\n* `6g-207-6985-160` - 207 (6985/160 MHz)\n* `6g-209-6995-20` - 209 (6995/20 MHz)\n* `6g-211-7005-40` - 211 (7005/40 MHz)\n* `6g-213-7015-20` - 213 (7015/20 MHz)\n* `6g-215-7025-80` - 215 (7025/80 MHz)\n* `6g-217-7035-20` - 217 (7035/20 MHz)\n* `6g-219-7045-40` - 219 (7045/40 MHz)\n* `6g-221-7055-20` - 221 (7055/20 MHz)\n* `6g-225-7075-20` - 225 (7075/20 MHz)\n* `6g-227-7085-40` - 227 (7085/40 MHz)\n* `6g-229-7095-20` - 229 (7095/20 MHz)\n* `6g-233-7115-20` - 233 (7115/20 MHz)\n* `60g-1-58320-2160` - 1 (58.32/2.16 GHz)\n* `60g-2-60480-2160` - 2 (60.48/2.16 GHz)\n* `60g-3-62640-2160` - 3 (62.64/2.16 GHz)\n* `60g-4-64800-2160` - 4 (64.80/2.16 GHz)\n* `60g-5-66960-2160` - 5 (66.96/2.16 GHz)\n* `60g-6-69120-2160` - 6 (69.12/2.16 GHz)\n* `60g-9-59400-4320` - 9 (59.40/4.32 GHz)\n* `60g-10-61560-4320` - 10 (61.56/4.32 GHz)\n* `60g-11-63720-4320` - 11 (63.72/4.32 GHz)\n* `60g-12-65880-4320` - 12 (65.88/4.32 GHz)\n* `60g-13-68040-4320` - 13 (68.04/4.32 GHz)\n* `60g-17-60480-6480` - 17 (60.48/6.48 GHz)\n* `60g-18-62640-6480` - 18 (62.64/6.48 GHz)\n* `60g-19-64800-6480` - 19 (64.80/6.48 GHz)\n* `60g-20-66960-6480` - 20 (66.96/6.48 GHz)\n* `60g-25-61560-6480` - 25 (61.56/8.64 GHz)\n* `60g-26-63720-6480` - 26 (63.72/8.64 GHz)\n* `60g-27-65880-6480` - 27 (65.88/8.64 GHz)",
+ "x-spec-enum-id": "70cf66176c475063",
+ "nullable": true,
+ "title": "Wireless channel"
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd",
+ "nullable": true
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab",
+ "nullable": true
+ },
+ "rf_channel_frequency": {
+ "type": "number",
+ "format": "double",
+ "maximum": 100000,
+ "minimum": -100000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel frequency (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "rf_channel_width": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": -10000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true,
+ "title": "Channel width (MHz)",
+ "description": "Populated by selected channel (if set)"
+ },
+ "tx_power": {
+ "type": "integer",
+ "maximum": 127,
+ "minimum": -40,
+ "nullable": true,
+ "title": "Transmit power (dBm)"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "wireless_lans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "WritableInterfaceTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "virtual",
+ "bridge",
+ "lag",
+ "100base-fx",
+ "100base-lfx",
+ "100base-tx",
+ "100base-t1",
+ "1000base-bx10-d",
+ "1000base-bx10-u",
+ "1000base-cwdm",
+ "1000base-cx",
+ "1000base-dwdm",
+ "1000base-ex",
+ "1000base-lsx",
+ "1000base-lx",
+ "1000base-lx10",
+ "1000base-sx",
+ "1000base-t",
+ "1000base-tx",
+ "1000base-zx",
+ "2.5gbase-t",
+ "5gbase-t",
+ "10gbase-br-d",
+ "10gbase-br-u",
+ "10gbase-cx4",
+ "10gbase-er",
+ "10gbase-lr",
+ "10gbase-lrm",
+ "10gbase-lx4",
+ "10gbase-sr",
+ "10gbase-t",
+ "10gbase-zr",
+ "25gbase-cr",
+ "25gbase-er",
+ "25gbase-lr",
+ "25gbase-sr",
+ "25gbase-t",
+ "40gbase-cr4",
+ "40gbase-er4",
+ "40gbase-fr4",
+ "40gbase-lr4",
+ "40gbase-sr4",
+ "50gbase-cr",
+ "50gbase-er",
+ "50gbase-fr",
+ "50gbase-lr",
+ "50gbase-sr",
+ "100gbase-cr1",
+ "100gbase-cr2",
+ "100gbase-cr4",
+ "100gbase-cr10",
+ "100gbase-cwdm4",
+ "100gbase-dr",
+ "100gbase-er4",
+ "100gbase-fr1",
+ "100gbase-lr1",
+ "100gbase-lr4",
+ "100gbase-sr1",
+ "100gbase-sr1.2",
+ "100gbase-sr2",
+ "100gbase-sr4",
+ "100gbase-sr10",
+ "100gbase-zr",
+ "200gbase-cr2",
+ "200gbase-cr4",
+ "200gbase-dr4",
+ "200gbase-er4",
+ "200gbase-fr4",
+ "200gbase-lr4",
+ "200gbase-sr2",
+ "200gbase-sr4",
+ "200gbase-vr2",
+ "400gbase-cr4",
+ "400gbase-dr4",
+ "400gbase-er8",
+ "400gbase-fr4",
+ "400gbase-fr8",
+ "400gbase-lr4",
+ "400gbase-lr8",
+ "400gbase-sr4",
+ "400gbase-sr4_2",
+ "400gbase-sr8",
+ "400gbase-sr16",
+ "400gbase-vr4",
+ "400gbase-zr",
+ "800gbase-cr8",
+ "800gbase-dr8",
+ "800gbase-sr8",
+ "800gbase-vr8",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "10gbase-x-sfpp",
+ "10gbase-x-xenpak",
+ "10gbase-x-xfp",
+ "10gbase-x-x2",
+ "25gbase-x-sfp28",
+ "40gbase-x-qsfpp",
+ "50gbase-x-sfp28",
+ "50gbase-x-sfp56",
+ "100gbase-x-cfp",
+ "100gbase-x-cfp2",
+ "100gbase-x-cfp4",
+ "100gbase-x-cxp",
+ "100gbase-x-cpak",
+ "100gbase-x-dsfp",
+ "100gbase-x-qsfp28",
+ "100gbase-x-qsfpdd",
+ "100gbase-x-sfpdd",
+ "200gbase-x-cfp2",
+ "200gbase-x-qsfp56",
+ "200gbase-x-qsfpdd",
+ "400gbase-x-qsfp112",
+ "400gbase-x-qsfpdd",
+ "400gbase-x-cdfp",
+ "400gbase-x-cfp2",
+ "400gbase-x-cfp8",
+ "400gbase-x-osfp",
+ "400gbase-x-osfp-rhs",
+ "800gbase-x-osfp",
+ "800gbase-x-qsfpdd",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "ieee802.11a",
+ "ieee802.11g",
+ "ieee802.11n",
+ "ieee802.11ac",
+ "ieee802.11ad",
+ "ieee802.11ax",
+ "ieee802.11ay",
+ "ieee802.11be",
+ "ieee802.15.1",
+ "ieee802.15.4",
+ "other-wireless",
+ "gsm",
+ "cdma",
+ "lte",
+ "4g",
+ "5g",
+ "sonet-oc3",
+ "sonet-oc12",
+ "sonet-oc48",
+ "sonet-oc192",
+ "sonet-oc768",
+ "sonet-oc1920",
+ "sonet-oc3840",
+ "1gfc-sfp",
+ "2gfc-sfp",
+ "4gfc-sfp",
+ "8gfc-sfpp",
+ "16gfc-sfpp",
+ "32gfc-sfp28",
+ "32gfc-sfpp",
+ "64gfc-qsfpp",
+ "64gfc-sfpdd",
+ "64gfc-sfpp",
+ "128gfc-qsfp28",
+ "infiniband-sdr",
+ "infiniband-ddr",
+ "infiniband-qdr",
+ "infiniband-fdr10",
+ "infiniband-fdr",
+ "infiniband-edr",
+ "infiniband-hdr",
+ "infiniband-ndr",
+ "infiniband-xdr",
+ "t1",
+ "e1",
+ "t3",
+ "e3",
+ "xdsl",
+ "docsis",
+ "moca",
+ "bpon",
+ "epon",
+ "10g-epon",
+ "gpon",
+ "xg-pon",
+ "xgs-pon",
+ "ng-pon2",
+ "25g-pon",
+ "50g-pon",
+ "cisco-stackwise",
+ "cisco-stackwise-plus",
+ "cisco-flexstack",
+ "cisco-flexstack-plus",
+ "cisco-stackwise-80",
+ "cisco-stackwise-160",
+ "cisco-stackwise-320",
+ "cisco-stackwise-480",
+ "cisco-stackwise-1t",
+ "juniper-vcp",
+ "extreme-summitstack",
+ "extreme-summitstack-128",
+ "extreme-summitstack-256",
+ "extreme-summitstack-512",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `virtual` - Virtual\n* `bridge` - Bridge\n* `lag` - Link Aggregation Group (LAG)\n* `100base-fx` - 100BASE-FX (10/100ME)\n* `100base-lfx` - 100BASE-LFX (10/100ME)\n* `100base-tx` - 100BASE-TX (10/100ME)\n* `100base-t1` - 100BASE-T1 (10/100ME)\n* `1000base-bx10-d` - 1000BASE-BX10-D (1GE BiDi Down)\n* `1000base-bx10-u` - 1000BASE-BX10-U (1GE BiDi Up)\n* `1000base-cwdm` - 1000BASE-CWDM (1GE)\n* `1000base-cx` - 1000BASE-CX (1GE DAC)\n* `1000base-dwdm` - 1000BASE-DWDM (1GE)\n* `1000base-ex` - 1000BASE-EX (1GE)\n* `1000base-lsx` - 1000BASE-LSX (1GE)\n* `1000base-lx` - 1000BASE-LX (1GE)\n* `1000base-lx10` - 1000BASE-LX10/LH (1GE)\n* `1000base-sx` - 1000BASE-SX (1GE)\n* `1000base-t` - 1000BASE-T (1GE)\n* `1000base-tx` - 1000BASE-TX (1GE)\n* `1000base-zx` - 1000BASE-ZX (1GE)\n* `2.5gbase-t` - 2.5GBASE-T (2.5GE)\n* `5gbase-t` - 5GBASE-T (5GE)\n* `10gbase-br-d` - 10GBASE-BR-D (10GE BiDi Down)\n* `10gbase-br-u` - 10GBASE-BR-U (10GE BiDi Up)\n* `10gbase-cx4` - 10GBASE-CX4 (10GE DAC)\n* `10gbase-er` - 10GBASE-ER (10GE)\n* `10gbase-lr` - 10GBASE-LR (10GE)\n* `10gbase-lrm` - 10GBASE-LRM (10GE)\n* `10gbase-lx4` - 10GBASE-LX4 (10GE)\n* `10gbase-sr` - 10GBASE-SR (10GE)\n* `10gbase-t` - 10GBASE-T (10GE)\n* `10gbase-zr` - 10GBASE-ZR (10GE)\n* `25gbase-cr` - 25GBASE-CR (25GE DAC)\n* `25gbase-er` - 25GBASE-ER (25GE)\n* `25gbase-lr` - 25GBASE-LR (25GE)\n* `25gbase-sr` - 25GBASE-SR (25GE)\n* `25gbase-t` - 25GBASE-T (25GE)\n* `40gbase-cr4` - 40GBASE-CR4 (40GE DAC)\n* `40gbase-er4` - 40GBASE-ER4 (40GE)\n* `40gbase-fr4` - 40GBASE-FR4 (40GE)\n* `40gbase-lr4` - 40GBASE-LR4 (40GE)\n* `40gbase-sr4` - 40GBASE-SR4 (40GE)\n* `50gbase-cr` - 50GBASE-CR (50GE DAC)\n* `50gbase-er` - 50GBASE-ER (50GE)\n* `50gbase-fr` - 50GBASE-FR (50GE)\n* `50gbase-lr` - 50GBASE-LR (50GE)\n* `50gbase-sr` - 50GBASE-SR (50GE)\n* `100gbase-cr1` - 100GBASE-CR1 (100GE DAC)\n* `100gbase-cr2` - 100GBASE-CR2 (100GE DAC)\n* `100gbase-cr4` - 100GBASE-CR4 (100GE DAC)\n* `100gbase-cr10` - 100GBASE-CR10 (100GE DAC)\n* `100gbase-cwdm4` - 100GBASE-CWDM4 (100GE)\n* `100gbase-dr` - 100GBASE-DR (100GE)\n* `100gbase-er4` - 100GBASE-ER4 (100GE)\n* `100gbase-fr1` - 100GBASE-FR1 (100GE)\n* `100gbase-lr1` - 100GBASE-LR1 (100GE)\n* `100gbase-lr4` - 100GBASE-LR4 (100GE)\n* `100gbase-sr1` - 100GBASE-SR1 (100GE)\n* `100gbase-sr1.2` - 100GBASE-SR1.2 (100GE BiDi)\n* `100gbase-sr2` - 100GBASE-SR2 (100GE)\n* `100gbase-sr4` - 100GBASE-SR4 (100GE)\n* `100gbase-sr10` - 100GBASE-SR10 (100GE)\n* `100gbase-zr` - 100GBASE-ZR (100GE)\n* `200gbase-cr2` - 200GBASE-CR2 (200GE)\n* `200gbase-cr4` - 200GBASE-CR4 (200GE)\n* `200gbase-dr4` - 200GBASE-DR4 (200GE)\n* `200gbase-er4` - 200GBASE-ER4 (200GE)\n* `200gbase-fr4` - 200GBASE-FR4 (200GE)\n* `200gbase-lr4` - 200GBASE-LR4 (200GE)\n* `200gbase-sr2` - 200GBASE-SR2 (200GE)\n* `200gbase-sr4` - 200GBASE-SR4 (200GE)\n* `200gbase-vr2` - 200GBASE-VR2 (200GE)\n* `400gbase-cr4` - 400GBASE-CR4 (400GE)\n* `400gbase-dr4` - 400GBASE-DR4 (400GE)\n* `400gbase-er8` - 400GBASE-ER8 (400GE)\n* `400gbase-fr4` - 400GBASE-FR4 (400GE)\n* `400gbase-fr8` - 400GBASE-FR8 (400GE)\n* `400gbase-lr4` - 400GBASE-LR4 (400GE)\n* `400gbase-lr8` - 400GBASE-LR8 (400GE)\n* `400gbase-sr4` - 400GBASE-SR4 (400GE)\n* `400gbase-sr4_2` - 400GBASE-SR4.2 (400GE BiDi)\n* `400gbase-sr8` - 400GBASE-SR8 (400GE)\n* `400gbase-sr16` - 400GBASE-SR16 (400GE)\n* `400gbase-vr4` - 400GBASE-VR4 (400GE)\n* `400gbase-zr` - 400GBASE-ZR (400GE)\n* `800gbase-cr8` - 800GBASE-CR8 (800GE)\n* `800gbase-dr8` - 800GBASE-DR8 (800GE)\n* `800gbase-sr8` - 800GBASE-SR8 (800GE)\n* `800gbase-vr8` - 800GBASE-VR8 (800GE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `10gbase-x-sfpp` - SFP+ (10GE)\n* `10gbase-x-xenpak` - XENPAK (10GE)\n* `10gbase-x-xfp` - XFP (10GE)\n* `10gbase-x-x2` - X2 (10GE)\n* `25gbase-x-sfp28` - SFP28 (25GE)\n* `40gbase-x-qsfpp` - QSFP+ (40GE)\n* `50gbase-x-sfp28` - QSFP28 (50GE)\n* `50gbase-x-sfp56` - SFP56 (50GE)\n* `100gbase-x-cfp` - CFP (100GE)\n* `100gbase-x-cfp2` - CFP2 (100GE)\n* `100gbase-x-cfp4` - CFP4 (100GE)\n* `100gbase-x-cxp` - CXP (100GE)\n* `100gbase-x-cpak` - Cisco CPAK (100GE)\n* `100gbase-x-dsfp` - DSFP (100GE)\n* `100gbase-x-qsfp28` - QSFP28 (100GE)\n* `100gbase-x-qsfpdd` - QSFP-DD (100GE)\n* `100gbase-x-sfpdd` - SFP-DD (100GE)\n* `200gbase-x-cfp2` - CFP2 (200GE)\n* `200gbase-x-qsfp56` - QSFP56 (200GE)\n* `200gbase-x-qsfpdd` - QSFP-DD (200GE)\n* `400gbase-x-qsfp112` - QSFP112 (400GE)\n* `400gbase-x-qsfpdd` - QSFP-DD (400GE)\n* `400gbase-x-cdfp` - CDFP (400GE)\n* `400gbase-x-cfp2` - CFP2 (400GE)\n* `400gbase-x-cfp8` - CPF8 (400GE)\n* `400gbase-x-osfp` - OSFP (400GE)\n* `400gbase-x-osfp-rhs` - OSFP-RHS (400GE)\n* `800gbase-x-osfp` - OSFP (800GE)\n* `800gbase-x-qsfpdd` - QSFP-DD (800GE)\n* `1000base-kx` - 1000BASE-KX (1GE)\n* `2.5gbase-kx` - 2.5GBASE-KX (2.5GE)\n* `5gbase-kr` - 5GBASE-KR (5GE)\n* `10gbase-kr` - 10GBASE-KR (10GE)\n* `10gbase-kx4` - 10GBASE-KX4 (10GE)\n* `25gbase-kr` - 25GBASE-KR (25GE)\n* `40gbase-kr4` - 40GBASE-KR4 (40GE)\n* `50gbase-kr` - 50GBASE-KR (50GE)\n* `100gbase-kp4` - 100GBASE-KP4 (100GE)\n* `100gbase-kr2` - 100GBASE-KR2 (100GE)\n* `100gbase-kr4` - 100GBASE-KR4 (100GE)\n* `ieee802.11a` - IEEE 802.11a\n* `ieee802.11g` - IEEE 802.11b/g\n* `ieee802.11n` - IEEE 802.11n (Wi-Fi 4)\n* `ieee802.11ac` - IEEE 802.11ac (Wi-Fi 5)\n* `ieee802.11ad` - IEEE 802.11ad (WiGig)\n* `ieee802.11ax` - IEEE 802.11ax (Wi-Fi 6)\n* `ieee802.11ay` - IEEE 802.11ay (WiGig)\n* `ieee802.11be` - IEEE 802.11be (Wi-Fi 7)\n* `ieee802.15.1` - IEEE 802.15.1 (Bluetooth)\n* `ieee802.15.4` - IEEE 802.15.4 (LR-WPAN)\n* `other-wireless` - Other (Wireless)\n* `gsm` - GSM\n* `cdma` - CDMA\n* `lte` - LTE\n* `4g` - 4G\n* `5g` - 5G\n* `sonet-oc3` - OC-3/STM-1\n* `sonet-oc12` - OC-12/STM-4\n* `sonet-oc48` - OC-48/STM-16\n* `sonet-oc192` - OC-192/STM-64\n* `sonet-oc768` - OC-768/STM-256\n* `sonet-oc1920` - OC-1920/STM-640\n* `sonet-oc3840` - OC-3840/STM-1234\n* `1gfc-sfp` - SFP (1GFC)\n* `2gfc-sfp` - SFP (2GFC)\n* `4gfc-sfp` - SFP (4GFC)\n* `8gfc-sfpp` - SFP+ (8GFC)\n* `16gfc-sfpp` - SFP+ (16GFC)\n* `32gfc-sfp28` - SFP28 (32GFC)\n* `32gfc-sfpp` - SFP+ (32GFC)\n* `64gfc-qsfpp` - QSFP+ (64GFC)\n* `64gfc-sfpdd` - SFP-DD (64GFC)\n* `64gfc-sfpp` - SFP+ (64GFC)\n* `128gfc-qsfp28` - QSFP28 (128GFC)\n* `infiniband-sdr` - SDR (2 Gbps)\n* `infiniband-ddr` - DDR (4 Gbps)\n* `infiniband-qdr` - QDR (8 Gbps)\n* `infiniband-fdr10` - FDR10 (10 Gbps)\n* `infiniband-fdr` - FDR (13.5 Gbps)\n* `infiniband-edr` - EDR (25 Gbps)\n* `infiniband-hdr` - HDR (50 Gbps)\n* `infiniband-ndr` - NDR (100 Gbps)\n* `infiniband-xdr` - XDR (250 Gbps)\n* `t1` - T1 (1.544 Mbps)\n* `e1` - E1 (2.048 Mbps)\n* `t3` - T3 (45 Mbps)\n* `e3` - E3 (34 Mbps)\n* `xdsl` - xDSL\n* `docsis` - DOCSIS\n* `moca` - MoCA\n* `bpon` - BPON (622 Mbps / 155 Mbps)\n* `epon` - EPON (1 Gbps)\n* `10g-epon` - 10G-EPON (10 Gbps)\n* `gpon` - GPON (2.5 Gbps / 1.25 Gbps)\n* `xg-pon` - XG-PON (10 Gbps / 2.5 Gbps)\n* `xgs-pon` - XGS-PON (10 Gbps)\n* `ng-pon2` - NG-PON2 (TWDM-PON) (4x10 Gbps)\n* `25g-pon` - 25G-PON (25 Gbps)\n* `50g-pon` - 50G-PON (50 Gbps)\n* `cisco-stackwise` - Cisco StackWise\n* `cisco-stackwise-plus` - Cisco StackWise Plus\n* `cisco-flexstack` - Cisco FlexStack\n* `cisco-flexstack-plus` - Cisco FlexStack Plus\n* `cisco-stackwise-80` - Cisco StackWise-80\n* `cisco-stackwise-160` - Cisco StackWise-160\n* `cisco-stackwise-320` - Cisco StackWise-320\n* `cisco-stackwise-480` - Cisco StackWise-480\n* `cisco-stackwise-1t` - Cisco StackWise-1T\n* `juniper-vcp` - Juniper VCP\n* `extreme-summitstack` - Extreme SummitStack\n* `extreme-summitstack-128` - Extreme SummitStack-128\n* `extreme-summitstack-256` - Extreme SummitStack-256\n* `extreme-summitstack-512` - Extreme SummitStack-512\n* `other` - Other",
+ "x-spec-enum-id": "62208bd818e5f524"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "mgmt_only": {
+ "type": "boolean",
+ "title": "Management only"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "poe_mode": {
+ "enum": [
+ "pd",
+ "pse",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `pd` - PD\n* `pse` - PSE",
+ "x-spec-enum-id": "2f2fe6dcdc7772bd",
+ "nullable": true
+ },
+ "poe_type": {
+ "enum": [
+ "type1-ieee802.3af",
+ "type2-ieee802.3at",
+ "type3-ieee802.3bt",
+ "type4-ieee802.3bt",
+ "passive-24v-2pair",
+ "passive-24v-4pair",
+ "passive-48v-2pair",
+ "passive-48v-4pair",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `type1-ieee802.3af` - 802.3af (Type 1)\n* `type2-ieee802.3at` - 802.3at (Type 2)\n* `type3-ieee802.3bt` - 802.3bt (Type 3)\n* `type4-ieee802.3bt` - 802.3bt (Type 4)\n* `passive-24v-2pair` - Passive 24V (2-pair)\n* `passive-24v-4pair` - Passive 24V (4-pair)\n* `passive-48v-2pair` - Passive 48V (2-pair)\n* `passive-48v-4pair` - Passive 48V (4-pair)",
+ "x-spec-enum-id": "5473d57885f237ab",
+ "nullable": true
+ },
+ "rf_role": {
+ "enum": [
+ "ap",
+ "station",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `ap` - Access point\n* `station` - Station",
+ "x-spec-enum-id": "d2772dbea88b0fb1",
+ "nullable": true,
+ "title": "Wireless role"
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "WritableInventoryItemRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefInventoryItemRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "part_id": {
+ "type": "string",
+ "description": "Manufacturer-assigned part identifier",
+ "maxLength": 50
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this item",
+ "maxLength": 50
+ },
+ "discovered": {
+ "type": "boolean",
+ "description": "This item was automatically discovered"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "component_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "component_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "WritableJournalEntryRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "assigned_object_type": {
+ "type": "string"
+ },
+ "assigned_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "created_by": {
+ "type": "integer",
+ "nullable": true
+ },
+ "kind": {
+ "enum": [
+ "info",
+ "success",
+ "warning",
+ "danger"
+ ],
+ "type": "string",
+ "description": "* `info` - Info\n* `success` - Success\n* `warning` - Warning\n* `danger` - Danger",
+ "x-spec-enum-id": "6f65abe0aab2c78c"
+ },
+ "comments": {
+ "type": "string",
+ "minLength": 1
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "assigned_object_id",
+ "assigned_object_type",
+ "comments"
+ ]
+ },
+ "WritableL2VPNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "identifier": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": -9223372036854775808,
+ "format": "int64",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "type": {
+ "enum": [
+ "vpws",
+ "vpls",
+ "vxlan",
+ "vxlan-evpn",
+ "mpls-evpn",
+ "pbb-evpn",
+ "evpn-vpws",
+ "epl",
+ "evpl",
+ "ep-lan",
+ "evp-lan",
+ "ep-tree",
+ "evp-tree",
+ "spb"
+ ],
+ "type": "string",
+ "description": "* `vpws` - VPWS\n* `vpls` - VPLS\n* `vxlan` - VXLAN\n* `vxlan-evpn` - VXLAN-EVPN\n* `mpls-evpn` - MPLS EVPN\n* `pbb-evpn` - PBB EVPN\n* `evpn-vpws` - EVPN VPWS\n* `epl` - EPL\n* `evpl` - EVPL\n* `ep-lan` - Ethernet Private LAN\n* `evp-lan` - Ethernet Virtual Private LAN\n* `ep-tree` - Ethernet Private Tree\n* `evp-tree` - Ethernet Virtual Private Tree\n* `spb` - SPB",
+ "x-spec-enum-id": "0a46f8056d717efc"
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "8b9dc8efc7c3d5b0"
+ },
+ "import_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "export_targets": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug",
+ "type"
+ ]
+ },
+ "WritableLocationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "site",
+ "slug"
+ ]
+ },
+ "WritableModuleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module_bay": {
+ "type": "integer"
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "545817eb4c4f2ae4"
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this device",
+ "maxLength": 50
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "module_bay",
+ "module_type"
+ ]
+ },
+ "WritableModuleTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "part_number": {
+ "type": "string",
+ "description": "Discrete part number (optional)",
+ "maxLength": 50
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "left-to-right",
+ "right-to-left",
+ "side-to-rear",
+ "passive",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front\n* `left-to-right` - Left to right\n* `right-to-left` - Right to left\n* `side-to-rear` - Side to rear\n* `passive` - Passive",
+ "x-spec-enum-id": "5ad4e700c656b09d",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "attributes": {
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "manufacturer",
+ "model"
+ ]
+ },
+ "WritablePlatformRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritablePowerFeedRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "power_panel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefPowerPanelRequest"
+ }
+ ]
+ },
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "failed"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `failed` - Failed",
+ "x-spec-enum-id": "ec530572dc778583"
+ },
+ "type": {
+ "enum": [
+ "primary",
+ "redundant"
+ ],
+ "type": "string",
+ "description": "* `primary` - Primary\n* `redundant` - Redundant",
+ "x-spec-enum-id": "093a164236819eb8"
+ },
+ "supply": {
+ "enum": [
+ "ac",
+ "dc"
+ ],
+ "type": "string",
+ "description": "* `ac` - AC\n* `dc` - DC",
+ "x-spec-enum-id": "1b6d99616ca6412b"
+ },
+ "phase": {
+ "enum": [
+ "single-phase",
+ "three-phase"
+ ],
+ "type": "string",
+ "description": "* `single-phase` - Single phase\n* `three-phase` - Three-phase",
+ "x-spec-enum-id": "994bc0696f4df57f"
+ },
+ "voltage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": -32768
+ },
+ "amperage": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1
+ },
+ "max_utilization": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "description": "Maximum permissible draw (percentage)"
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "power_panel"
+ ]
+ },
+ "WritablePowerOutletRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true,
+ "description": "Physical port type\n\n* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other"
+ },
+ "status": {
+ "enum": [
+ "enabled",
+ "disabled",
+ "faulty"
+ ],
+ "type": "string",
+ "description": "* `enabled` - Enabled\n* `disabled` - Disabled\n* `faulty` - Faulty",
+ "x-spec-enum-id": "d60dce16858f3c69"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true,
+ "description": "Phase (for three-phase feeds)\n\n* `A` - A\n* `B` - B\n* `C` - C"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "WritablePowerOutletTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c5",
+ "iec-60320-c7",
+ "iec-60320-c13",
+ "iec-60320-c15",
+ "iec-60320-c17",
+ "iec-60320-c19",
+ "iec-60320-c21",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15r",
+ "nema-5-15r",
+ "nema-5-20r",
+ "nema-5-30r",
+ "nema-5-50r",
+ "nema-6-15r",
+ "nema-6-20r",
+ "nema-6-30r",
+ "nema-6-50r",
+ "nema-10-30r",
+ "nema-10-50r",
+ "nema-14-20r",
+ "nema-14-30r",
+ "nema-14-50r",
+ "nema-14-60r",
+ "nema-15-15r",
+ "nema-15-20r",
+ "nema-15-30r",
+ "nema-15-50r",
+ "nema-15-60r",
+ "nema-l1-15r",
+ "nema-l5-15r",
+ "nema-l5-20r",
+ "nema-l5-30r",
+ "nema-l5-50r",
+ "nema-l6-15r",
+ "nema-l6-20r",
+ "nema-l6-30r",
+ "nema-l6-50r",
+ "nema-l10-30r",
+ "nema-l14-20r",
+ "nema-l14-30r",
+ "nema-l14-50r",
+ "nema-l14-60r",
+ "nema-l15-20r",
+ "nema-l15-30r",
+ "nema-l15-50r",
+ "nema-l15-60r",
+ "nema-l21-20r",
+ "nema-l21-30r",
+ "nema-l22-20r",
+ "nema-l22-30r",
+ "CS6360C",
+ "CS6364C",
+ "CS8164C",
+ "CS8264C",
+ "CS8364C",
+ "CS8464C",
+ "ita-e",
+ "ita-f",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "ita-multistandard",
+ "usb-a",
+ "usb-micro-b",
+ "usb-c",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "eaton-c39",
+ "hdot-cx",
+ "saf-d-grid",
+ "neutrik-powercon-20a",
+ "neutrik-powercon-32a",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c5` - C5\n* `iec-60320-c7` - C7\n* `iec-60320-c13` - C13\n* `iec-60320-c15` - C15\n* `iec-60320-c17` - C17\n* `iec-60320-c19` - C19\n* `iec-60320-c21` - C21\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15r` - NEMA 1-15R\n* `nema-5-15r` - NEMA 5-15R\n* `nema-5-20r` - NEMA 5-20R\n* `nema-5-30r` - NEMA 5-30R\n* `nema-5-50r` - NEMA 5-50R\n* `nema-6-15r` - NEMA 6-15R\n* `nema-6-20r` - NEMA 6-20R\n* `nema-6-30r` - NEMA 6-30R\n* `nema-6-50r` - NEMA 6-50R\n* `nema-10-30r` - NEMA 10-30R\n* `nema-10-50r` - NEMA 10-50R\n* `nema-14-20r` - NEMA 14-20R\n* `nema-14-30r` - NEMA 14-30R\n* `nema-14-50r` - NEMA 14-50R\n* `nema-14-60r` - NEMA 14-60R\n* `nema-15-15r` - NEMA 15-15R\n* `nema-15-20r` - NEMA 15-20R\n* `nema-15-30r` - NEMA 15-30R\n* `nema-15-50r` - NEMA 15-50R\n* `nema-15-60r` - NEMA 15-60R\n* `nema-l1-15r` - NEMA L1-15R\n* `nema-l5-15r` - NEMA L5-15R\n* `nema-l5-20r` - NEMA L5-20R\n* `nema-l5-30r` - NEMA L5-30R\n* `nema-l5-50r` - NEMA L5-50R\n* `nema-l6-15r` - NEMA L6-15R\n* `nema-l6-20r` - NEMA L6-20R\n* `nema-l6-30r` - NEMA L6-30R\n* `nema-l6-50r` - NEMA L6-50R\n* `nema-l10-30r` - NEMA L10-30R\n* `nema-l14-20r` - NEMA L14-20R\n* `nema-l14-30r` - NEMA L14-30R\n* `nema-l14-50r` - NEMA L14-50R\n* `nema-l14-60r` - NEMA L14-60R\n* `nema-l15-20r` - NEMA L15-20R\n* `nema-l15-30r` - NEMA L15-30R\n* `nema-l15-50r` - NEMA L15-50R\n* `nema-l15-60r` - NEMA L15-60R\n* `nema-l21-20r` - NEMA L21-20R\n* `nema-l21-30r` - NEMA L21-30R\n* `nema-l22-20r` - NEMA L22-20R\n* `nema-l22-30r` - NEMA L22-30R\n* `CS6360C` - CS6360C\n* `CS6364C` - CS6364C\n* `CS8164C` - CS8164C\n* `CS8264C` - CS8264C\n* `CS8364C` - CS8364C\n* `CS8464C` - CS8464C\n* `ita-e` - ITA Type E (CEE 7/5)\n* `ita-f` - ITA Type F (CEE 7/3)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `ita-multistandard` - ITA Multistandard\n* `usb-a` - USB Type A\n* `usb-micro-b` - USB Micro B\n* `usb-c` - USB Type C\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `eaton-c39` - Eaton C39\n* `hdot-cx` - HDOT Cx\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20a` - Neutrik powerCON (20A)\n* `neutrik-powercon-32a` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "db3e4eb2b93615f8",
+ "nullable": true
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "power_port": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPowerPortTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "feed_leg": {
+ "enum": [
+ "A",
+ "B",
+ "C",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "a4902339df0b7c06",
+ "nullable": true,
+ "description": "Phase (for three-phase feeds)\n\n* `A` - A\n* `B` - B\n* `C` - C"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritablePowerPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true,
+ "description": "Physical port type\n\n* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other"
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name"
+ ]
+ },
+ "WritablePowerPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "iec-60320-c6",
+ "iec-60320-c8",
+ "iec-60320-c14",
+ "iec-60320-c16",
+ "iec-60320-c18",
+ "iec-60320-c20",
+ "iec-60320-c22",
+ "iec-60309-p-n-e-4h",
+ "iec-60309-p-n-e-6h",
+ "iec-60309-p-n-e-9h",
+ "iec-60309-2p-e-4h",
+ "iec-60309-2p-e-6h",
+ "iec-60309-2p-e-9h",
+ "iec-60309-3p-e-4h",
+ "iec-60309-3p-e-6h",
+ "iec-60309-3p-e-9h",
+ "iec-60309-3p-n-e-4h",
+ "iec-60309-3p-n-e-6h",
+ "iec-60309-3p-n-e-9h",
+ "iec-60906-1",
+ "nbr-14136-10a",
+ "nbr-14136-20a",
+ "nema-1-15p",
+ "nema-5-15p",
+ "nema-5-20p",
+ "nema-5-30p",
+ "nema-5-50p",
+ "nema-6-15p",
+ "nema-6-20p",
+ "nema-6-30p",
+ "nema-6-50p",
+ "nema-10-30p",
+ "nema-10-50p",
+ "nema-14-20p",
+ "nema-14-30p",
+ "nema-14-50p",
+ "nema-14-60p",
+ "nema-15-15p",
+ "nema-15-20p",
+ "nema-15-30p",
+ "nema-15-50p",
+ "nema-15-60p",
+ "nema-l1-15p",
+ "nema-l5-15p",
+ "nema-l5-20p",
+ "nema-l5-30p",
+ "nema-l5-50p",
+ "nema-l6-15p",
+ "nema-l6-20p",
+ "nema-l6-30p",
+ "nema-l6-50p",
+ "nema-l10-30p",
+ "nema-l14-20p",
+ "nema-l14-30p",
+ "nema-l14-50p",
+ "nema-l14-60p",
+ "nema-l15-20p",
+ "nema-l15-30p",
+ "nema-l15-50p",
+ "nema-l15-60p",
+ "nema-l21-20p",
+ "nema-l21-30p",
+ "nema-l22-20p",
+ "nema-l22-30p",
+ "cs6361c",
+ "cs6365c",
+ "cs8165c",
+ "cs8265c",
+ "cs8365c",
+ "cs8465c",
+ "ita-c",
+ "ita-e",
+ "ita-f",
+ "ita-ef",
+ "ita-g",
+ "ita-h",
+ "ita-i",
+ "ita-j",
+ "ita-k",
+ "ita-l",
+ "ita-m",
+ "ita-n",
+ "ita-o",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "usb-3-b",
+ "usb-3-micro-b",
+ "molex-micro-fit-1x2",
+ "molex-micro-fit-2x2",
+ "molex-micro-fit-2x3",
+ "molex-micro-fit-2x4",
+ "dc-terminal",
+ "saf-d-grid",
+ "neutrik-powercon-20",
+ "neutrik-powercon-32",
+ "neutrik-powercon-true1",
+ "neutrik-powercon-true1-top",
+ "ubiquiti-smartpower",
+ "hardwired",
+ "other",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `iec-60320-c6` - C6\n* `iec-60320-c8` - C8\n* `iec-60320-c14` - C14\n* `iec-60320-c16` - C16\n* `iec-60320-c18` - C18\n* `iec-60320-c20` - C20\n* `iec-60320-c22` - C22\n* `iec-60309-p-n-e-4h` - P+N+E 4H\n* `iec-60309-p-n-e-6h` - P+N+E 6H\n* `iec-60309-p-n-e-9h` - P+N+E 9H\n* `iec-60309-2p-e-4h` - 2P+E 4H\n* `iec-60309-2p-e-6h` - 2P+E 6H\n* `iec-60309-2p-e-9h` - 2P+E 9H\n* `iec-60309-3p-e-4h` - 3P+E 4H\n* `iec-60309-3p-e-6h` - 3P+E 6H\n* `iec-60309-3p-e-9h` - 3P+E 9H\n* `iec-60309-3p-n-e-4h` - 3P+N+E 4H\n* `iec-60309-3p-n-e-6h` - 3P+N+E 6H\n* `iec-60309-3p-n-e-9h` - 3P+N+E 9H\n* `iec-60906-1` - IEC 60906-1\n* `nbr-14136-10a` - 2P+T 10A (NBR 14136)\n* `nbr-14136-20a` - 2P+T 20A (NBR 14136)\n* `nema-1-15p` - NEMA 1-15P\n* `nema-5-15p` - NEMA 5-15P\n* `nema-5-20p` - NEMA 5-20P\n* `nema-5-30p` - NEMA 5-30P\n* `nema-5-50p` - NEMA 5-50P\n* `nema-6-15p` - NEMA 6-15P\n* `nema-6-20p` - NEMA 6-20P\n* `nema-6-30p` - NEMA 6-30P\n* `nema-6-50p` - NEMA 6-50P\n* `nema-10-30p` - NEMA 10-30P\n* `nema-10-50p` - NEMA 10-50P\n* `nema-14-20p` - NEMA 14-20P\n* `nema-14-30p` - NEMA 14-30P\n* `nema-14-50p` - NEMA 14-50P\n* `nema-14-60p` - NEMA 14-60P\n* `nema-15-15p` - NEMA 15-15P\n* `nema-15-20p` - NEMA 15-20P\n* `nema-15-30p` - NEMA 15-30P\n* `nema-15-50p` - NEMA 15-50P\n* `nema-15-60p` - NEMA 15-60P\n* `nema-l1-15p` - NEMA L1-15P\n* `nema-l5-15p` - NEMA L5-15P\n* `nema-l5-20p` - NEMA L5-20P\n* `nema-l5-30p` - NEMA L5-30P\n* `nema-l5-50p` - NEMA L5-50P\n* `nema-l6-15p` - NEMA L6-15P\n* `nema-l6-20p` - NEMA L6-20P\n* `nema-l6-30p` - NEMA L6-30P\n* `nema-l6-50p` - NEMA L6-50P\n* `nema-l10-30p` - NEMA L10-30P\n* `nema-l14-20p` - NEMA L14-20P\n* `nema-l14-30p` - NEMA L14-30P\n* `nema-l14-50p` - NEMA L14-50P\n* `nema-l14-60p` - NEMA L14-60P\n* `nema-l15-20p` - NEMA L15-20P\n* `nema-l15-30p` - NEMA L15-30P\n* `nema-l15-50p` - NEMA L15-50P\n* `nema-l15-60p` - NEMA L15-60P\n* `nema-l21-20p` - NEMA L21-20P\n* `nema-l21-30p` - NEMA L21-30P\n* `nema-l22-20p` - NEMA L22-20P\n* `nema-l22-30p` - NEMA L22-30P\n* `cs6361c` - CS6361C\n* `cs6365c` - CS6365C\n* `cs8165c` - CS8165C\n* `cs8265c` - CS8265C\n* `cs8365c` - CS8365C\n* `cs8465c` - CS8465C\n* `ita-c` - ITA Type C (CEE 7/16)\n* `ita-e` - ITA Type E (CEE 7/6)\n* `ita-f` - ITA Type F (CEE 7/4)\n* `ita-ef` - ITA Type E/F (CEE 7/7)\n* `ita-g` - ITA Type G (BS 1363)\n* `ita-h` - ITA Type H\n* `ita-i` - ITA Type I\n* `ita-j` - ITA Type J\n* `ita-k` - ITA Type K\n* `ita-l` - ITA Type L (CEI 23-50)\n* `ita-m` - ITA Type M (BS 546)\n* `ita-n` - ITA Type N\n* `ita-o` - ITA Type O\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `usb-3-b` - USB 3.0 Type B\n* `usb-3-micro-b` - USB 3.0 Micro B\n* `molex-micro-fit-1x2` - Molex Micro-Fit 1x2\n* `molex-micro-fit-2x2` - Molex Micro-Fit 2x2\n* `molex-micro-fit-2x3` - Molex Micro-Fit 2x3\n* `molex-micro-fit-2x4` - Molex Micro-Fit 2x4\n* `dc-terminal` - DC Terminal\n* `saf-d-grid` - Saf-D-Grid\n* `neutrik-powercon-20` - Neutrik powerCON (20A)\n* `neutrik-powercon-32` - Neutrik powerCON (32A)\n* `neutrik-powercon-true1` - Neutrik powerCON TRUE1\n* `neutrik-powercon-true1-top` - Neutrik powerCON TRUE1 TOP\n* `ubiquiti-smartpower` - Ubiquiti SmartPower\n* `hardwired` - Hardwired\n* `other` - Other",
+ "x-spec-enum-id": "aadcbe6ca854c1ed",
+ "nullable": true
+ },
+ "maximum_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Maximum power draw (watts)"
+ },
+ "allocated_draw": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 1,
+ "nullable": true,
+ "description": "Allocated power draw (watts)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritablePrefixRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "prefix": {
+ "type": "string",
+ "minLength": 1
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "container",
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "026173ce39f2ee63",
+ "description": "Operational status of this prefix\n\n* `container` - Container\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "is_pool": {
+ "type": "boolean",
+ "title": "Is a pool",
+ "description": "All IP addresses within this prefix are considered usable"
+ },
+ "mark_utilized": {
+ "type": "boolean",
+ "description": "Treat as fully utilized"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "prefix"
+ ]
+ },
+ "WritableRackRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "facility_id": {
+ "type": "string",
+ "nullable": true,
+ "maxLength": 50
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ]
+ },
+ "location": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefLocationRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "reserved",
+ "available",
+ "planned",
+ "active",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `reserved` - Reserved\n* `available` - Available\n* `planned` - Planned\n* `active` - Active\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "76eea4eef8804bcb"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "asset_tag": {
+ "type": "string",
+ "nullable": true,
+ "description": "A unique tag used to identify this rack",
+ "maxLength": 50
+ },
+ "rack_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841",
+ "nullable": true
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "9b322795f297a9c3",
+ "description": "Rail-to-rail width\n\n* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "airflow": {
+ "enum": [
+ "front-to-rear",
+ "rear-to-front",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `front-to-rear` - Front to rear\n* `rear-to-front` - Rear to front",
+ "x-spec-enum-id": "a784734d07ef1b3c",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "site"
+ ]
+ },
+ "WritableRackReservationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "rack": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefRackRequest"
+ }
+ ]
+ },
+ "units": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0
+ }
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "active",
+ "stale"
+ ],
+ "type": "string",
+ "description": "* `pending` - Pending\n* `active` - Active\n* `stale` - Stale",
+ "x-spec-enum-id": "ed6038a4deee151c"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "description",
+ "rack",
+ "units",
+ "user"
+ ]
+ },
+ "WritableRackTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "manufacturer": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefManufacturerRequest"
+ }
+ ]
+ },
+ "model": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "form_factor": {
+ "enum": [
+ "2-post-frame",
+ "4-post-frame",
+ "4-post-cabinet",
+ "wall-frame",
+ "wall-frame-vertical",
+ "wall-cabinet",
+ "wall-cabinet-vertical"
+ ],
+ "type": "string",
+ "description": "* `2-post-frame` - 2-post frame\n* `4-post-frame` - 4-post frame\n* `4-post-cabinet` - 4-post cabinet\n* `wall-frame` - Wall-mounted frame\n* `wall-frame-vertical` - Wall-mounted frame (vertical)\n* `wall-cabinet` - Wall-mounted cabinet\n* `wall-cabinet-vertical` - Wall-mounted cabinet (vertical)",
+ "x-spec-enum-id": "8a902fde21d48841"
+ },
+ "width": {
+ "enum": [
+ 10,
+ 19,
+ 21,
+ 23
+ ],
+ "type": "integer",
+ "x-spec-enum-id": "9b322795f297a9c3",
+ "description": "Rail-to-rail width\n\n* `10` - 10 inches\n* `19` - 19 inches\n* `21` - 21 inches\n* `23` - 23 inches",
+ "minimum": 0,
+ "maximum": 32767
+ },
+ "u_height": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Height (U)",
+ "description": "Height in rack units"
+ },
+ "starting_unit": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 1,
+ "description": "Starting unit for rack"
+ },
+ "desc_units": {
+ "type": "boolean",
+ "title": "Descending units",
+ "description": "Units are numbered top-to-bottom"
+ },
+ "outer_width": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (width)"
+ },
+ "outer_height": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (height)"
+ },
+ "outer_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Outer dimension of rack (depth)"
+ },
+ "outer_unit": {
+ "enum": [
+ "mm",
+ "in",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `mm` - Millimeters\n* `in` - Inches",
+ "x-spec-enum-id": "3d701848b66312c3",
+ "nullable": true
+ },
+ "weight": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "max_weight": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum load capacity for the rack"
+ },
+ "weight_unit": {
+ "enum": [
+ "kg",
+ "g",
+ "lb",
+ "oz",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `kg` - Kilograms\n* `g` - Grams\n* `lb` - Pounds\n* `oz` - Ounces",
+ "x-spec-enum-id": "2235ce3f404afbc0",
+ "nullable": true
+ },
+ "mounting_depth": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true,
+ "description": "Maximum depth of a mounted device, in millimeters. For four-post racks, this is the distance between the front and rear rails."
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "form_factor",
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "WritableRearPortRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mark_connected": {
+ "type": "boolean",
+ "description": "Treat as if a cable is connected"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "type"
+ ]
+ },
+ "WritableRearPortTemplateRequest": {
+ "type": "object",
+ "description": "Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during\nvalidation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)",
+ "properties": {
+ "device_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "module_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "{module} is accepted as a substitution for the module bay position when attached to a module type.",
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "type": {
+ "enum": [
+ "8p8c",
+ "8p6c",
+ "8p4c",
+ "8p2c",
+ "6p6c",
+ "6p4c",
+ "6p2c",
+ "4p4c",
+ "4p2c",
+ "gg45",
+ "tera-4p",
+ "tera-2p",
+ "tera-1p",
+ "110-punch",
+ "bnc",
+ "f",
+ "n",
+ "mrj21",
+ "fc",
+ "fc-pc",
+ "fc-upc",
+ "fc-apc",
+ "lc",
+ "lc-pc",
+ "lc-upc",
+ "lc-apc",
+ "lsh",
+ "lsh-pc",
+ "lsh-upc",
+ "lsh-apc",
+ "lx5",
+ "lx5-pc",
+ "lx5-upc",
+ "lx5-apc",
+ "mpo",
+ "mtrj",
+ "sc",
+ "sc-pc",
+ "sc-upc",
+ "sc-apc",
+ "st",
+ "cs",
+ "sn",
+ "sma-905",
+ "sma-906",
+ "urm-p2",
+ "urm-p4",
+ "urm-p8",
+ "splice",
+ "usb-a",
+ "usb-b",
+ "usb-c",
+ "usb-mini-a",
+ "usb-mini-b",
+ "usb-micro-a",
+ "usb-micro-b",
+ "usb-micro-ab",
+ "other"
+ ],
+ "type": "string",
+ "description": "* `8p8c` - 8P8C\n* `8p6c` - 8P6C\n* `8p4c` - 8P4C\n* `8p2c` - 8P2C\n* `6p6c` - 6P6C\n* `6p4c` - 6P4C\n* `6p2c` - 6P2C\n* `4p4c` - 4P4C\n* `4p2c` - 4P2C\n* `gg45` - GG45\n* `tera-4p` - TERA 4P\n* `tera-2p` - TERA 2P\n* `tera-1p` - TERA 1P\n* `110-punch` - 110 Punch\n* `bnc` - BNC\n* `f` - F Connector\n* `n` - N Connector\n* `mrj21` - MRJ21\n* `fc` - FC\n* `fc-pc` - FC/PC\n* `fc-upc` - FC/UPC\n* `fc-apc` - FC/APC\n* `lc` - LC\n* `lc-pc` - LC/PC\n* `lc-upc` - LC/UPC\n* `lc-apc` - LC/APC\n* `lsh` - LSH\n* `lsh-pc` - LSH/PC\n* `lsh-upc` - LSH/UPC\n* `lsh-apc` - LSH/APC\n* `lx5` - LX.5\n* `lx5-pc` - LX.5/PC\n* `lx5-upc` - LX.5/UPC\n* `lx5-apc` - LX.5/APC\n* `mpo` - MPO\n* `mtrj` - MTRJ\n* `sc` - SC\n* `sc-pc` - SC/PC\n* `sc-upc` - SC/UPC\n* `sc-apc` - SC/APC\n* `st` - ST\n* `cs` - CS\n* `sn` - SN\n* `sma-905` - SMA 905\n* `sma-906` - SMA 906\n* `urm-p2` - URM-P2\n* `urm-p4` - URM-P4\n* `urm-p8` - URM-P8\n* `splice` - Splice\n* `usb-a` - USB Type A\n* `usb-b` - USB Type B\n* `usb-c` - USB Type C\n* `usb-mini-a` - USB Mini A\n* `usb-mini-b` - USB Mini B\n* `usb-micro-a` - USB Micro A\n* `usb-micro-b` - USB Micro B\n* `usb-micro-ab` - USB Micro AB\n* `other` - Other",
+ "x-spec-enum-id": "2696b7065f33307c"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{6}$",
+ "maxLength": 6
+ },
+ "positions": {
+ "type": "integer",
+ "maximum": 1024,
+ "minimum": 1
+ },
+ "front_ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RearPortTemplateMappingRequest"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "name",
+ "type"
+ ]
+ },
+ "WritableRegionRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableServiceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "parent_object_type": {
+ "type": "string"
+ },
+ "parent_object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "ipaddresses": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "parent_object_id",
+ "parent_object_type",
+ "ports",
+ "protocol"
+ ]
+ },
+ "WritableServiceTemplateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "protocol": {
+ "enum": [
+ "tcp",
+ "udp",
+ "sctp"
+ ],
+ "type": "string",
+ "description": "* `tcp` - TCP\n* `udp` - UDP\n* `sctp` - SCTP",
+ "x-spec-enum-id": "e4b15bec749a2a32"
+ },
+ "ports": {
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 1
+ },
+ "title": "Port numbers"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "ports",
+ "protocol"
+ ]
+ },
+ "WritableSiteGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableSiteRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full name of the site",
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "staging",
+ "active",
+ "decommissioning",
+ "retired"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `staging` - Staging\n* `active` - Active\n* `decommissioning` - Decommissioning\n* `retired` - Retired",
+ "x-spec-enum-id": "1cf60831fbb35e7f"
+ },
+ "region": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRegionRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "facility": {
+ "type": "string",
+ "description": "Local facility ID or description",
+ "maxLength": 50
+ },
+ "time_zone": {
+ "type": "string",
+ "nullable": true,
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "physical_address": {
+ "type": "string",
+ "description": "Physical location of the building",
+ "maxLength": 200
+ },
+ "shipping_address": {
+ "type": "string",
+ "description": "If different from the physical address",
+ "maxLength": 200
+ },
+ "latitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 90.0,
+ "minimum": -90.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "longitude": {
+ "type": "number",
+ "format": "double",
+ "maximum": 180.0,
+ "minimum": -180.0,
+ "nullable": true,
+ "description": "GPS coordinate in decimal format (xx.yyyyyy)"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "asns": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableTenantGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableTunnelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "active",
+ "disabled"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `active` - Active\n* `disabled` - Disabled",
+ "x-spec-enum-id": "2431ef62c418f485"
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTunnelGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "encapsulation": {
+ "enum": [
+ "ipsec-transport",
+ "ipsec-tunnel",
+ "ip-ip",
+ "gre",
+ "wireguard",
+ "openvpn",
+ "l2tp",
+ "pptp"
+ ],
+ "type": "string",
+ "description": "* `ipsec-transport` - IPsec - Transport\n* `ipsec-tunnel` - IPsec - Tunnel\n* `ip-ip` - IP-in-IP\n* `gre` - GRE\n* `wireguard` - WireGuard\n* `openvpn` - OpenVPN\n* `l2tp` - L2TP\n* `pptp` - PPTP",
+ "x-spec-enum-id": "4f3254459f0e94f0"
+ },
+ "ipsec_profile": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPSecProfileRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tunnel_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "encapsulation",
+ "name"
+ ]
+ },
+ "WritableTunnelTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "tunnel": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefTunnelRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "termination_type": {
+ "type": "string"
+ },
+ "termination_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "nullable": true
+ },
+ "outside_ip": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "termination_type",
+ "tunnel"
+ ]
+ },
+ "WritableVLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vid": {
+ "type": "integer",
+ "maximum": 4094,
+ "minimum": 1,
+ "title": "VLAN ID",
+ "description": "Numeric VLAN ID (1-4094)"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "deprecated"
+ ],
+ "type": "string",
+ "x-spec-enum-id": "ca933c38b935e547",
+ "description": "Operational status of this VLAN\n\n* `active` - Active\n* `reserved` - Reserved\n* `deprecated` - Deprecated"
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "qinq_role": {
+ "enum": [
+ "svlan",
+ "cvlan",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "fa0abd59fb1a7312",
+ "nullable": true,
+ "title": "Q-in-Q role",
+ "description": "Customer/service VLAN designation (for Q-in-Q/IEEE 802.1ad)\n\n* `svlan` - Service\n* `cvlan` - Customer"
+ },
+ "qinq_svlan": {
+ "type": "integer",
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "vid"
+ ]
+ },
+ "WritableVMInterfaceRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_machine": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Parent interface"
+ },
+ "bridge": {
+ "type": "integer",
+ "nullable": true,
+ "title": "Bridge interface"
+ },
+ "mtu": {
+ "type": "integer",
+ "maximum": 65536,
+ "minimum": 1,
+ "nullable": true
+ },
+ "primary_mac_address": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefMACAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "mode": {
+ "enum": [
+ "access",
+ "tagged",
+ "tagged-all",
+ "q-in-q",
+ "",
+ null
+ ],
+ "type": "string",
+ "x-spec-enum-id": "84129b71b974ebe5",
+ "nullable": true,
+ "description": "IEEE 802.1Q tagging strategy\n\n* `access` - Access\n* `tagged` - Tagged\n* `tagged-all` - Tagged (All)\n* `q-in-q` - Q-in-Q (802.1ad)"
+ },
+ "untagged_vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tagged_vlans": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "qinq_svlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vlan_translation_policy": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANTranslationPolicyRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vrf": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVRFRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "virtual_machine"
+ ]
+ },
+ "WritableVirtualChassisRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "domain": {
+ "type": "string",
+ "maxLength": 30
+ },
+ "master": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableVirtualCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Circuit ID",
+ "description": "Unique circuit ID",
+ "maxLength": 100
+ },
+ "provider_network": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefProviderNetworkRequest"
+ }
+ ]
+ },
+ "provider_account": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefProviderAccountRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitTypeRequest"
+ }
+ ]
+ },
+ "status": {
+ "enum": [
+ "planned",
+ "provisioning",
+ "active",
+ "offline",
+ "deprovisioning",
+ "decommissioned"
+ ],
+ "type": "string",
+ "description": "* `planned` - Planned\n* `provisioning` - Provisioning\n* `active` - Active\n* `offline` - Offline\n* `deprovisioning` - Deprovisioning\n* `decommissioned` - Decommissioned",
+ "x-spec-enum-id": "0a239d878b6666a4"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "cid",
+ "provider_network",
+ "type"
+ ]
+ },
+ "WritableVirtualCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "virtual_circuit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefVirtualCircuitRequest"
+ }
+ ]
+ },
+ "role": {
+ "enum": [
+ "peer",
+ "hub",
+ "spoke"
+ ],
+ "type": "string",
+ "description": "* `peer` - Peer\n* `hub` - Hub\n* `spoke` - Spoke",
+ "x-spec-enum-id": "0b3bfadcebd86b58"
+ },
+ "interface": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "interface",
+ "virtual_circuit"
+ ]
+ },
+ "WritableVirtualDeviceContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "identifier": {
+ "type": "integer",
+ "maximum": 32767,
+ "minimum": 0,
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "planned",
+ "offline"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `planned` - Planned\n* `offline` - Offline",
+ "x-spec-enum-id": "0e2c0919d51b83cb"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "name",
+ "status"
+ ]
+ },
+ "WritableVirtualMachineWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "status": {
+ "enum": [
+ "offline",
+ "active",
+ "planned",
+ "staged",
+ "failed",
+ "decommissioning",
+ "paused"
+ ],
+ "type": "string",
+ "description": "* `offline` - Offline\n* `active` - Active\n* `planned` - Planned\n* `staged` - Staged\n* `failed` - Failed\n* `decommissioning` - Decommissioning\n* `paused` - Paused",
+ "x-spec-enum-id": "effecc3b94e0b74b"
+ },
+ "start_on_boot": {
+ "enum": [
+ "on",
+ "off",
+ "laststate"
+ ],
+ "type": "string",
+ "description": "* `on` - On\n* `off` - Off\n* `laststate` - Last State",
+ "x-spec-enum-id": "610e33fc2fde73d6"
+ },
+ "site": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefSiteRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "cluster": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefClusterRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip4": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "primary_ip6": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefIPAddressRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Memory (MB)"
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Disk (MB)"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "comments": {
+ "type": "string"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "WritableWirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "parent": {
+ "type": "integer",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "slug"
+ ]
+ },
+ "WritableWirelessLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "ssid": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefWirelessLANGroupRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "status": {
+ "enum": [
+ "active",
+ "reserved",
+ "disabled",
+ "deprecated"
+ ],
+ "type": "string",
+ "description": "* `active` - Active\n* `reserved` - Reserved\n* `disabled` - Disabled\n* `deprecated` - Deprecated",
+ "x-spec-enum-id": "e5549d7370ce2e6c"
+ },
+ "vlan": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVLANRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "scope_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope_id": {
+ "type": "integer",
+ "nullable": true
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c",
+ "nullable": true,
+ "title": "Authentication cipher"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "ssid"
+ ]
+ },
+ "WritableWirelessLinkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "interface_a": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "interface_b": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefInterfaceRequest"
+ }
+ ]
+ },
+ "ssid": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "status": {
+ "enum": [
+ "connected",
+ "planned",
+ "decommissioning"
+ ],
+ "type": "string",
+ "description": "* `connected` - Connected\n* `planned` - Planned\n* `decommissioning` - Decommissioning",
+ "x-spec-enum-id": "80d251a40f3a3144"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "auth_type": {
+ "enum": [
+ "open",
+ "wep",
+ "wpa-personal",
+ "wpa-enterprise",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `open` - Open\n* `wep` - WEP\n* `wpa-personal` - WPA Personal (PSK)\n* `wpa-enterprise` - WPA Enterprise",
+ "x-spec-enum-id": "e917c12aac765910",
+ "nullable": true,
+ "title": "Authentication type"
+ },
+ "auth_cipher": {
+ "enum": [
+ "auto",
+ "tkip",
+ "aes",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `auto` - Auto\n* `tkip` - TKIP\n* `aes` - AES",
+ "x-spec-enum-id": "42f867e89988bb0c",
+ "nullable": true,
+ "title": "Authentication cipher"
+ },
+ "auth_psk": {
+ "type": "string",
+ "title": "Pre-shared key",
+ "maxLength": 64
+ },
+ "distance": {
+ "type": "number",
+ "format": "double",
+ "maximum": 1000000,
+ "minimum": -1000000,
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "nullable": true
+ },
+ "distance_unit": {
+ "enum": [
+ "km",
+ "m",
+ "mi",
+ "ft",
+ "",
+ null
+ ],
+ "type": "string",
+ "description": "* `km` - Kilometers\n* `m` - Meters\n* `mi` - Miles\n* `ft` - Feet",
+ "x-spec-enum-id": "b1169a409430c02e",
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "comments": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "interface_a",
+ "interface_b"
+ ]
+ }
+ },
+ "securitySchemes": {
+ "cookieAuth": {
+ "type": "apiKey",
+ "in": "cookie",
+ "name": "sessionid"
+ },
+ "tokenAuth": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "Authorization",
+ "description": "`Token vdc1,vdc2,vdc3'
+ _('VDC names separated by commas, encased with double quotes. Example:') + ' "vdc1,vdc2,vdc3"'
)
)
type = CSVChoiceField(
@@ -967,7 +987,41 @@ class InterfaceImportForm(NetBoxModelImportForm):
label=_('Mode'),
choices=InterfaceModeChoices,
required=False,
- help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
+ help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)'),
+ )
+ vlan_group = CSVModelChoiceField(
+ label=_('VLAN group'),
+ queryset=VLANGroup.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text=_('Filter VLANs available for assignment by group'),
+ )
+ untagged_vlan = CSVModelChoiceField(
+ label=_('Untagged VLAN'),
+ queryset=VLAN.objects.all(),
+ required=False,
+ to_field_name='vid',
+ help_text=_('Assigned untagged VLAN ID (filtered by VLAN group)'),
+ )
+ tagged_vlans = CSVModelMultipleChoiceField(
+ label=_('Tagged VLANs'),
+ queryset=VLAN.objects.all(),
+ required=False,
+ to_field_name='vid',
+ help_text=mark_safe(
+ _(
+ 'Assigned tagged VLAN IDs separated by commas, encased with double quotes '
+ '(filtered by VLAN group). Example:'
+ )
+ + ' "100,200,300"'
+ ),
+ )
+ qinq_svlan = CSVModelChoiceField(
+ label=_('Q-in-Q Service VLAN'),
+ queryset=VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
+ required=False,
+ to_field_name='vid',
+ help_text=_('Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)'),
)
vrf = CSVModelChoiceField(
label=_('VRF'),
@@ -988,7 +1042,8 @@ class InterfaceImportForm(NetBoxModelImportForm):
fields = (
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
'mark_connected', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
- 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
+ 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vrf', 'rf_role', 'rf_channel',
+ 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'owner', 'tags'
)
def __init__(self, data=None, *args, **kwargs):
@@ -1005,6 +1060,13 @@ class InterfaceImportForm(NetBoxModelImportForm):
self.fields['lag'].queryset = self.fields['lag'].queryset.filter(**params)
self.fields['vdcs'].queryset = self.fields['vdcs'].queryset.filter(**params)
+ # Limit choices for VLANs to the assigned VLAN group
+ if vlan_group := data.get('vlan_group'):
+ params = {f"group__{self.fields['vlan_group'].to_field_name}": vlan_group}
+ self.fields['untagged_vlan'].queryset = self.fields['untagged_vlan'].queryset.filter(**params)
+ self.fields['tagged_vlans'].queryset = self.fields['tagged_vlans'].queryset.filter(**params)
+ self.fields['qinq_svlan'].queryset = self.fields['qinq_svlan'].queryset.filter(**params)
+
def clean_enabled(self):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:
@@ -1023,18 +1085,12 @@ class InterfaceImportForm(NetBoxModelImportForm):
return self.cleaned_data['vdcs']
-class FrontPortImportForm(NetBoxModelImportForm):
+class FrontPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
- rear_port = CSVModelChoiceField(
- label=_('Rear port'),
- queryset=RearPort.objects.all(),
- to_field_name='name',
- help_text=_('Corresponding rear port')
- )
type = CSVChoiceField(
label=_('Type'),
choices=PortTypeChoices,
@@ -1044,34 +1100,11 @@ class FrontPortImportForm(NetBoxModelImportForm):
class Meta:
model = FrontPort
fields = (
- 'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position',
- 'description', 'tags'
+ 'device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'owner', 'tags'
)
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # Limit RearPort choices to those belonging to this device (or VC master)
- if self.is_bound and 'device' in self.data:
- try:
- device = self.fields['device'].to_python(self.data['device'])
- except forms.ValidationError:
- device = None
- else:
- try:
- device = self.instance.device
- except Device.DoesNotExist:
- device = None
-
- if device:
- self.fields['rear_port'].queryset = RearPort.objects.filter(
- device__in=[device, device.get_vc_master()]
- )
- else:
- self.fields['rear_port'].queryset = RearPort.objects.none()
-
-
-class RearPortImportForm(NetBoxModelImportForm):
+class RearPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1085,10 +1118,12 @@ class RearPortImportForm(NetBoxModelImportForm):
class Meta:
model = RearPort
- fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
+ fields = (
+ 'device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'owner', 'tags',
+ )
-class ModuleBayImportForm(NetBoxModelImportForm):
+class ModuleBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1097,10 +1132,10 @@ class ModuleBayImportForm(NetBoxModelImportForm):
class Meta:
model = ModuleBay
- fields = ('device', 'name', 'label', 'position', 'description', 'tags')
+ fields = ('device', 'name', 'label', 'position', 'description', 'owner', 'tags')
-class DeviceBayImportForm(NetBoxModelImportForm):
+class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1119,7 +1154,7 @@ class DeviceBayImportForm(NetBoxModelImportForm):
class Meta:
model = DeviceBay
- fields = ('device', 'name', 'label', 'installed_device', 'description', 'tags')
+ fields = ('device', 'name', 'label', 'installed_device', 'description', 'owner', 'tags')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1148,7 +1183,7 @@ class DeviceBayImportForm(NetBoxModelImportForm):
self.fields['installed_device'].queryset = Device.objects.none()
-class InventoryItemImportForm(NetBoxModelImportForm):
+class InventoryItemImportForm(OwnerCSVMixin, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1181,7 +1216,7 @@ class InventoryItemImportForm(NetBoxModelImportForm):
help_text=_('Component Type')
)
component_name = forms.CharField(
- label=_('Compnent name'),
+ label=_('Component name'),
required=False,
help_text=_('Component Name')
)
@@ -1195,7 +1230,7 @@ class InventoryItemImportForm(NetBoxModelImportForm):
model = InventoryItem
fields = (
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag',
- 'discovered', 'description', 'tags', 'component_type', 'component_name',
+ 'discovered', 'description', 'owner', 'tags', 'component_type', 'component_name',
)
def __init__(self, *args, **kwargs):
@@ -1258,19 +1293,19 @@ class InventoryItemImportForm(NetBoxModelImportForm):
# Device component roles
#
-class InventoryItemRoleImportForm(NetBoxModelImportForm):
+class InventoryItemRoleImportForm(OrganizationalModelImportForm):
slug = SlugField()
class Meta:
model = InventoryItemRole
- fields = ('name', 'slug', 'color', 'description')
+ fields = ('name', 'slug', 'color', 'description', 'owner', 'comments')
#
# Addressing
#
-class MACAddressImportForm(NetBoxModelImportForm):
+class MACAddressImportForm(PrimaryModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1301,7 +1336,8 @@ class MACAddressImportForm(NetBoxModelImportForm):
class Meta:
model = MACAddress
fields = [
- 'mac_address', 'device', 'virtual_machine', 'interface', 'is_primary', 'description', 'comments', 'tags',
+ 'mac_address', 'device', 'virtual_machine', 'interface', 'is_primary', 'description', 'owner', 'comments',
+ 'tags',
]
def __init__(self, data=None, *args, **kwargs):
@@ -1354,7 +1390,7 @@ class MACAddressImportForm(NetBoxModelImportForm):
# Cables
#
-class CableImportForm(NetBoxModelImportForm):
+class CableImportForm(PrimaryModelImportForm):
# Termination A
side_a_site = CSVModelChoiceField(
label=_('Side A site'),
@@ -1412,6 +1448,12 @@ class CableImportForm(NetBoxModelImportForm):
required=False,
help_text=_('Connection status')
)
+ profile = CSVChoiceField(
+ label=_('Profile'),
+ choices=CableProfileChoices,
+ required=False,
+ help_text=_('Cable connection profile')
+ )
type = CSVChoiceField(
label=_('Type'),
choices=CableTypeChoices,
@@ -1442,8 +1484,8 @@ class CableImportForm(NetBoxModelImportForm):
model = Cable
fields = [
'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
- 'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
- 'comments', 'tags',
+ 'side_b_name', 'type', 'status', 'profile', 'tenant', 'label', 'color', 'length', 'length_unit',
+ 'description', 'owner', 'comments', 'tags',
]
def __init__(self, data=None, *args, **kwargs):
@@ -1537,7 +1579,7 @@ class CableImportForm(NetBoxModelImportForm):
#
-class VirtualChassisImportForm(NetBoxModelImportForm):
+class VirtualChassisImportForm(PrimaryModelImportForm):
master = CSVModelChoiceField(
label=_('Master'),
queryset=Device.objects.all(),
@@ -1548,14 +1590,14 @@ class VirtualChassisImportForm(NetBoxModelImportForm):
class Meta:
model = VirtualChassis
- fields = ('name', 'domain', 'master', 'description', 'comments', 'tags')
+ fields = ('name', 'domain', 'master', 'description', 'owner', 'comments', 'tags')
#
# Power
#
-class PowerPanelImportForm(NetBoxModelImportForm):
+class PowerPanelImportForm(PrimaryModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -1571,7 +1613,7 @@ class PowerPanelImportForm(NetBoxModelImportForm):
class Meta:
model = PowerPanel
- fields = ('site', 'location', 'name', 'description', 'comments', 'tags')
+ fields = ('site', 'location', 'name', 'description', 'owner', 'comments', 'tags')
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
@@ -1583,7 +1625,7 @@ class PowerPanelImportForm(NetBoxModelImportForm):
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
-class PowerFeedImportForm(NetBoxModelImportForm):
+class PowerFeedImportForm(PrimaryModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -1641,7 +1683,7 @@ class PowerFeedImportForm(NetBoxModelImportForm):
model = PowerFeed
fields = (
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
- 'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'comments', 'tags',
+ 'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'owner', 'comments', 'tags',
)
def __init__(self, data=None, *args, **kwargs):
@@ -1665,8 +1707,7 @@ class PowerFeedImportForm(NetBoxModelImportForm):
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
-class VirtualDeviceContextImportForm(NetBoxModelImportForm):
-
+class VirtualDeviceContextImportForm(PrimaryModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1701,7 +1742,7 @@ class VirtualDeviceContextImportForm(NetBoxModelImportForm):
class Meta:
fields = [
- 'name', 'device', 'status', 'tenant', 'identifier', 'comments', 'primary_ip4', 'primary_ip6',
+ 'name', 'device', 'status', 'tenant', 'identifier', 'owner', 'comments', 'primary_ip4', 'primary_ip6',
]
model = VirtualDeviceContext
diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py
index daa3eef65..ea14a5b14 100644
--- a/netbox/dcim/forms/filtersets.py
+++ b/netbox/dcim/forms/filtersets.py
@@ -8,11 +8,15 @@ from extras.forms import LocalConfigContextFilterForm
from extras.models import ConfigTemplate
from ipam.models import ASN, VRF, VLANTranslationPolicy
from netbox.choices import *
-from netbox.forms import NetBoxModelFilterSetForm
+from netbox.forms import (
+ NestedGroupModelFilterSetForm, NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm,
+ PrimaryModelFilterSetForm,
+)
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
-from users.models import User
+from tenancy.models import Tenant
+from users.models import Owner, User
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
-from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
+from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import NumberWithOptions
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
@@ -120,6 +124,11 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Device role')
)
+ tenant_id = DynamicModelMultipleChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False,
+ label=_('Tenant')
+ )
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
@@ -128,7 +137,8 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
'location_id': '$location_id',
'virtual_chassis_id': '$virtual_chassis_id',
'device_type_id': '$device_type_id',
- 'role_id': '$role_id'
+ 'role_id': '$role_id',
+ 'tenant_id': '$tenant_id'
},
label=_('Device')
)
@@ -137,12 +147,18 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Device Status'),
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
-class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
+class RegionFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm):
model = Region
fieldsets = (
- FieldSet('q', 'filter_id', 'tag', 'parent_id'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('parent_id', name=_('Region')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
)
parent_id = DynamicModelMultipleChoiceField(
@@ -153,10 +169,11 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
+class SiteGroupFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm):
model = SiteGroup
fieldsets = (
- FieldSet('q', 'filter_id', 'tag', 'parent_id'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('parent_id', name=_('Site Group')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
)
parent_id = DynamicModelMultipleChoiceField(
@@ -167,10 +184,10 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
+class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilterSetForm):
model = Site
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('status', 'region_id', 'group_id', 'asn_id', name=_('Attributes')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
@@ -199,10 +216,10 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
tag = TagFilterField(model)
-class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
+class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NestedGroupModelFilterSetForm):
model = Location
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'parent_id', 'status', 'facility', name=_('Attributes')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
@@ -247,12 +264,15 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
tag = TagFilterField(model)
-class RackRoleFilterForm(NetBoxModelFilterSetForm):
+class RackRoleFilterForm(OrganizationalModelFilterSetForm):
model = RackRole
+ fieldsets = (
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ )
tag = TagFilterField(model)
-class RackBaseFilterForm(NetBoxModelFilterSetForm):
+class RackBaseFilterForm(PrimaryModelFilterSetForm):
form_factor = forms.MultipleChoiceField(
label=_('Form factor'),
choices=RackFormFactorChoices,
@@ -278,11 +298,6 @@ class RackBaseFilterForm(NetBoxModelFilterSetForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
- airflow = forms.MultipleChoiceField(
- label=_('Airflow'),
- choices=add_blank_choice(RackAirflowChoices),
- required=False
- )
weight = forms.DecimalField(
label=_('Weight'),
required=False,
@@ -303,8 +318,8 @@ class RackBaseFilterForm(NetBoxModelFilterSetForm):
class RackTypeFilterForm(RackBaseFilterForm):
model = RackType
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
- FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', 'rack_count', name=_('Rack Type')),
FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
)
@@ -314,13 +329,18 @@ class RackTypeFilterForm(RackBaseFilterForm):
required=False,
label=_('Manufacturer')
)
+ rack_count = forms.IntegerField(
+ label=_('Rack count'),
+ required=False,
+ min_value=0,
+ )
tag = TagFilterField(model)
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, RackBaseFilterForm):
model = Rack
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('status', 'role_id', 'manufacturer_id', 'rack_type_id', 'serial', 'asset_tag', name=_('Rack')),
@@ -381,6 +401,11 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, RackBaseFilterFo
},
label=_('Rack type')
)
+ airflow = forms.MultipleChoiceField(
+ label=_('Airflow'),
+ choices=add_blank_choice(RackAirflowChoices),
+ required=False
+ )
serial = forms.CharField(
label=_('Serial'),
required=False
@@ -413,10 +438,10 @@ class RackElevationFilterForm(RackFilterForm):
)
-class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
+class RackReservationFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
model = RackReservation
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('status', 'user_id', name=_('Reservation')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
@@ -471,21 +496,22 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
+class ManufacturerFilterForm(ContactModelFilterForm, OrganizationalModelFilterSetForm):
model = Manufacturer
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
)
tag = TagFilterField(model)
-class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
+class DeviceTypeFilterForm(PrimaryModelFilterSetForm):
model = DeviceType
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet(
- 'manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow', name=_('Hardware')
+ 'manufacturer_id', 'default_platform_id', 'part_number', 'device_count',
+ 'subdevice_role', 'airflow', name=_('Hardware')
),
FieldSet('has_front_image', 'has_rear_image', name=_('Images')),
FieldSet(
@@ -509,6 +535,11 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
label=_('Part number'),
required=False
)
+ device_count = forms.IntegerField(
+ label=_('Device count'),
+ required=False,
+ min_value=0,
+ )
subdevice_role = forms.MultipleChoiceField(
label=_('Subdevice role'),
choices=add_blank_choice(SubdeviceRoleChoices),
@@ -608,19 +639,23 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
)
-class ModuleTypeProfileFilterForm(NetBoxModelFilterSetForm):
+class ModuleTypeProfileFilterForm(PrimaryModelFilterSetForm):
model = ModuleTypeProfile
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
)
selector_fields = ('filter_id', 'q')
+ tag = TagFilterField(model)
-class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
+class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
model = ModuleType
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
- FieldSet('profile_id', 'manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet(
+ 'profile_id', 'manufacturer_id', 'part_number', 'module_count',
+ 'airflow', name=_('Hardware')
+ ),
FieldSet(
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
'pass_through_ports', name=_('Components')
@@ -642,6 +677,11 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
label=_('Part number'),
required=False
)
+ module_count = forms.IntegerField(
+ label=_('Module count'),
+ required=False,
+ min_value=0,
+ )
console_ports = forms.NullBooleanField(
required=False,
label=_('Has console ports'),
@@ -701,8 +741,12 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
)
-class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
+class DeviceRoleFilterForm(NestedGroupModelFilterSetForm):
model = DeviceRole
+ fieldsets = (
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('parent_id', 'config_template_id', name=_('Device Role'))
+ )
config_template_id = DynamicModelMultipleChoiceField(
queryset=ConfigTemplate.objects.all(),
required=False,
@@ -716,8 +760,12 @@ class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class PlatformFilterForm(NetBoxModelFilterSetForm):
+class PlatformFilterForm(NestedGroupModelFilterSetForm):
model = Platform
+ fieldsets = (
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('manufacturer_id', 'parent_id', 'config_template_id', name=_('Platform'))
+ )
selector_fields = ('filter_id', 'q', 'manufacturer_id')
parent_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
@@ -741,11 +789,11 @@ class DeviceFilterForm(
LocalConfigContextFilterForm,
TenancyFilterForm,
ContactModelFilterForm,
- NetBoxModelFilterSetForm
+ PrimaryModelFilterSetForm
):
model = Device
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address', name=_('Operation')),
FieldSet('manufacturer_id', 'device_type_id', 'platform_id', name=_('Hardware')),
@@ -935,13 +983,10 @@ class DeviceFilterForm(
tag = TagFilterField(model)
-class VirtualDeviceContextFilterForm(
- TenancyFilterForm,
- NetBoxModelFilterSetForm
-):
+class VirtualDeviceContextFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
model = VirtualDeviceContext
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('device', 'status', 'has_primary_ip', name=_('Attributes')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
)
@@ -965,10 +1010,10 @@ class VirtualDeviceContextFilterForm(
tag = TagFilterField(model)
-class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
+class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm):
model = Module
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
)
@@ -1048,10 +1093,10 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
tag = TagFilterField(model)
-class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
+class VirtualChassisFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
model = VirtualChassis
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
)
@@ -1077,12 +1122,12 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
+class CableFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
model = Cable
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
- FieldSet('type', 'status', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')),
+ FieldSet('type', 'status', 'profile', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
)
region_id = DynamicModelMultipleChoiceField(
@@ -1138,6 +1183,11 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
required=False,
choices=add_blank_choice(LinkStatusChoices)
)
+ profile = forms.MultipleChoiceField(
+ label=_('Profile'),
+ required=False,
+ choices=add_blank_choice(CableProfileChoices)
+ )
color = ColorField(
label=_('Color'),
required=False
@@ -1161,10 +1211,10 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
+class PowerPanelFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
model = PowerPanel
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
)
@@ -1200,10 +1250,10 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
+class PowerFeedFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
model = PowerFeed
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id', name=_('Location')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Attributes')),
@@ -1313,11 +1363,12 @@ class PathEndpointFilterForm(CabledFilterForm):
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = ConsolePort
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
@@ -1337,11 +1388,11 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = ConsoleServerPort
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
@@ -1362,11 +1413,12 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = PowerPort
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
)
@@ -1381,11 +1433,11 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = PowerOutlet
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', 'color', 'status', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
@@ -1410,7 +1462,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = Interface
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
@@ -1418,7 +1470,8 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', 'vdc_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'vdc_id',
name=_('Device')
),
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
@@ -1535,11 +1588,12 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id', name=_('Device')
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ name=_('Device')
),
FieldSet('cabled', 'occupied', name=_('Cable')),
)
@@ -1559,11 +1613,11 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
model = RearPort
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
FieldSet('cabled', 'occupied', name=_('Cable')),
@@ -1583,11 +1637,11 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
class ModuleBayFilterForm(DeviceComponentFilterForm):
model = ModuleBay
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', 'position', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
@@ -1601,11 +1655,11 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
class DeviceBayFilterForm(DeviceComponentFilterForm):
model = DeviceBay
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('name', 'label', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
@@ -1615,14 +1669,14 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
class InventoryItemFilterForm(DeviceComponentFilterForm):
model = InventoryItem
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet(
'name', 'label', 'status', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered',
name=_('Attributes')
),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
FieldSet(
- 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
+ 'tenant_id', 'device_type_id', 'device_role_id', 'device_id', 'device_status', 'virtual_chassis_id',
name=_('Device')
),
)
@@ -1663,8 +1717,11 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
# Device component roles
#
-class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
+class InventoryItemRoleFilterForm(OrganizationalModelFilterSetForm):
model = InventoryItemRole
+ fieldsets = (
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ )
tag = TagFilterField(model)
@@ -1672,16 +1729,17 @@ class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
# Addressing
#
-class MACAddressFilterForm(NetBoxModelFilterSetForm):
+class MACAddressFilterForm(PrimaryModelFilterSetForm):
model = MACAddress
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
- FieldSet('mac_address', 'device_id', 'virtual_machine_id', name=_('MAC address')),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
+ FieldSet('mac_address', name=_('Attributes')),
+ FieldSet('device_id', 'virtual_machine_id', 'assigned', 'primary', name=_('Assignments')),
)
selector_fields = ('filter_id', 'q', 'device_id', 'virtual_machine_id')
mac_address = forms.CharField(
required=False,
- label=_('MAC address')
+ label=_('MAC address'),
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@@ -1693,6 +1751,20 @@ class MACAddressFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Assigned VM'),
)
+ assigned = forms.NullBooleanField(
+ required=False,
+ label=_('Assigned to an interface'),
+ widget=forms.Select(
+ choices=BOOLEAN_WITH_BLANK_CHOICES
+ ),
+ )
+ primary = forms.NullBooleanField(
+ required=False,
+ label=_('Primary MAC of an interface'),
+ widget=forms.Select(
+ choices=BOOLEAN_WITH_BLANK_CHOICES
+ ),
+ )
tag = TagFilterField(model)
diff --git a/netbox/dcim/forms/mixins.py b/netbox/dcim/forms/mixins.py
index 5a57e3364..b2fc46bc3 100644
--- a/netbox/dcim/forms/mixins.py
+++ b/netbox/dcim/forms/mixins.py
@@ -1,10 +1,12 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.db import connection
+from django.db.models.signals import post_save
from django.utils.translation import gettext_lazy as _
from dcim.constants import LOCATION_SCOPE_TYPES
-from dcim.models import Site
+from dcim.models import PortMapping, PortTemplateMapping, Site
from utilities.forms import get_field_value
from utilities.forms.fields import (
ContentTypeChoiceField, CSVContentTypeField, DynamicModelChoiceField,
@@ -13,6 +15,7 @@ from utilities.templatetags.builtins.filters import bettertitle
from utilities.forms.widgets import HTMXSelect
__all__ = (
+ 'FrontPortFormMixin',
'ScopedBulkEditForm',
'ScopedForm',
'ScopedImportForm',
@@ -48,8 +51,17 @@ class ScopedForm(forms.Form):
def clean(self):
super().clean()
+ scope = self.cleaned_data.get('scope')
+ scope_type = self.cleaned_data.get('scope_type')
+ if scope_type and not scope:
+ raise ValidationError({
+ 'scope': _(
+ "Please select a {scope_type}."
+ ).format(scope_type=scope_type.model_class()._meta.model_name)
+ })
+
# Assign the selected scope (if any)
- self.instance.scope = self.cleaned_data.get('scope')
+ self.instance.scope = scope
def _set_scoped_values(self):
if scope_type_id := get_field_value(self, 'scope_type'):
@@ -107,3 +119,87 @@ class ScopedImportForm(forms.Form):
required=False,
label=_('Scope type (app & model)')
)
+
+ def clean(self):
+ super().clean()
+
+ scope_id = self.cleaned_data.get('scope_id')
+ scope_type = self.cleaned_data.get('scope_type')
+ if scope_type and not scope_id:
+ raise ValidationError({
+ 'scope_id': _(
+ "Please select a {scope_type}."
+ ).format(scope_type=scope_type.model_class()._meta.model_name)
+ })
+
+
+class FrontPortFormMixin(forms.Form):
+ rear_ports = forms.MultipleChoiceField(
+ choices=[],
+ label=_('Rear ports'),
+ widget=forms.SelectMultiple(attrs={'size': 8})
+ )
+
+ port_mapping_model = PortMapping
+ parent_field = 'device'
+
+ def clean(self):
+ super().clean()
+
+ # Check that the total number of FrontPorts and positions matches the selected number of RearPort:position
+ # mappings. Note that `name` will be a list under FrontPortCreateForm, in which cases we multiply the number of
+ # FrontPorts being creation by the number of positions.
+ positions = self.cleaned_data['positions']
+ frontport_count = len(self.cleaned_data['name']) if type(self.cleaned_data['name']) is list else 1
+ rearport_count = len(self.cleaned_data['rear_ports'])
+ if frontport_count * positions != rearport_count:
+ raise forms.ValidationError({
+ 'rear_ports': _(
+ "The total number of front port positions ({frontport_count}) must match the selected number of "
+ "rear port positions ({rearport_count})."
+ ).format(
+ frontport_count=frontport_count,
+ rearport_count=rearport_count
+ )
+ })
+
+ def _save_m2m(self):
+ super()._save_m2m()
+
+ # TODO: Can this be made more efficient?
+ # Delete existing rear port mappings
+ self.port_mapping_model.objects.filter(front_port_id=self.instance.pk).delete()
+
+ # Create new rear port mappings
+ mappings = []
+ if self.port_mapping_model is PortTemplateMapping:
+ params = {
+ 'device_type_id': self.instance.device_type_id,
+ 'module_type_id': self.instance.module_type_id,
+ }
+ else:
+ params = {
+ 'device_id': self.instance.device_id,
+ }
+ for i, rp_position in enumerate(self.cleaned_data['rear_ports'], start=1):
+ rear_port_id, rear_port_position = rp_position.split(':')
+ mappings.append(
+ self.port_mapping_model(**{
+ **params,
+ 'front_port_id': self.instance.pk,
+ 'front_port_position': i,
+ 'rear_port_id': rear_port_id,
+ 'rear_port_position': rear_port_position,
+ })
+ )
+ self.port_mapping_model.objects.bulk_create(mappings)
+ # Send post_save signals
+ for mapping in mappings:
+ post_save.send(
+ sender=PortMapping,
+ instance=mapping,
+ created=True,
+ raw=False,
+ using=connection,
+ update_fields=None
+ )
diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py
index 32ea2d263..69bd7d5cd 100644
--- a/netbox/dcim/forms/model_forms.py
+++ b/netbox/dcim/forms/model_forms.py
@@ -6,17 +6,18 @@ from timezone_field import TimeZoneFormField
from dcim.choices import *
from dcim.constants import *
+from dcim.forms.mixins import FrontPortFormMixin
from dcim.models import *
from extras.models import ConfigTemplate
from ipam.choices import VLANQinQRoleChoices
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
-from netbox.forms import NetBoxModelForm
-from netbox.forms.mixins import ChangelogMessageMixin
+from netbox.forms import NestedGroupModelForm, NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
+from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
from tenancy.forms import TenancyForm
from users.models import User
from utilities.forms import add_blank_choice, get_field_value
from utilities.forms.fields import (
- CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
+ DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
)
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
@@ -75,14 +76,12 @@ __all__ = (
)
-class RegionForm(NetBoxModelForm):
+class RegionForm(NestedGroupModelForm):
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=Region.objects.all(),
required=False
)
- slug = SlugField()
- comments = CommentField()
fieldsets = (
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
@@ -91,18 +90,16 @@ class RegionForm(NetBoxModelForm):
class Meta:
model = Region
fields = (
- 'parent', 'name', 'slug', 'description', 'tags', 'comments',
+ 'parent', 'name', 'slug', 'description', 'owner', 'tags', 'comments',
)
-class SiteGroupForm(NetBoxModelForm):
+class SiteGroupForm(NestedGroupModelForm):
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=SiteGroup.objects.all(),
required=False
)
- slug = SlugField()
- comments = CommentField()
fieldsets = (
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
@@ -111,11 +108,11 @@ class SiteGroupForm(NetBoxModelForm):
class Meta:
model = SiteGroup
fields = (
- 'parent', 'name', 'slug', 'description', 'comments', 'tags',
+ 'parent', 'name', 'slug', 'description', 'owner', 'comments', 'tags',
)
-class SiteForm(TenancyForm, NetBoxModelForm):
+class SiteForm(TenancyForm, PrimaryModelForm):
region = DynamicModelChoiceField(
label=_('Region'),
queryset=Region.objects.all(),
@@ -139,7 +136,6 @@ class SiteForm(TenancyForm, NetBoxModelForm):
choices=add_blank_choice(TimeZoneFormField().choices),
required=False
)
- comments = CommentField()
fieldsets = (
FieldSet(
@@ -154,7 +150,7 @@ class SiteForm(TenancyForm, NetBoxModelForm):
model = Site
fields = (
'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
- 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
+ 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'owner', 'comments', 'tags',
)
widgets = {
'physical_address': forms.Textarea(
@@ -170,7 +166,7 @@ class SiteForm(TenancyForm, NetBoxModelForm):
}
-class LocationForm(TenancyForm, NetBoxModelForm):
+class LocationForm(TenancyForm, NestedGroupModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -184,8 +180,6 @@ class LocationForm(TenancyForm, NetBoxModelForm):
'site_id': '$site'
}
)
- slug = SlugField()
- comments = CommentField()
fieldsets = (
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
@@ -195,14 +189,12 @@ class LocationForm(TenancyForm, NetBoxModelForm):
class Meta:
model = Location
fields = (
- 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant',
- 'facility', 'tags', 'comments',
+ 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'facility', 'owner',
+ 'comments', 'tags',
)
-class RackRoleForm(NetBoxModelForm):
- slug = SlugField()
-
+class RackRoleForm(OrganizationalModelForm):
fieldsets = (
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Rack Role')),
)
@@ -210,17 +202,16 @@ class RackRoleForm(NetBoxModelForm):
class Meta:
model = RackRole
fields = [
- 'name', 'slug', 'color', 'description', 'tags',
+ 'name', 'slug', 'color', 'description', 'owner', 'comments', 'tags',
]
-class RackTypeForm(NetBoxModelForm):
+class RackTypeForm(PrimaryModelForm):
manufacturer = DynamicModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
quick_add=True
)
- comments = CommentField()
slug = SlugField(
label=_('Slug'),
slug_source='model'
@@ -242,11 +233,11 @@ class RackTypeForm(NetBoxModelForm):
fields = [
'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
- 'weight_unit', 'description', 'comments', 'tags',
+ 'weight_unit', 'description', 'owner', 'comments', 'tags',
]
-class RackForm(TenancyForm, NetBoxModelForm):
+class RackForm(TenancyForm, PrimaryModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -269,9 +260,9 @@ class RackForm(TenancyForm, NetBoxModelForm):
label=_('Rack Type'),
queryset=RackType.objects.all(),
required=False,
- help_text=_("Select a pre-defined rack type, or set physical characteristics below.")
+ selector=True,
+ help_text=_("Select a pre-defined rack type, or set physical characteristics below."),
)
- comments = CommentField()
fieldsets = (
FieldSet(
@@ -288,7 +279,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
- 'weight_unit', 'description', 'comments', 'tags',
+ 'weight_unit', 'description', 'owner', 'comments', 'tags',
]
def __init__(self, *args, **kwargs):
@@ -318,7 +309,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
)
-class RackReservationForm(TenancyForm, NetBoxModelForm):
+class RackReservationForm(TenancyForm, PrimaryModelForm):
rack = DynamicModelChoiceField(
label=_('Rack'),
queryset=Rack.objects.all(),
@@ -333,7 +324,6 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
label=_('User'),
queryset=User.objects.order_by('username')
)
- comments = CommentField()
fieldsets = (
FieldSet('rack', 'units', 'status', 'user', 'description', 'tags', name=_('Reservation')),
@@ -343,13 +333,11 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
class Meta:
model = RackReservation
fields = [
- 'rack', 'units', 'status', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
+ 'rack', 'units', 'status', 'user', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags',
]
-class ManufacturerForm(NetBoxModelForm):
- slug = SlugField()
-
+class ManufacturerForm(OrganizationalModelForm):
fieldsets = (
FieldSet('name', 'slug', 'description', 'tags', name=_('Manufacturer')),
)
@@ -357,11 +345,11 @@ class ManufacturerForm(NetBoxModelForm):
class Meta:
model = Manufacturer
fields = [
- 'name', 'slug', 'description', 'tags',
+ 'name', 'slug', 'description', 'owner', 'comments', 'tags',
]
-class DeviceTypeForm(NetBoxModelForm):
+class DeviceTypeForm(PrimaryModelForm):
manufacturer = DynamicModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
@@ -380,7 +368,6 @@ class DeviceTypeForm(NetBoxModelForm):
label=_('Slug'),
slug_source='model'
)
- comments = CommentField()
fieldsets = (
FieldSet('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags', name=_('Device Type')),
@@ -396,7 +383,7 @@ class DeviceTypeForm(NetBoxModelForm):
fields = [
'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization',
'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
- 'description', 'comments', 'tags',
+ 'description', 'owner', 'comments', 'tags',
]
widgets = {
'front_image': ClearableFileInput(attrs={
@@ -408,13 +395,12 @@ class DeviceTypeForm(NetBoxModelForm):
}
-class ModuleTypeProfileForm(NetBoxModelForm):
+class ModuleTypeProfileForm(PrimaryModelForm):
schema = JSONField(
label=_('Schema'),
required=False,
help_text=_("Enter a valid JSON schema to define supported attributes.")
)
- comments = CommentField()
fieldsets = (
FieldSet('name', 'description', 'schema', 'tags', name=_('Profile')),
@@ -423,11 +409,11 @@ class ModuleTypeProfileForm(NetBoxModelForm):
class Meta:
model = ModuleTypeProfile
fields = [
- 'name', 'description', 'schema', 'comments', 'tags',
+ 'name', 'description', 'schema', 'owner', 'comments', 'tags',
]
-class ModuleTypeForm(NetBoxModelForm):
+class ModuleTypeForm(PrimaryModelForm):
profile = forms.ModelChoiceField(
queryset=ModuleTypeProfile.objects.all(),
label=_('Profile'),
@@ -438,7 +424,6 @@ class ModuleTypeForm(NetBoxModelForm):
label=_('Manufacturer'),
queryset=Manufacturer.objects.all()
)
- comments = CommentField()
@property
def fieldsets(self):
@@ -452,7 +437,7 @@ class ModuleTypeForm(NetBoxModelForm):
model = ModuleType
fields = [
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
- 'comments', 'tags',
+ 'owner', 'comments', 'tags',
]
def __init__(self, *args, **kwargs):
@@ -507,19 +492,17 @@ class ModuleTypeForm(NetBoxModelForm):
return super()._post_clean()
-class DeviceRoleForm(NetBoxModelForm):
+class DeviceRoleForm(NestedGroupModelForm):
config_template = DynamicModelChoiceField(
label=_('Config template'),
queryset=ConfigTemplate.objects.all(),
required=False
)
- slug = SlugField()
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=DeviceRole.objects.all(),
required=False,
)
- comments = CommentField()
fieldsets = (
FieldSet(
@@ -531,11 +514,11 @@ class DeviceRoleForm(NetBoxModelForm):
class Meta:
model = DeviceRole
fields = [
- 'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'comments', 'tags',
+ 'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'owner', 'comments', 'tags',
]
-class PlatformForm(NetBoxModelForm):
+class PlatformForm(NestedGroupModelForm):
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=Platform.objects.all(),
@@ -556,7 +539,6 @@ class PlatformForm(NetBoxModelForm):
label=_('Slug'),
max_length=64
)
- comments = CommentField()
fieldsets = (
FieldSet(
@@ -567,11 +549,11 @@ class PlatformForm(NetBoxModelForm):
class Meta:
model = Platform
fields = [
- 'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'comments', 'tags',
+ 'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'owner', 'comments', 'tags',
]
-class DeviceForm(TenancyForm, NetBoxModelForm):
+class DeviceForm(TenancyForm, PrimaryModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -641,7 +623,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
'site_id': ['$site', 'null']
},
)
- comments = CommentField()
local_context_data = JSONField(
required=False,
label=''
@@ -677,7 +658,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
'name', 'role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster',
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
- 'comments', 'tags', 'local_context_data',
+ 'owner', 'comments', 'tags', 'local_context_data',
]
def __init__(self, *args, **kwargs):
@@ -742,7 +723,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
self.fields['position'].widget.choices = [(position, f'U{position}')]
-class ModuleForm(ModuleCommonForm, NetBoxModelForm):
+class ModuleForm(ModuleCommonForm, PrimaryModelForm):
device = DynamicModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -755,7 +736,10 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
queryset=ModuleBay.objects.all(),
query_params={
'device_id': '$device'
- }
+ },
+ context={
+ 'disabled': 'installed_module',
+ },
)
module_type = DynamicModelChoiceField(
label=_('Module type'),
@@ -765,7 +749,6 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
},
selector=True
)
- comments = CommentField()
replicate_components = forms.BooleanField(
label=_('Replicate components'),
required=False,
@@ -788,7 +771,7 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
model = Module
fields = [
'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'tags', 'replicate_components',
- 'adopt_components', 'description', 'comments',
+ 'adopt_components', 'description', 'owner', 'comments',
]
def __init__(self, *args, **kwargs):
@@ -809,7 +792,7 @@ def get_termination_type_choices():
])
-class CableForm(TenancyForm, NetBoxModelForm):
+class CableForm(TenancyForm, PrimaryModelForm):
a_terminations_type = forms.ChoiceField(
choices=get_termination_type_choices,
required=False,
@@ -822,17 +805,16 @@ class CableForm(TenancyForm, NetBoxModelForm):
widget=HTMXSelect(),
label=_('Type')
)
- comments = CommentField()
class Meta:
model = Cable
fields = [
- 'a_terminations_type', 'b_terminations_type', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
- 'length', 'length_unit', 'description', 'comments', 'tags',
+ 'a_terminations_type', 'b_terminations_type', 'type', 'status', 'profile', 'tenant_group', 'tenant',
+ 'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags',
]
-class PowerPanelForm(NetBoxModelForm):
+class PowerPanelForm(PrimaryModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -846,7 +828,6 @@ class PowerPanelForm(NetBoxModelForm):
'site_id': '$site'
}
)
- comments = CommentField()
fieldsets = (
FieldSet('site', 'location', 'name', 'description', 'tags', name=_('Power Panel')),
@@ -855,11 +836,11 @@ class PowerPanelForm(NetBoxModelForm):
class Meta:
model = PowerPanel
fields = [
- 'site', 'location', 'name', 'description', 'comments', 'tags',
+ 'site', 'location', 'name', 'description', 'owner', 'comments', 'tags',
]
-class PowerFeedForm(TenancyForm, NetBoxModelForm):
+class PowerFeedForm(TenancyForm, PrimaryModelForm):
power_panel = DynamicModelChoiceField(
label=_('Power panel'),
queryset=PowerPanel.objects.all(),
@@ -872,7 +853,6 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
required=False,
selector=True
)
- comments = CommentField()
fieldsets = (
FieldSet(
@@ -887,7 +867,7 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
model = PowerFeed
fields = [
'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
- 'max_utilization', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
+ 'max_utilization', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags'
]
@@ -895,18 +875,17 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
# Virtual chassis
#
-class VirtualChassisForm(NetBoxModelForm):
+class VirtualChassisForm(PrimaryModelForm):
master = forms.ModelChoiceField(
label=_('Master'),
queryset=Device.objects.all(),
required=False,
)
- comments = CommentField()
class Meta:
model = VirtualChassis
fields = [
- 'name', 'domain', 'master', 'description', 'comments', 'tags',
+ 'name', 'domain', 'master', 'description', 'owner', 'comments', 'tags',
]
widgets = {
'master': SelectWithPK(),
@@ -1092,14 +1071,14 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
FieldSet('device_type', name=_('Device Type')),
FieldSet('module_type', name=_('Module Type')),
),
- 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
+ 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description',
),
)
class Meta:
model = PowerOutletTemplate
fields = [
- 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
+ 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description',
]
@@ -1134,34 +1113,66 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
]
-class FrontPortTemplateForm(ModularComponentTemplateForm):
- rear_port = DynamicModelChoiceField(
- label=_('Rear port'),
- queryset=RearPortTemplate.objects.all(),
- required=False,
- query_params={
- 'device_type_id': '$device_type',
- 'module_type_id': '$module_type',
- }
- )
-
+class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
fieldsets = (
FieldSet(
TabbedGroups(
FieldSet('device_type', name=_('Device Type')),
FieldSet('module_type', name=_('Module Type')),
),
- 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
+ 'name', 'label', 'type', 'positions', 'rear_ports', 'description',
),
)
+ # Override FrontPortFormMixin attrs
+ port_mapping_model = PortTemplateMapping
+ parent_field = 'device_type'
+
class Meta:
model = FrontPortTemplate
fields = [
- 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
- 'description',
+ 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
]
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if device_type_id := self.data.get('device_type') or self.initial.get('device_type'):
+ device_type = DeviceType.objects.get(pk=device_type_id)
+ else:
+ return
+
+ # Populate rear port choices
+ self.fields['rear_ports'].choices = self._get_rear_port_choices(device_type, self.instance)
+
+ # Set initial rear port mappings
+ if self.instance.pk:
+ self.initial['rear_ports'] = [
+ f'{mapping.rear_port_id}:{mapping.rear_port_position}'
+ for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk)
+ ]
+
+ def _get_rear_port_choices(self, device_type, front_port):
+ """
+ Return a list of choices representing each available rear port & position pair on the device type, excluding
+ those assigned to the specified instance.
+ """
+ occupied_rear_port_positions = [
+ f'{mapping.rear_port_id}:{mapping.rear_port_position}'
+ for mapping in device_type.port_mappings.exclude(front_port=front_port.pk)
+ ]
+
+ choices = []
+ for rear_port in RearPortTemplate.objects.filter(device_type=device_type):
+ for i in range(1, rear_port.positions + 1):
+ pair_id = f'{rear_port.pk}:{i}'
+ if pair_id not in occupied_rear_port_positions:
+ pair_label = f'{rear_port.name}:{i}'
+ choices.append(
+ (pair_id, pair_label)
+ )
+ return choices
+
class RearPortTemplateForm(ModularComponentTemplateForm):
fieldsets = (
@@ -1360,7 +1371,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
# Device components
#
-class DeviceComponentForm(NetBoxModelForm):
+class DeviceComponentForm(OwnerMixin, NetBoxModelForm):
device = DynamicModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1396,7 +1407,7 @@ class ConsolePortForm(ModularDeviceComponentForm):
class Meta:
model = ConsolePort
fields = [
- 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags',
]
@@ -1410,7 +1421,7 @@ class ConsoleServerPortForm(ModularDeviceComponentForm):
class Meta:
model = ConsoleServerPort
fields = [
- 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags',
]
@@ -1426,7 +1437,7 @@ class PowerPortForm(ModularDeviceComponentForm):
model = PowerPort
fields = [
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
- 'description', 'tags',
+ 'description', 'owner', 'tags',
]
@@ -1443,7 +1454,7 @@ class PowerOutletForm(ModularDeviceComponentForm):
fieldsets = (
FieldSet(
'device', 'module', 'name', 'label', 'type', 'status', 'color', 'power_port', 'feed_leg', 'mark_connected',
- 'description', 'tags',
+ 'description', 'owner', 'tags',
),
)
@@ -1587,7 +1598,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
'lag', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
- 'tags',
+ 'owner', 'tags',
]
widgets = {
'speed': NumberWithOptions(
@@ -1600,17 +1611,10 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
}
-class FrontPortForm(ModularDeviceComponentForm):
- rear_port = DynamicModelChoiceField(
- queryset=RearPort.objects.all(),
- query_params={
- 'device_id': '$device',
- }
- )
-
+class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
fieldsets = (
FieldSet(
- 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
+ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'rear_ports', 'mark_connected',
'description', 'tags',
),
)
@@ -1618,10 +1622,49 @@ class FrontPortForm(ModularDeviceComponentForm):
class Meta:
model = FrontPort
fields = [
- 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
- 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'owner',
+ 'tags',
]
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if device_id := self.data.get('device') or self.initial.get('device'):
+ device = Device.objects.get(pk=device_id)
+ else:
+ return
+
+ # Populate rear port choices
+ self.fields['rear_ports'].choices = self._get_rear_port_choices(device, self.instance)
+
+ # Set initial rear port mappings
+ if self.instance.pk:
+ self.initial['rear_ports'] = [
+ f'{mapping.rear_port_id}:{mapping.rear_port_position}'
+ for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk)
+ ]
+
+ def _get_rear_port_choices(self, device, front_port):
+ """
+ Return a list of choices representing each available rear port & position pair on the device, excluding those
+ assigned to the specified instance.
+ """
+ occupied_rear_port_positions = [
+ f'{mapping.rear_port_id}:{mapping.rear_port_position}'
+ for mapping in device.port_mappings.exclude(front_port=front_port.pk)
+ ]
+
+ choices = []
+ for rear_port in RearPort.objects.filter(device=device):
+ for i in range(1, rear_port.positions + 1):
+ pair_id = f'{rear_port.pk}:{i}'
+ if pair_id not in occupied_rear_port_positions:
+ pair_label = f'{rear_port.name}:{i}'
+ choices.append(
+ (pair_id, pair_label)
+ )
+ return choices
+
class RearPortForm(ModularDeviceComponentForm):
fieldsets = (
@@ -1633,7 +1676,8 @@ class RearPortForm(ModularDeviceComponentForm):
class Meta:
model = RearPort
fields = [
- 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'owner',
+ 'tags',
]
@@ -1645,7 +1689,7 @@ class ModuleBayForm(ModularDeviceComponentForm):
class Meta:
model = ModuleBay
fields = [
- 'device', 'module', 'name', 'label', 'position', 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'position', 'description', 'owner', 'tags',
]
@@ -1657,7 +1701,7 @@ class DeviceBayForm(DeviceComponentForm):
class Meta:
model = DeviceBay
fields = [
- 'device', 'name', 'label', 'description', 'tags',
+ 'device', 'name', 'label', 'description', 'owner', 'tags',
]
@@ -1782,7 +1826,7 @@ class InventoryItemForm(DeviceComponentForm):
model = InventoryItem
fields = [
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
- 'status', 'description', 'tags',
+ 'status', 'description', 'owner', 'tags',
]
def __init__(self, *args, **kwargs):
@@ -1828,12 +1872,7 @@ class InventoryItemForm(DeviceComponentForm):
self.instance.component = None
-# Device component roles
-#
-
-class InventoryItemRoleForm(NetBoxModelForm):
- slug = SlugField()
-
+class InventoryItemRoleForm(OrganizationalModelForm):
fieldsets = (
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Inventory Item Role')),
)
@@ -1841,11 +1880,11 @@ class InventoryItemRoleForm(NetBoxModelForm):
class Meta:
model = InventoryItemRole
fields = [
- 'name', 'slug', 'color', 'description', 'tags',
+ 'name', 'slug', 'color', 'description', 'owner', 'comments', 'tags',
]
-class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
+class VirtualDeviceContextForm(TenancyForm, PrimaryModelForm):
device = DynamicModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
@@ -1881,7 +1920,7 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
class Meta:
model = VirtualDeviceContext
fields = [
- 'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant',
+ 'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'owner',
'comments', 'tags'
]
@@ -1890,7 +1929,7 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
# Addressing
#
-class MACAddressForm(NetBoxModelForm):
+class MACAddressForm(PrimaryModelForm):
mac_address = forms.CharField(
required=True,
label=_('MAC address')
@@ -1929,7 +1968,7 @@ class MACAddressForm(NetBoxModelForm):
class Meta:
model = MACAddress
fields = [
- 'mac_address', 'interface', 'vminterface', 'description', 'tags',
+ 'mac_address', 'interface', 'vminterface', 'description', 'owner', 'tags',
]
def __init__(self, *args, **kwargs):
diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py
index 5c9599eeb..8e0818326 100644
--- a/netbox/dcim/forms/object_create.py
+++ b/netbox/dcim/forms/object_create.py
@@ -109,85 +109,30 @@ class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemp
class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
- rear_port = forms.MultipleChoiceField(
- choices=[],
- label=_('Rear ports'),
- help_text=_('Select one rear port assignment for each front port being created.'),
- widget=forms.SelectMultiple(attrs={'size': 6})
- )
- # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
+ # Override fieldsets from FrontPortTemplateForm
fieldsets = (
FieldSet(
TabbedGroups(
FieldSet('device_type', name=_('Device Type')),
FieldSet('module_type', name=_('Module Type')),
),
- 'name', 'label', 'type', 'color', 'rear_port', 'description',
+ 'name', 'label', 'type', 'color', 'positions', 'rear_ports', 'description',
),
)
- class Meta(model_forms.FrontPortTemplateForm.Meta):
- exclude = ('name', 'label', 'rear_port', 'rear_port_position')
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # TODO: This needs better validation
- if 'device_type' in self.initial or self.data.get('device_type'):
- parent = DeviceType.objects.get(
- pk=self.initial.get('device_type') or self.data.get('device_type')
- )
- elif 'module_type' in self.initial or self.data.get('module_type'):
- parent = ModuleType.objects.get(
- pk=self.initial.get('module_type') or self.data.get('module_type')
- )
- else:
- return
-
- # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
- occupied_port_positions = [
- (front_port.rear_port_id, front_port.rear_port_position)
- for front_port in parent.frontporttemplates.all()
- ]
-
- # Populate rear port choices
- choices = []
- rear_ports = parent.rearporttemplates.all()
- for rear_port in rear_ports:
- for i in range(1, rear_port.positions + 1):
- if (rear_port.pk, i) not in occupied_port_positions:
- choices.append(
- ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
- )
- self.fields['rear_port'].choices = choices
-
- def clean(self):
- super().clean()
-
- # Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate
- # positions
- frontport_count = len(self.cleaned_data['name'])
- rearport_count = len(self.cleaned_data['rear_port'])
- if frontport_count != rearport_count:
- raise forms.ValidationError({
- 'rear_port': _(
- "The number of front port templates to be created ({frontport_count}) must match the selected "
- "number of rear port positions ({rearport_count})."
- ).format(
- frontport_count=frontport_count,
- rearport_count=rearport_count
- )
- })
+ class Meta:
+ model = FrontPortTemplate
+ fields = (
+ 'device_type', 'module_type', 'type', 'color', 'positions', 'description',
+ )
def get_iterative_data(self, iteration):
-
- # Assign rear port and position from selected set
- rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
+ positions = self.cleaned_data['positions']
+ offset = positions * iteration
return {
- 'rear_port': int(rear_port),
- 'rear_port_position': int(position),
+ 'rear_ports': self.cleaned_data['rear_ports'][offset:offset + positions]
}
@@ -269,74 +214,26 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
}
)
)
- rear_port = forms.MultipleChoiceField(
- choices=[],
- label=_('Rear ports'),
- help_text=_('Select one rear port assignment for each front port being created.'),
- widget=forms.SelectMultiple(attrs={'size': 6})
- )
# Override fieldsets from FrontPortForm to omit rear_port_position
fieldsets = (
FieldSet(
- 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'rear_ports', 'mark_connected',
+ 'description', 'tags',
),
)
- class Meta(model_forms.FrontPortForm.Meta):
- exclude = ('name', 'label', 'rear_port', 'rear_port_position')
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- if device_id := self.data.get('device') or self.initial.get('device'):
- device = Device.objects.get(pk=device_id)
- else:
- return
-
- # Determine which rear port positions are occupied. These will be excluded from the list of available
- # mappings.
- occupied_port_positions = [
- (front_port.rear_port_id, front_port.rear_port_position)
- for front_port in device.frontports.all()
+ class Meta:
+ model = FrontPort
+ fields = [
+ 'device', 'module', 'type', 'color', 'positions', 'mark_connected', 'description', 'owner', 'tags',
]
- # Populate rear port choices
- choices = []
- rear_ports = RearPort.objects.filter(device=device)
- for rear_port in rear_ports:
- for i in range(1, rear_port.positions + 1):
- if (rear_port.pk, i) not in occupied_port_positions:
- choices.append(
- ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
- )
- self.fields['rear_port'].choices = choices
-
- def clean(self):
- super().clean()
-
- # Check that the number of FrontPorts to be created matches the selected number of RearPort positions
- frontport_count = len(self.cleaned_data['name'])
- rearport_count = len(self.cleaned_data['rear_port'])
- if frontport_count != rearport_count:
- raise forms.ValidationError({
- 'rear_port': _(
- "The number of front ports to be created ({frontport_count}) must match the selected number of "
- "rear port positions ({rearport_count})."
- ).format(
- frontport_count=frontport_count,
- rearport_count=rearport_count
- )
- })
-
def get_iterative_data(self, iteration):
-
- # Assign rear port and position from selected set
- rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
-
+ positions = self.cleaned_data['positions']
+ offset = positions * iteration
return {
- 'rear_port': int(rear_port),
- 'rear_port_position': int(position),
+ 'rear_ports': self.cleaned_data['rear_ports'][offset:offset + positions]
}
@@ -434,8 +331,8 @@ class VirtualChassisCreateForm(NetBoxModelForm):
class Meta:
model = VirtualChassis
fields = [
- 'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position',
- 'tags',
+ 'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'owner', 'members',
+ 'initial_position', 'tags',
]
def clean(self):
@@ -453,6 +350,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
if instance.pk and self.cleaned_data['members']:
initial_position = self.cleaned_data.get('initial_position', 1)
for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
+ member.snapshot()
member.virtual_chassis = instance
member.vc_position = i
member.save()
diff --git a/netbox/dcim/forms/object_import.py b/netbox/dcim/forms/object_import.py
index 3f2cc3ef6..3b6a6e648 100644
--- a/netbox/dcim/forms/object_import.py
+++ b/netbox/dcim/forms/object_import.py
@@ -13,6 +13,7 @@ __all__ = (
'InterfaceTemplateImportForm',
'InventoryItemTemplateImportForm',
'ModuleBayTemplateImportForm',
+ 'PortTemplateMappingImportForm',
'PowerOutletTemplateImportForm',
'PowerPortTemplateImportForm',
'RearPortTemplateImportForm',
@@ -113,31 +114,11 @@ class FrontPortTemplateImportForm(forms.ModelForm):
label=_('Type'),
choices=PortTypeChoices.CHOICES
)
- rear_port = forms.ModelChoiceField(
- label=_('Rear port'),
- queryset=RearPortTemplate.objects.all(),
- to_field_name='name'
- )
-
- def clean_device_type(self):
- if device_type := self.cleaned_data['device_type']:
- rear_port = self.fields['rear_port']
- rear_port.queryset = rear_port.queryset.filter(device_type=device_type)
-
- return device_type
-
- def clean_module_type(self):
- if module_type := self.cleaned_data['module_type']:
- rear_port = self.fields['rear_port']
- rear_port.queryset = rear_port.queryset.filter(module_type=module_type)
-
- return module_type
class Meta:
model = FrontPortTemplate
fields = [
- 'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label',
- 'description',
+ 'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description',
]
@@ -154,6 +135,25 @@ class RearPortTemplateImportForm(forms.ModelForm):
]
+class PortTemplateMappingImportForm(forms.ModelForm):
+ front_port = forms.ModelChoiceField(
+ label=_('Front port'),
+ queryset=FrontPortTemplate.objects.all(),
+ to_field_name='name',
+ )
+ rear_port = forms.ModelChoiceField(
+ label=_('Rear port'),
+ queryset=RearPortTemplate.objects.all(),
+ to_field_name='name',
+ )
+
+ class Meta:
+ model = PortTemplateMapping
+ fields = [
+ 'front_port', 'front_port_position', 'rear_port', 'rear_port_position',
+ ]
+
+
class ModuleBayTemplateImportForm(forms.ModelForm):
class Meta:
diff --git a/netbox/dcim/graphql/enums.py b/netbox/dcim/graphql/enums.py
index 5f888cfb5..437094d72 100644
--- a/netbox/dcim/graphql/enums.py
+++ b/netbox/dcim/graphql/enums.py
@@ -12,6 +12,7 @@ __all__ = (
'DeviceFaceEnum',
'DeviceStatusEnum',
'InterfaceDuplexEnum',
+ 'InterfaceKindEnum',
'InterfaceModeEnum',
'InterfacePoEModeEnum',
'InterfacePoETypeEnum',
@@ -27,11 +28,13 @@ __all__ = (
'PowerFeedSupplyEnum',
'PowerFeedTypeEnum',
'PowerOutletFeedLegEnum',
+ 'PowerOutletStatusEnum',
'PowerOutletTypeEnum',
'PowerPortTypeEnum',
'RackAirflowEnum',
'RackDimensionUnitEnum',
'RackFormFactorEnum',
+ 'RackReservationStatusEnum',
'RackStatusEnum',
'RackWidthEnum',
'SiteStatusEnum',
@@ -48,6 +51,7 @@ DeviceAirflowEnum = strawberry.enum(DeviceAirflowChoices.as_enum(prefix='airflow
DeviceFaceEnum = strawberry.enum(DeviceFaceChoices.as_enum(prefix='face'))
DeviceStatusEnum = strawberry.enum(DeviceStatusChoices.as_enum(prefix='status'))
InterfaceDuplexEnum = strawberry.enum(InterfaceDuplexChoices.as_enum(prefix='duplex'))
+InterfaceKindEnum = strawberry.enum(InterfaceKindChoices.as_enum(prefix='kind'))
InterfaceModeEnum = strawberry.enum(InterfaceModeChoices.as_enum(prefix='mode'))
InterfacePoEModeEnum = strawberry.enum(InterfacePoEModeChoices.as_enum(prefix='mode'))
InterfacePoETypeEnum = strawberry.enum(InterfacePoETypeChoices.as_enum())
@@ -63,11 +67,13 @@ PowerFeedStatusEnum = strawberry.enum(PowerFeedStatusChoices.as_enum(prefix='sta
PowerFeedSupplyEnum = strawberry.enum(PowerFeedSupplyChoices.as_enum(prefix='supply'))
PowerFeedTypeEnum = strawberry.enum(PowerFeedTypeChoices.as_enum(prefix='type'))
PowerOutletFeedLegEnum = strawberry.enum(PowerOutletFeedLegChoices.as_enum(prefix='feed_leg'))
+PowerOutletStatusEnum = strawberry.enum(PowerOutletStatusChoices.as_enum(prefix='status'))
PowerOutletTypeEnum = strawberry.enum(PowerOutletTypeChoices.as_enum(prefix='type'))
PowerPortTypeEnum = strawberry.enum(PowerPortTypeChoices.as_enum(prefix='type'))
RackAirflowEnum = strawberry.enum(RackAirflowChoices.as_enum())
RackDimensionUnitEnum = strawberry.enum(RackDimensionUnitChoices.as_enum(prefix='unit'))
RackFormFactorEnum = strawberry.enum(RackFormFactorChoices.as_enum(prefix='type'))
+RackReservationStatusEnum = strawberry.enum(RackReservationStatusChoices.as_enum(prefix='status'))
RackStatusEnum = strawberry.enum(RackStatusChoices.as_enum(prefix='status'))
RackWidthEnum = strawberry.enum(RackWidthChoices.as_enum(prefix='width'))
SiteStatusEnum = strawberry.enum(SiteStatusChoices.as_enum(prefix='status'))
diff --git a/netbox/dcim/graphql/filter_mixins.py b/netbox/dcim/graphql/filter_mixins.py
index 25379ad7f..c02c89948 100644
--- a/netbox/dcim/graphql/filter_mixins.py
+++ b/netbox/dcim/graphql/filter_mixins.py
@@ -4,11 +4,9 @@ from typing import Annotated, TYPE_CHECKING
import strawberry
import strawberry_django
from strawberry import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
-from core.graphql.filter_mixins import BaseFilterMixin, ChangeLogFilterMixin
from core.graphql.filters import ContentTypeFilter
-from netbox.graphql.filter_mixins import NetBoxModelFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin
from .enums import *
if TYPE_CHECKING:
@@ -22,16 +20,16 @@ __all__ = (
'ComponentModelFilterMixin',
'ComponentTemplateFilterMixin',
'InterfaceBaseFilterMixin',
- 'ModularComponentModelFilterMixin',
+ 'ModularComponentFilterMixin',
'ModularComponentTemplateFilterMixin',
- 'RackBaseFilterMixin',
+ 'RackFilterMixin',
'RenderConfigFilterMixin',
'ScopedFilterMixin',
)
@dataclass
-class ScopedFilterMixin(BaseFilterMixin):
+class ScopedFilterMixin:
scope_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -39,7 +37,7 @@ class ScopedFilterMixin(BaseFilterMixin):
@dataclass
-class ComponentModelFilterMixin(NetBoxModelFilterMixin):
+class ComponentModelFilterMixin:
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
device_id: ID | None = strawberry_django.filter_field()
name: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -48,7 +46,7 @@ class ComponentModelFilterMixin(NetBoxModelFilterMixin):
@dataclass
-class ModularComponentModelFilterMixin(ComponentModelFilterMixin):
+class ModularComponentFilterMixin(ComponentModelFilterMixin):
module: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
module_id: ID | None = strawberry_django.filter_field()
inventory_items: Annotated['InventoryItemFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -57,15 +55,17 @@ class ModularComponentModelFilterMixin(ComponentModelFilterMixin):
@dataclass
-class CabledObjectModelFilterMixin(BaseFilterMixin):
+class CabledObjectModelFilterMixin:
cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
cable_id: ID | None = strawberry_django.filter_field()
- cable_end: CableEndEnum | None = strawberry_django.filter_field()
+ cable_end: (
+ BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+ ) = strawberry_django.filter_field()
mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field()
@dataclass
-class ComponentTemplateFilterMixin(ChangeLogFilterMixin):
+class ComponentTemplateFilterMixin:
device_type: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -83,7 +83,7 @@ class ModularComponentTemplateFilterMixin(ComponentTemplateFilterMixin):
@dataclass
-class RenderConfigFilterMixin(BaseFilterMixin):
+class RenderConfigFilterMixin:
config_template: Annotated['ConfigTemplateFilter', strawberry.lazy('extras.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -91,12 +91,14 @@ class RenderConfigFilterMixin(BaseFilterMixin):
@dataclass
-class InterfaceBaseFilterMixin(BaseFilterMixin):
+class InterfaceBaseFilterMixin:
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
mtu: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- mode: InterfaceModeEnum | None = strawberry_django.filter_field()
+ mode: (
+ BaseFilterLookup[Annotated['InterfaceModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+ ) = strawberry_django.filter_field()
bridge: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -110,8 +112,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin):
qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- vlan_translation_policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None \
- = strawberry_django.filter_field()
+ vlan_translation_policy: (
+ Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None
+ ) = strawberry_django.filter_field()
primary_mac_address: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -119,8 +122,10 @@ class InterfaceBaseFilterMixin(BaseFilterMixin):
@dataclass
-class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin):
- width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+class RackFilterMixin:
+ width: BaseFilterLookup[Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
u_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -137,7 +142,7 @@ class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin):
outer_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ outer_unit: BaseFilterLookup[Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
mounting_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py
index a8a6c2a5e..9e5915146 100644
--- a/netbox/dcim/graphql/filters.py
+++ b/netbox/dcim/graphql/filters.py
@@ -1,29 +1,30 @@
from typing import Annotated, TYPE_CHECKING
+from django.db.models import Q
import strawberry
import strawberry_django
from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, FilterLookup
-from core.graphql.filter_mixins import ChangeLogFilterMixin
from dcim import models
-from extras.graphql.filter_mixins import ConfigContextFilterMixin
-from netbox.graphql.filter_mixins import (
- PrimaryModelFilterMixin,
- OrganizationalModelFilterMixin,
- NestedGroupModelFilterMixin,
- ImageAttachmentFilterMixin,
- WeightFilterMixin,
+from dcim.constants import *
+from dcim.graphql.enums import InterfaceKindEnum
+from dcim.graphql.filter_mixins import (
+ ComponentModelFilterMixin, ComponentTemplateFilterMixin, ModularComponentFilterMixin,
+ ModularComponentTemplateFilterMixin, RackFilterMixin,
)
-from tenancy.graphql.filter_mixins import TenancyFilterMixin, ContactFilterMixin
+from extras.graphql.filter_mixins import ConfigContextFilterMixin
+from netbox.graphql.filter_mixins import ImageAttachmentFilterMixin, WeightFilterMixin
+from netbox.graphql.filters import (
+ BaseModelFilter, ChangeLoggedModelFilter, NestedGroupModelFilter, OrganizationalModelFilter, PrimaryModelFilter,
+ NetBoxModelFilter,
+)
+from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin
+from virtualization.models import VMInterface
+
from .filter_mixins import (
CabledObjectModelFilterMixin,
- ComponentModelFilterMixin,
- ComponentTemplateFilterMixin,
InterfaceBaseFilterMixin,
- ModularComponentModelFilterMixin,
- ModularComponentTemplateFilterMixin,
- RackBaseFilterMixin,
RenderConfigFilterMixin,
)
@@ -70,6 +71,8 @@ __all__ = (
'ModuleTypeFilter',
'ModuleTypeProfileFilter',
'PlatformFilter',
+ 'PortMappingFilter',
+ 'PortTemplateMappingFilter',
'PowerFeedFilter',
'PowerOutletFilter',
'PowerOutletTemplateFilter',
@@ -91,15 +94,21 @@ __all__ = (
@strawberry_django.filter_type(models.Cable, lookups=True)
-class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin):
- type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+class CableFilter(TenancyFilterMixin, PrimaryModelFilter):
+ type: BaseFilterLookup[Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
label: FilterLookup[str] | None = strawberry_django.filter_field()
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ length_unit: BaseFilterLookup[Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
terminations: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -108,10 +117,10 @@ class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin):
@strawberry_django.filter_type(models.CableTermination, lookups=True)
-class CableTerminationFilter(ChangeLogFilterMixin):
+class CableTerminationFilter(ChangeLoggedModelFilter):
cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
cable_id: ID | None = strawberry_django.filter_field()
- cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ cable_end: BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
termination_type: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -121,35 +130,35 @@ class CableTerminationFilter(ChangeLogFilterMixin):
@strawberry_django.filter_type(models.ConsolePort, lookups=True)
-class ConsolePortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class ConsolePortFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True)
-class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.ConsoleServerPort, lookups=True)
-class ConsoleServerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class ConsoleServerPortFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True)
-class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -161,7 +170,7 @@ class DeviceFilter(
ImageAttachmentFilterMixin,
RenderConfigFilterMixin,
ConfigContextFilterMixin,
- PrimaryModelFilterMixin,
+ PrimaryModelFilter,
):
device_type: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
@@ -190,11 +199,13 @@ class DeviceFilter(
position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ face: BaseFilterLookup[Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: BaseFilterLookup[Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -272,7 +283,7 @@ class DeviceFilter(
@strawberry_django.filter_type(models.DeviceBay, lookups=True)
-class DeviceBayFilter(ComponentModelFilterMixin):
+class DeviceBayFilter(ComponentModelFilterMixin, NetBoxModelFilter):
installed_device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -280,12 +291,12 @@ class DeviceBayFilter(ComponentModelFilterMixin):
@strawberry_django.filter_type(models.DeviceBayTemplate, lookups=True)
-class DeviceBayTemplateFilter(ComponentTemplateFilterMixin):
+class DeviceBayTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedModelFilter):
pass
@strawberry_django.filter_type(models.InventoryItemTemplate, lookups=True)
-class InventoryItemTemplateFilter(ComponentTemplateFilterMixin):
+class InventoryItemTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedModelFilter):
parent: Annotated['InventoryItemTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -305,13 +316,15 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin):
@strawberry_django.filter_type(models.DeviceRole, lookups=True)
-class DeviceRoleFilter(OrganizationalModelFilterMixin, RenderConfigFilterMixin):
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class DeviceRoleFilter(RenderConfigFilterMixin, OrganizationalModelFilter):
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
vm_role: FilterLookup[bool] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.DeviceType, lookups=True)
-class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin):
+class DeviceTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryModelFilter):
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -323,15 +336,18 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
)
default_platform_id: ID | None = strawberry_django.filter_field()
part_number: FilterLookup[str] | None = strawberry_django.filter_field()
+ instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
u_height: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field()
is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field()
- subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ subdevice_role: BaseFilterLookup[Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
front_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = (
@@ -380,58 +396,107 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
device_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
module_bay_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
inventory_item_template_count: FilterLookup[int] | None = strawberry_django.filter_field()
+ device_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.FrontPort, lookups=True)
-class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
- rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+class FrontPortFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- rear_port_id: ID | None = strawberry_django.filter_field()
- rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.FrontPortTemplate, lookups=True)
-class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+
+
+@strawberry_django.filter_type(models.PortMapping, lookups=True)
+class PortMappingFilter(BaseModelFilter):
+ device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
+ front_port: Annotated['FrontPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
+ rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
+ front_port_position: FilterLookup[int] | None = strawberry_django.filter_field()
+ rear_port_position: FilterLookup[int] | None = strawberry_django.filter_field()
+
+
+@strawberry_django.filter_type(models.PortTemplateMapping, lookups=True)
+class PortTemplateMappingFilter(BaseModelFilter):
+ device_type: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
+ module_type: Annotated['ModuleTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
+ front_port: Annotated['FrontPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
rear_port: Annotated['RearPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- rear_port_id: ID | None = strawberry_django.filter_field()
- rear_port_position: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
- strawberry_django.filter_field()
- )
+ front_port_position: FilterLookup[int] | None = strawberry_django.filter_field()
+ rear_port_position: FilterLookup[int] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.MACAddress, lookups=True)
-class MACAddressFilter(PrimaryModelFilterMixin):
+class MACAddressFilter(PrimaryModelFilter):
mac_address: FilterLookup[str] | None = strawberry_django.filter_field()
assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
assigned_object_id: ID | None = strawberry_django.filter_field()
+ @strawberry_django.filter_field()
+ def assigned(self, value: bool, prefix) -> Q:
+ return Q(**{f'{prefix}assigned_object_id__isnull': (not value)})
+
+ @strawberry_django.filter_field()
+ def primary(self, value: bool, prefix) -> Q:
+ interface_mac_ids = models.Interface.objects.filter(primary_mac_address_id__isnull=False).values_list(
+ 'primary_mac_address_id', flat=True
+ )
+ vminterface_mac_ids = VMInterface.objects.filter(primary_mac_address_id__isnull=False).values_list(
+ 'primary_mac_address_id', flat=True
+ )
+ query = Q(**{f'{prefix}pk__in': interface_mac_ids}) | Q(**{f'{prefix}pk__in': vminterface_mac_ids})
+ if value:
+ return Q(query)
+ else:
+ return ~Q(query)
+
@strawberry_django.filter_type(models.Interface, lookups=True)
-class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin):
+class InterfaceFilter(
+ ModularComponentFilterMixin,
+ InterfaceBaseFilterMixin,
+ CabledObjectModelFilterMixin,
+ NetBoxModelFilter
+):
vcdcs: Annotated['VirtualDeviceContextFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
lag: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
lag_id: ID | None = strawberry_django.filter_field()
- type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field()
speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
wwn: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -439,10 +504,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
strawberry_django.filter_field()
)
parent_id: ID | None = strawberry_django.filter_field()
- rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+ rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+ rf_channel: BaseFilterLookup[Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -454,10 +519,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
wireless_link: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = (
@@ -485,10 +550,31 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
strawberry_django.filter_field()
)
+ @strawberry_django.filter_field
+ def connected(self, queryset, value: bool, prefix: str):
+ if value is True:
+ return queryset, Q(**{f"{prefix}_path__is_active": True})
+ else:
+ return queryset, Q(**{f"{prefix}_path__isnull": True}) | Q(**{f"{prefix}_path__is_active": False})
+
+ @strawberry_django.filter_field
+ def kind(
+ self,
+ queryset,
+ value: Annotated['InterfaceKindEnum', strawberry.lazy('dcim.graphql.enums')],
+ prefix: str
+ ):
+ if value == InterfaceKindEnum.KIND_PHYSICAL:
+ return queryset, ~Q(**{f"{prefix}type__in": NONCONNECTABLE_IFACE_TYPES})
+ elif value == InterfaceKindEnum.KIND_VIRTUAL:
+ return queryset, Q(**{f"{prefix}type__in": VIRTUAL_IFACE_TYPES})
+ elif value == InterfaceKindEnum.KIND_WIRELESS:
+ return queryset, Q(**{f"{prefix}type__in": WIRELESS_IFACE_TYPES})
+
@strawberry_django.filter_type(models.InterfaceTemplate, lookups=True)
-class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -497,19 +583,19 @@ class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin):
strawberry_django.filter_field()
)
bridge_id: ID | None = strawberry_django.filter_field()
- poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+ rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.InventoryItem, lookups=True)
-class InventoryItemFilter(ComponentModelFilterMixin):
+class InventoryItemFilter(ComponentModelFilterMixin, NetBoxModelFilter):
parent: Annotated['InventoryItemFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -518,7 +604,7 @@ class InventoryItemFilter(ComponentModelFilterMixin):
strawberry_django.filter_field()
)
component_id: ID | None = strawberry_django.filter_field()
- status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: BaseFilterLookup[Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -536,15 +622,17 @@ class InventoryItemFilter(ComponentModelFilterMixin):
@strawberry_django.filter_type(models.InventoryItemRole, lookups=True)
-class InventoryItemRoleFilter(OrganizationalModelFilterMixin):
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class InventoryItemRoleFilter(OrganizationalModelFilter):
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
@strawberry_django.filter_type(models.Location, lookups=True)
-class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, NestedGroupModelFilterMixin):
+class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, NestedGroupModelFilter):
site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
site_id: ID | None = strawberry_django.filter_field()
- status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
facility: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -557,12 +645,12 @@ class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilt
@strawberry_django.filter_type(models.Manufacturer, lookups=True)
-class ManufacturerFilter(ContactFilterMixin, OrganizationalModelFilterMixin):
+class ManufacturerFilter(ContactFilterMixin, OrganizationalModelFilter):
pass
@strawberry_django.filter_type(models.Module, lookups=True)
-class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin):
+class ModuleFilter(ConfigContextFilterMixin, PrimaryModelFilter):
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
device_id: ID | None = strawberry_django.filter_field()
module_bay: Annotated['ModuleBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -573,7 +661,7 @@ class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin):
strawberry_django.filter_field()
)
module_type_id: ID | None = strawberry_django.filter_field()
- status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
serial: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -611,7 +699,7 @@ class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin):
@strawberry_django.filter_type(models.ModuleBay, lookups=True)
-class ModuleBayFilter(ModularComponentModelFilterMixin):
+class ModuleBayFilter(ModularComponentFilterMixin, NetBoxModelFilter):
parent: Annotated['ModuleBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -620,17 +708,17 @@ class ModuleBayFilter(ModularComponentModelFilterMixin):
@strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True)
-class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin):
+class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
position: FilterLookup[str] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True)
-class ModuleTypeProfileFilter(PrimaryModelFilterMixin):
+class ModuleTypeProfileFilter(PrimaryModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ModuleType, lookups=True)
-class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin):
+class ModuleTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryModelFilter):
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -641,7 +729,10 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
profile_id: ID | None = strawberry_django.filter_field()
model: FilterLookup[str] | None = strawberry_django.filter_field()
part_number: FilterLookup[str] | None = strawberry_django.filter_field()
- airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
+ strawberry_django.filter_field()
+ )
+ airflow: BaseFilterLookup[Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
console_port_templates: (
@@ -674,10 +765,11 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
inventory_item_templates: (
Annotated['InventoryItemTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None
) = strawberry_django.filter_field()
+ module_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Platform, lookups=True)
-class PlatformFilter(OrganizationalModelFilterMixin):
+class PlatformFilter(OrganizationalModelFilter):
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -689,7 +781,7 @@ class PlatformFilter(OrganizationalModelFilterMixin):
@strawberry_django.filter_type(models.PowerFeed, lookups=True)
-class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin):
+class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
power_panel: Annotated['PowerPanelFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -697,16 +789,16 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
rack_id: ID | None = strawberry_django.filter_field()
name: FilterLookup[str] | None = strawberry_django.filter_field()
- status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ type: BaseFilterLookup[Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ supply: BaseFilterLookup[Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ phase: BaseFilterLookup[Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -724,36 +816,41 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
@strawberry_django.filter_type(models.PowerOutlet, lookups=True)
-class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class PowerOutletFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
power_port: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
power_port_id: ID | None = strawberry_django.filter_field()
- feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ status: BaseFilterLookup[Annotated['PowerOutletStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True)
-class PowerOutletTemplateFilter(ModularComponentModelFilterMixin):
- type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class PowerOutletTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
power_port: Annotated['PowerPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
power_port_id: ID | None = strawberry_django.filter_field()
- feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.PowerPanel, lookups=True)
-class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryModelFilterMixin):
+class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryModelFilter):
site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
site_id: ID | None = strawberry_django.filter_field()
location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -766,8 +863,8 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo
@strawberry_django.filter_type(models.PowerPort, lookups=True)
-class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class PowerPortFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -779,8 +876,8 @@ class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM
@strawberry_django.filter_type(models.PowerPortTemplate, lookups=True)
-class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -792,8 +889,8 @@ class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin):
@strawberry_django.filter_type(models.RackType, lookups=True)
-class RackTypeFilter(RackBaseFilterMixin):
- form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class RackTypeFilter(RackFilterMixin, WeightFilterMixin, PrimaryModelFilter):
+ form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -802,11 +899,20 @@ class RackTypeFilter(RackBaseFilterMixin):
manufacturer_id: ID | None = strawberry_django.filter_field()
model: FilterLookup[str] | None = strawberry_django.filter_field()
slug: FilterLookup[str] | None = strawberry_django.filter_field()
+ racks: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
+ rack_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Rack, lookups=True)
-class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, RackBaseFilterMixin):
- form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+class RackFilter(
+ ContactFilterMixin,
+ ImageAttachmentFilterMixin,
+ TenancyFilterMixin,
+ WeightFilterMixin,
+ RackFilterMixin,
+ PrimaryModelFilter
+):
+ form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
rack_type: Annotated['RackTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -823,12 +929,14 @@ class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+ status: BaseFilterLookup[Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
role_id: ID | None = strawberry_django.filter_field()
serial: FilterLookup[str] | None = strawberry_django.filter_field()
asset_tag: FilterLookup[str] | None = strawberry_django.filter_field()
- airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -837,7 +945,7 @@ class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
@strawberry_django.filter_type(models.RackReservation, lookups=True)
-class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
+class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilter):
rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
rack_id: ID | None = strawberry_django.filter_field()
units: Annotated['IntegerArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -846,33 +954,46 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
+ status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
@strawberry_django.filter_type(models.RackRole, lookups=True)
-class RackRoleFilter(OrganizationalModelFilterMixin):
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class RackRoleFilter(OrganizationalModelFilter):
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
@strawberry_django.filter_type(models.RearPort, lookups=True)
-class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
- type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class RearPortFilter(ModularComponentFilterMixin, CabledObjectModelFilterMixin, NetBoxModelFilter):
+ type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.RearPortTemplate, lookups=True)
-class RearPortTemplateFilter(ModularComponentTemplateFilterMixin):
- type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class RearPortTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@strawberry_django.filter_type(models.Region, lookups=True)
-class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin):
+class RegionFilter(ContactFilterMixin, NestedGroupModelFilter):
prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -882,10 +1003,12 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin):
@strawberry_django.filter_type(models.Site, lookups=True)
-class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin):
+class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
slug: FilterLookup[str] | None = strawberry_django.filter_field()
- status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+ status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
@@ -916,7 +1039,7 @@ class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
@strawberry_django.filter_type(models.SiteGroup, lookups=True)
-class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilterMixin):
+class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilter):
prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -926,7 +1049,7 @@ class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilterMixin):
@strawberry_django.filter_type(models.VirtualChassis, lookups=True)
-class VirtualChassisFilter(PrimaryModelFilterMixin):
+class VirtualChassisFilter(PrimaryModelFilter):
master: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
master_id: ID | None = strawberry_django.filter_field()
name: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -938,11 +1061,13 @@ class VirtualChassisFilter(PrimaryModelFilterMixin):
@strawberry_django.filter_type(models.VirtualDeviceContext, lookups=True)
-class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
+class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter):
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
device_id: ID | None = strawberry_django.filter_field()
name: FilterLookup[str] | None = strawberry_django.filter_field()
- status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+ status: (
+ BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
diff --git a/netbox/dcim/graphql/gfk_mixins.py b/netbox/dcim/graphql/gfk_mixins.py
index 86ca88774..4d0948207 100644
--- a/netbox/dcim/graphql/gfk_mixins.py
+++ b/netbox/dcim/graphql/gfk_mixins.py
@@ -1,3 +1,5 @@
+from strawberry.types import Info
+
from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
from circuits.models import CircuitTermination, ProviderNetwork
from dcim.graphql.types import (
@@ -49,7 +51,7 @@ class InventoryItemTemplateComponentType:
)
@classmethod
- def resolve_type(cls, instance, info):
+ def resolve_type(cls, instance, info: Info):
if type(instance) is ConsolePortTemplate:
return ConsolePortTemplateType
if type(instance) is ConsoleServerPortTemplate:
@@ -79,7 +81,7 @@ class InventoryItemComponentType:
)
@classmethod
- def resolve_type(cls, instance, info):
+ def resolve_type(cls, instance, info: Info):
if type(instance) is ConsolePort:
return ConsolePortType
if type(instance) is ConsoleServerPort:
@@ -112,7 +114,7 @@ class ConnectedEndpointType:
)
@classmethod
- def resolve_type(cls, instance, info):
+ def resolve_type(cls, instance, info: Info):
if type(instance) is CircuitTermination:
return CircuitTerminationType
if type(instance) is ConsolePortType:
diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py
index 0cd5e8fd1..1132a0ca9 100644
--- a/netbox/dcim/graphql/types.py
+++ b/netbox/dcim/graphql/types.py
@@ -5,16 +5,13 @@ import strawberry_django
from core.graphql.mixins import ChangelogMixin
from dcim import models
-from extras.graphql.mixins import (
- ConfigContextMixin,
- ContactsMixin,
- CustomFieldsMixin,
- ImageAttachmentsMixin,
- TagsMixin,
-)
+from extras.graphql.mixins import ConfigContextMixin, ContactsMixin, ImageAttachmentsMixin
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.scalars import BigInt
-from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
+from netbox.graphql.types import (
+ BaseObjectType, NestedGroupObjectType, NetBoxObjectType, OrganizationalObjectType, PrimaryObjectType,
+)
+from users.graphql.mixins import OwnerMixin
from .filters import *
from .mixins import CabledObjectMixin, PathEndpointMixin
@@ -91,12 +88,7 @@ __all__ = (
@strawberry.type
-class ComponentType(
- ChangelogMixin,
- CustomFieldsMixin,
- TagsMixin,
- BaseObjectType
-):
+class ComponentType(OwnerMixin, NetBoxObjectType):
"""
Base type for device/VM components
"""
@@ -159,7 +151,7 @@ class CableTerminationType(NetBoxObjectType):
filters=CableFilter,
pagination=True
)
-class CableType(NetBoxObjectType):
+class CableType(PrimaryObjectType):
color: str
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -236,7 +228,7 @@ class ConsoleServerPortTemplateType(ModularComponentTemplateType):
filters=DeviceFilter,
pagination=True
)
-class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
console_port_count: BigInt
console_server_port_count: BigInt
power_port_count: BigInt
@@ -339,7 +331,7 @@ class InventoryItemTemplateType(ComponentTemplateType):
filters=DeviceRoleFilter,
pagination=True
)
-class DeviceRoleType(OrganizationalObjectType):
+class DeviceRoleType(NestedGroupObjectType):
parent: Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')] | None
children: List[Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')]]
color: str
@@ -355,7 +347,7 @@ class DeviceRoleType(OrganizationalObjectType):
filters=DeviceTypeFilter,
pagination=True
)
-class DeviceTypeType(NetBoxObjectType):
+class DeviceTypeType(PrimaryObjectType):
console_port_template_count: BigInt
console_server_port_template_count: BigInt
power_port_template_count: BigInt
@@ -366,6 +358,7 @@ class DeviceTypeType(NetBoxObjectType):
device_bay_template_count: BigInt
module_bay_template_count: BigInt
inventory_item_template_count: BigInt
+ device_count: BigInt
front_image: strawberry_django.fields.types.DjangoImageType | None
rear_image: strawberry_django.fields.types.DjangoImageType | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
@@ -392,7 +385,8 @@ class DeviceTypeType(NetBoxObjectType):
)
class FrontPortType(ModularComponentType, CabledObjectMixin):
color: str
- rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
+
+ mappings: List[Annotated["PortMappingType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.type(
@@ -403,7 +397,8 @@ class FrontPortType(ModularComponentType, CabledObjectMixin):
)
class FrontPortTemplateType(ModularComponentTemplateType):
color: str
- rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
+
+ mappings: List[Annotated["PortMappingTemplateType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.type(
@@ -412,7 +407,7 @@ class FrontPortTemplateType(ModularComponentTemplateType):
filters=MACAddressFilter,
pagination=True
)
-class MACAddressType(NetBoxObjectType):
+class MACAddressType(PrimaryObjectType):
mac_address: str
@strawberry_django.field
@@ -512,7 +507,7 @@ class InventoryItemRoleType(OrganizationalObjectType):
filters=LocationFilter,
pagination=True
)
-class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
+class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NestedGroupObjectType):
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
parent: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
@@ -555,7 +550,7 @@ class ManufacturerType(OrganizationalObjectType, ContactsMixin):
filters=ModuleFilter,
pagination=True
)
-class ModuleType(NetBoxObjectType):
+class ModuleType(PrimaryObjectType):
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
module_bay: Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]
module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')]
@@ -602,7 +597,7 @@ class ModuleBayTemplateType(ModularComponentTemplateType):
filters=ModuleTypeProfileFilter,
pagination=True
)
-class ModuleTypeProfileType(NetBoxObjectType):
+class ModuleTypeProfileType(PrimaryObjectType):
module_types: List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
@@ -612,7 +607,8 @@ class ModuleTypeProfileType(NetBoxObjectType):
filters=ModuleTypeFilter,
pagination=True
)
-class ModuleTypeType(NetBoxObjectType):
+class ModuleTypeType(PrimaryObjectType):
+ module_count: BigInt
profile: Annotated["ModuleTypeProfileType", strawberry.lazy('dcim.graphql.types')] | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
@@ -632,7 +628,7 @@ class ModuleTypeType(NetBoxObjectType):
filters=PlatformFilter,
pagination=True
)
-class PlatformType(OrganizationalObjectType):
+class PlatformType(NestedGroupObjectType):
parent: Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')] | None
children: List[Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')]]
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')] | None
@@ -642,13 +638,35 @@ class PlatformType(OrganizationalObjectType):
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
+@strawberry_django.type(
+ models.PortMapping,
+ fields='__all__',
+ filters=PortMappingFilter,
+ pagination=True
+)
+class PortMappingType(ModularComponentTemplateType):
+ front_port: Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]
+ rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
+
+
+@strawberry_django.type(
+ models.PortTemplateMapping,
+ fields='__all__',
+ filters=PortTemplateMappingFilter,
+ pagination=True
+)
+class PortMappingTemplateType(ModularComponentTemplateType):
+ front_port: Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]
+ rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
+
+
@strawberry_django.type(
models.PowerFeed,
exclude=['_path'],
filters=PowerFeedFilter,
pagination=True
)
-class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
+class PowerFeedType(CabledObjectMixin, PathEndpointMixin, PrimaryObjectType):
power_panel: Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]
rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -673,6 +691,7 @@ class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
)
class PowerOutletTemplateType(ModularComponentTemplateType):
power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
+ color: str
@strawberry_django.type(
@@ -681,7 +700,7 @@ class PowerOutletTemplateType(ModularComponentTemplateType):
filters=PowerPanelFilter,
pagination=True
)
-class PowerPanelType(NetBoxObjectType, ContactsMixin):
+class PowerPanelType(ContactsMixin, PrimaryObjectType):
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
@@ -715,7 +734,8 @@ class PowerPortTemplateType(ModularComponentTemplateType):
filters=RackTypeFilter,
pagination=True
)
-class RackTypeType(NetBoxObjectType):
+class RackTypeType(PrimaryObjectType):
+ rack_count: BigInt
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
@@ -725,7 +745,7 @@ class RackTypeType(NetBoxObjectType):
filters=RackFilter,
pagination=True
)
-class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -744,7 +764,7 @@ class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
filters=RackReservationFilter,
pagination=True
)
-class RackReservationType(NetBoxObjectType):
+class RackReservationType(PrimaryObjectType):
units: List[int]
rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')]
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -772,7 +792,7 @@ class RackRoleType(OrganizationalObjectType):
class RearPortType(ModularComponentType, CabledObjectMixin):
color: str
- frontports: List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]
+ mappings: List[Annotated["PortMappingType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.type(
@@ -784,7 +804,7 @@ class RearPortType(ModularComponentType, CabledObjectMixin):
class RearPortTemplateType(ModularComponentTemplateType):
color: str
- frontport_templates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
+ mappings: List[Annotated["PortMappingTemplateType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.type(
@@ -793,7 +813,7 @@ class RearPortTemplateType(ModularComponentTemplateType):
filters=RegionFilter,
pagination=True
)
-class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
+class RegionType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
sites: List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
children: List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]
@@ -819,7 +839,7 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
filters=SiteFilter,
pagination=True
)
-class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
time_zone: str | None
region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
@@ -854,7 +874,7 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
filters=SiteGroupFilter,
pagination=True
)
-class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
+class SiteGroupType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
sites: List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
children: List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]
@@ -880,7 +900,7 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
filters=VirtualChassisFilter,
pagination=True
)
-class VirtualChassisType(NetBoxObjectType):
+class VirtualChassisType(PrimaryObjectType):
member_count: BigInt
master: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
@@ -893,7 +913,7 @@ class VirtualChassisType(NetBoxObjectType):
filters=VirtualDeviceContextFilter,
pagination=True
)
-class VirtualDeviceContextType(NetBoxObjectType):
+class VirtualDeviceContextType(PrimaryObjectType):
device: Annotated["DeviceType", 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
diff --git a/netbox/dcim/management/commands/trace_paths.py b/netbox/dcim/management/commands/trace_paths.py
index 592aeb6a7..ded4e1780 100644
--- a/netbox/dcim/management/commands/trace_paths.py
+++ b/netbox/dcim/management/commands/trace_paths.py
@@ -4,7 +4,7 @@ from django.db import connection
from django.db.models import Q
from dcim.models import CablePath, ConsolePort, ConsoleServerPort, Interface, PowerFeed, PowerOutlet, PowerPort
-from dcim.signals import create_cablepath
+from dcim.signals import create_cablepaths
ENDPOINT_MODELS = (
ConsolePort,
@@ -81,7 +81,7 @@ class Command(BaseCommand):
self.stdout.write(f'Retracing {origins_count} cabled {model._meta.verbose_name_plural}...')
i = 0
for i, obj in enumerate(origins, start=1):
- create_cablepath([obj])
+ create_cablepaths([obj])
if not i % 100:
self.draw_progress_bar(i * 100 / origins_count)
self.draw_progress_bar(100)
diff --git a/netbox/dcim/migrations/0188_racktype.py b/netbox/dcim/migrations/0188_racktype.py
index 7c36e03ba..1b5fd25c8 100644
--- a/netbox/dcim/migrations/0188_racktype.py
+++ b/netbox/dcim/migrations/0188_racktype.py
@@ -3,9 +3,7 @@ import django.db.models.deletion
import taggit.managers
from django.db import migrations, models
-import utilities.fields
import utilities.json
-import utilities.ordering
class Migration(migrations.Migration):
diff --git a/netbox/dcim/migrations/0216_latitude_longitude_validators.py b/netbox/dcim/migrations/0216_latitude_longitude_validators.py
new file mode 100644
index 000000000..e3bd2ca78
--- /dev/null
+++ b/netbox/dcim/migrations/0216_latitude_longitude_validators.py
@@ -0,0 +1,69 @@
+import decimal
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0215_rackreservation_status'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='device',
+ name='latitude',
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=6,
+ max_digits=8,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
+ django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
+ ],
+ ),
+ ),
+ migrations.AlterField(
+ model_name='device',
+ name='longitude',
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=6,
+ max_digits=9,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
+ django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
+ ],
+ ),
+ ),
+ migrations.AlterField(
+ model_name='site',
+ name='latitude',
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=6,
+ max_digits=8,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
+ django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
+ ],
+ ),
+ ),
+ migrations.AlterField(
+ model_name='site',
+ name='longitude',
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=6,
+ max_digits=9,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
+ django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
+ ],
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0217_poweroutlettemplate_color.py b/netbox/dcim/migrations/0217_poweroutlettemplate_color.py
new file mode 100644
index 000000000..95ebad57a
--- /dev/null
+++ b/netbox/dcim/migrations/0217_poweroutlettemplate_color.py
@@ -0,0 +1,17 @@
+import utilities.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0216_latitude_longitude_validators'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='poweroutlettemplate',
+ name='color',
+ field=utilities.fields.ColorField(blank=True, max_length=6),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0218_owner.py b/netbox/dcim/migrations/0218_owner.py
new file mode 100644
index 000000000..e1c9e64ac
--- /dev/null
+++ b/netbox/dcim/migrations/0218_owner.py
@@ -0,0 +1,243 @@
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0217_poweroutlettemplate_color'),
+ ('users', '0015_owner'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cable',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='consoleport',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='consoleserverport',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='device',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='devicebay',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='devicerole',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='devicetype',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='frontport',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='interface',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='inventoryitem',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='inventoryitemrole',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='location',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='macaddress',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='manufacturer',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='module',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='modulebay',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='moduletype',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='moduletypeprofile',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='platform',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerfeed',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='poweroutlet',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerpanel',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerport',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='rack',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='rackreservation',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='rackrole',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='racktype',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='rearport',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='region',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='site',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='sitegroup',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='virtualchassis',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ migrations.AddField(
+ model_name='virtualdevicecontext',
+ name='owner',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0219_devicetype_device_count.py b/netbox/dcim/migrations/0219_devicetype_device_count.py
new file mode 100644
index 000000000..c28304738
--- /dev/null
+++ b/netbox/dcim/migrations/0219_devicetype_device_count.py
@@ -0,0 +1,66 @@
+import utilities.fields
+from django.db import migrations
+from django.db.models import Count, OuterRef, Subquery
+
+
+def _populate_count_for_type(
+ apps, schema_editor, app_name: str, model_name: str, target_field: str, related_name: str = 'instances'
+):
+ """
+ Update a CounterCache field on the specified model by annotating the count of related instances.
+ """
+ Model = apps.get_model(app_name, model_name)
+ db_alias = schema_editor.connection.alias
+
+ count_subquery = (
+ Model.objects.using(db_alias)
+ .filter(pk=OuterRef('pk'))
+ .annotate(_instance_count=Count(related_name))
+ .values('_instance_count')
+ )
+ Model.objects.using(db_alias).update(**{target_field: Subquery(count_subquery)})
+
+
+def populate_device_type_device_count(apps, schema_editor):
+ _populate_count_for_type(apps, schema_editor, 'dcim', 'DeviceType', 'device_count')
+
+
+def populate_module_type_module_count(apps, schema_editor):
+ _populate_count_for_type(apps, schema_editor, 'dcim', 'ModuleType', 'module_count')
+
+
+def populate_rack_type_rack_count(apps, schema_editor):
+ _populate_count_for_type(apps, schema_editor, 'dcim', 'RackType', 'rack_count', related_name='racks')
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0218_owner'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='devicetype',
+ name='device_count',
+ field=utilities.fields.CounterCacheField(
+ default=0, editable=False, to_field='device_type', to_model='dcim.Device'
+ ),
+ ),
+ migrations.RunPython(populate_device_type_device_count, migrations.RunPython.noop),
+ migrations.AddField(
+ model_name='moduletype',
+ name='module_count',
+ field=utilities.fields.CounterCacheField(
+ default=0, editable=False, to_field='module_type', to_model='dcim.Module'
+ ),
+ ),
+ migrations.RunPython(populate_module_type_module_count, migrations.RunPython.noop),
+ migrations.AddField(
+ model_name='racktype',
+ name='rack_count',
+ field=utilities.fields.CounterCacheField(
+ default=0, editable=False, to_field='rack_type', to_model='dcim.Rack'
+ ),
+ ),
+ migrations.RunPython(populate_rack_type_rack_count, migrations.RunPython.noop),
+ ]
diff --git a/netbox/dcim/migrations/0220_cable_profile.py b/netbox/dcim/migrations/0220_cable_profile.py
new file mode 100644
index 000000000..5160506ed
--- /dev/null
+++ b/netbox/dcim/migrations/0220_cable_profile.py
@@ -0,0 +1,56 @@
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0219_devicetype_device_count'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cable',
+ name='profile',
+ field=models.CharField(blank=True, max_length=50),
+ ),
+ migrations.AddField(
+ model_name='cabletermination',
+ name='connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ]
+ ),
+ ),
+ migrations.AddField(
+ model_name='cabletermination',
+ name='positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None
+ ),
+ ),
+ migrations.AlterModelOptions(
+ name='cabletermination',
+ options={'ordering': ('cable', 'cable_end', 'connector', 'pk')}, # connector may be null
+ ),
+ migrations.AddConstraint(
+ model_name='cabletermination',
+ constraint=models.UniqueConstraint(
+ fields=('cable', 'cable_end', 'connector'),
+ name='dcim_cabletermination_unique_connector'
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0221_cable_connector_positions.py b/netbox/dcim/migrations/0221_cable_connector_positions.py
new file mode 100644
index 000000000..e986a28e1
--- /dev/null
+++ b/netbox/dcim/migrations/0221_cable_connector_positions.py
@@ -0,0 +1,228 @@
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0220_cable_profile'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='consoleport',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='consoleport',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='consoleserverport',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='consoleserverport',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='frontport',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='frontport',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='interface',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='interface',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerfeed',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerfeed',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='poweroutlet',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='poweroutlet',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerport',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='powerport',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ migrations.AddField(
+ model_name='rearport',
+ name='cable_connector',
+ field=models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(256)
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name='rearport',
+ name='cable_positions',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ blank=True,
+ null=True,
+ size=None,
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0222_port_mappings.py b/netbox/dcim/migrations/0222_port_mappings.py
new file mode 100644
index 000000000..42de44dbc
--- /dev/null
+++ b/netbox/dcim/migrations/0222_port_mappings.py
@@ -0,0 +1,219 @@
+import django.core.validators
+import django.db.models.deletion
+from django.db import migrations
+from django.db import models
+from itertools import islice
+
+
+def chunked(iterable, size):
+ """
+ Yield successive chunks of a given size from an iterator.
+ """
+ iterator = iter(iterable)
+ while chunk := list(islice(iterator, size)):
+ yield chunk
+
+
+def populate_port_template_mappings(apps, schema_editor):
+ FrontPortTemplate = apps.get_model('dcim', 'FrontPortTemplate')
+ PortTemplateMapping = apps.get_model('dcim', 'PortTemplateMapping')
+
+ front_ports = FrontPortTemplate.objects.iterator(chunk_size=1000)
+
+ def generate_copies():
+ for front_port in front_ports:
+ yield PortTemplateMapping(
+ device_type_id=front_port.device_type_id,
+ module_type_id=front_port.module_type_id,
+ front_port_id=front_port.pk,
+ front_port_position=1,
+ rear_port_id=front_port.rear_port_id,
+ rear_port_position=front_port.rear_port_position,
+ )
+
+ # Bulk insert in streaming batches
+ for chunk in chunked(generate_copies(), 1000):
+ PortTemplateMapping.objects.bulk_create(chunk, batch_size=1000)
+
+
+def populate_port_mappings(apps, schema_editor):
+ FrontPort = apps.get_model('dcim', 'FrontPort')
+ PortMapping = apps.get_model('dcim', 'PortMapping')
+
+ front_ports = FrontPort.objects.iterator(chunk_size=1000)
+
+ def generate_copies():
+ for front_port in front_ports:
+ yield PortMapping(
+ device_id=front_port.device_id,
+ front_port_id=front_port.pk,
+ front_port_position=1,
+ rear_port_id=front_port.rear_port_id,
+ rear_port_position=front_port.rear_port_position,
+ )
+
+ # Bulk insert in streaming batches
+ for chunk in chunked(generate_copies(), 1000):
+ PortMapping.objects.bulk_create(chunk, batch_size=1000)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0221_cable_connector_positions'),
+ ]
+
+ operations = [
+ # Create PortTemplateMapping model (for DeviceTypes)
+ migrations.CreateModel(
+ name='PortTemplateMapping',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
+ (
+ 'front_port_position',
+ models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ )
+ ),
+ (
+ 'rear_port_position',
+ models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ )
+ ),
+ (
+ 'device_type',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.devicetype',
+ related_name='port_mappings',
+ blank=True,
+ null=True
+ )
+ ),
+ (
+ 'module_type',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.moduletype',
+ related_name='port_mappings',
+ blank=True,
+ null=True
+ )
+ ),
+ (
+ 'front_port',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.frontporttemplate',
+ related_name='mappings'
+ )
+ ),
+ (
+ 'rear_port',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.rearporttemplate',
+ related_name='mappings'
+ )
+ ),
+ ],
+ ),
+ migrations.AddConstraint(
+ model_name='porttemplatemapping',
+ constraint=models.UniqueConstraint(
+ fields=('front_port', 'front_port_position'),
+ name='dcim_porttemplatemapping_unique_front_port_position'
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name='porttemplatemapping',
+ constraint=models.UniqueConstraint(
+ fields=('rear_port', 'rear_port_position'),
+ name='dcim_porttemplatemapping_unique_rear_port_position'
+ ),
+ ),
+
+ # Create PortMapping model (for Devices)
+ migrations.CreateModel(
+ name='PortMapping',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
+ (
+ 'front_port_position',
+ models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ ),
+ ),
+ (
+ 'rear_port_position',
+ models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024),
+ ]
+ ),
+ ),
+ (
+ 'device',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.device',
+ related_name='port_mappings'
+ )
+ ),
+ (
+ 'front_port',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.frontport',
+ related_name='mappings'
+ )
+ ),
+ (
+ 'rear_port',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='dcim.rearport',
+ related_name='mappings'
+ )
+ ),
+ ],
+ ),
+ migrations.AddConstraint(
+ model_name='portmapping',
+ constraint=models.UniqueConstraint(
+ fields=('front_port', 'front_port_position'),
+ name='dcim_portmapping_unique_front_port_position'
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name='portmapping',
+ constraint=models.UniqueConstraint(
+ fields=('rear_port', 'rear_port_position'),
+ name='dcim_portmapping_unique_rear_port_position'
+ ),
+ ),
+
+ # Data migration
+ migrations.RunPython(
+ code=populate_port_template_mappings,
+ reverse_code=migrations.RunPython.noop
+ ),
+ migrations.RunPython(
+ code=populate_port_mappings,
+ reverse_code=migrations.RunPython.noop
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0223_frontport_positions.py b/netbox/dcim/migrations/0223_frontport_positions.py
new file mode 100644
index 000000000..fc3394738
--- /dev/null
+++ b/netbox/dcim/migrations/0223_frontport_positions.py
@@ -0,0 +1,65 @@
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0222_port_mappings'),
+ ]
+
+ operations = [
+ # Remove rear_port & rear_port_position from FrontPortTemplate
+ migrations.RemoveConstraint(
+ model_name='frontporttemplate',
+ name='dcim_frontporttemplate_unique_rear_port_position',
+ ),
+ migrations.RemoveField(
+ model_name='frontporttemplate',
+ name='rear_port',
+ ),
+ migrations.RemoveField(
+ model_name='frontporttemplate',
+ name='rear_port_position',
+ ),
+
+ # Add positions on FrontPortTemplate
+ migrations.AddField(
+ model_name='frontporttemplate',
+ name='positions',
+ field=models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ ),
+ ),
+
+ # Remove rear_port & rear_port_position from FrontPort
+ migrations.RemoveConstraint(
+ model_name='frontport',
+ name='dcim_frontport_unique_rear_port_position',
+ ),
+ migrations.RemoveField(
+ model_name='frontport',
+ name='rear_port',
+ ),
+ migrations.RemoveField(
+ model_name='frontport',
+ name='rear_port_position',
+ ),
+
+ # Add positions on FrontPort
+ migrations.AddField(
+ model_name='frontport',
+ name='positions',
+ field=models.PositiveSmallIntegerField(
+ default=1,
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(1024)
+ ]
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0224_add_comments_to_organizationalmodel.py b/netbox/dcim/migrations/0224_add_comments_to_organizationalmodel.py
new file mode 100644
index 000000000..0ec79102f
--- /dev/null
+++ b/netbox/dcim/migrations/0224_add_comments_to_organizationalmodel.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.2.8 on 2025-12-08 17:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dcim', '0223_frontport_positions'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='inventoryitemrole',
+ name='comments',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='manufacturer',
+ name='comments',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='rackrole',
+ name='comments',
+ field=models.TextField(blank=True),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0225_gfk_indexes.py b/netbox/dcim/migrations/0225_gfk_indexes.py
new file mode 100644
index 000000000..393fc0f8f
--- /dev/null
+++ b/netbox/dcim/migrations/0225_gfk_indexes.py
@@ -0,0 +1,19 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('contenttypes', '0002_remove_content_type_name'),
+ ('dcim', '0224_add_comments_to_organizationalmodel'),
+ ('extras', '0134_owner'),
+ ('users', '0015_owner'),
+ ]
+
+ operations = [
+ migrations.AddIndex(
+ model_name='macaddress',
+ index=models.Index(
+ fields=['assigned_object_type', 'assigned_object_id'], name='dcim_macadd_assigne_54115d_idx'
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/models/base.py b/netbox/dcim/models/base.py
new file mode 100644
index 000000000..f8021d4db
--- /dev/null
+++ b/netbox/dcim/models/base.py
@@ -0,0 +1,61 @@
+from django.core.exceptions import ValidationError
+from django.core.validators import MaxValueValidator, MinValueValidator
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+from dcim.constants import PORT_POSITION_MAX, PORT_POSITION_MIN
+
+__all__ = (
+ 'PortMappingBase',
+)
+
+
+class PortMappingBase(models.Model):
+ """
+ Base class for PortMapping and PortTemplateMapping
+ """
+ front_port_position = models.PositiveSmallIntegerField(
+ default=1,
+ validators=(
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX),
+ ),
+ )
+ rear_port_position = models.PositiveSmallIntegerField(
+ default=1,
+ validators=(
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX),
+ ),
+ )
+
+ _netbox_private = True
+
+ class Meta:
+ abstract = True
+ constraints = (
+ models.UniqueConstraint(
+ fields=('front_port', 'front_port_position'),
+ name='%(app_label)s_%(class)s_unique_front_port_position'
+ ),
+ models.UniqueConstraint(
+ fields=('rear_port', 'rear_port_position'),
+ name='%(app_label)s_%(class)s_unique_rear_port_position'
+ ),
+ )
+
+ def clean(self):
+ super().clean()
+
+ # Validate rear port position
+ if self.rear_port_position > self.rear_port.positions:
+ raise ValidationError({
+ "rear_port_position": _(
+ "Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} "
+ "positions."
+ ).format(
+ rear_port_position=self.rear_port_position,
+ name=self.rear_port.name,
+ positions=self.rear_port.positions
+ )
+ })
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
index 69e07ed94..8e155d70e 100644
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -1,8 +1,11 @@
import itertools
+import logging
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
+from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
+from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.dispatch import Signal
from django.utils.translation import gettext_lazy as _
@@ -10,6 +13,7 @@ from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
from dcim.choices import *
from dcim.constants import *
+from dcim.exceptions import UnsupportedCablePath
from dcim.fields import PathField
from dcim.utils import decompile_path_node, object_to_path_node
from netbox.choices import ColorChoices
@@ -18,8 +22,9 @@ from utilities.conversion import to_meters
from utilities.exceptions import AbortRequest
from utilities.fields import ColorField, GenericArrayForeignKey
from utilities.querysets import RestrictedQuerySet
+from utilities.serialization import deserialize_object, serialize_object
from wireless.models import WirelessLink
-from .device_components import FrontPort, RearPort, PathEndpoint
+from .device_components import FrontPort, PathEndpoint, PortMapping, RearPort
__all__ = (
'Cable',
@@ -27,7 +32,7 @@ __all__ = (
'CableTermination',
)
-from ..exceptions import UnsupportedCablePath
+logger = logging.getLogger(f'netbox.{__name__}')
trace_paths = Signal()
@@ -53,6 +58,12 @@ class Cable(PrimaryModel):
choices=LinkStatusChoices,
default=LinkStatusChoices.STATUS_CONNECTED
)
+ profile = models.CharField(
+ verbose_name=_('profile'),
+ max_length=50,
+ choices=CableProfileChoices,
+ blank=True,
+ )
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
@@ -91,7 +102,7 @@ class Cable(PrimaryModel):
null=True
)
- clone_fields = ('tenant', 'type',)
+ clone_fields = ('tenant', 'type', 'profile')
class Meta:
ordering = ('pk',)
@@ -119,43 +130,91 @@ class Cable(PrimaryModel):
pk = self.pk or self._pk
return self.label or f'#{pk}'
- @property
- def a_terminations(self):
- if hasattr(self, '_a_terminations'):
- return self._a_terminations
+ def get_status_color(self):
+ return LinkStatusChoices.colors.get(self.status)
+ @property
+ def profile_class(self):
+ from dcim import cable_profiles
+ return {
+ CableProfileChoices.SINGLE_1C1P: cable_profiles.Single1C1PCableProfile,
+ CableProfileChoices.SINGLE_1C2P: cable_profiles.Single1C2PCableProfile,
+ CableProfileChoices.SINGLE_1C4P: cable_profiles.Single1C4PCableProfile,
+ CableProfileChoices.SINGLE_1C6P: cable_profiles.Single1C6PCableProfile,
+ CableProfileChoices.SINGLE_1C8P: cable_profiles.Single1C8PCableProfile,
+ CableProfileChoices.SINGLE_1C12P: cable_profiles.Single1C12PCableProfile,
+ CableProfileChoices.SINGLE_1C16P: cable_profiles.Single1C16PCableProfile,
+ CableProfileChoices.TRUNK_2C1P: cable_profiles.Trunk2C1PCableProfile,
+ CableProfileChoices.TRUNK_2C2P: cable_profiles.Trunk2C2PCableProfile,
+ CableProfileChoices.TRUNK_2C4P: cable_profiles.Trunk2C4PCableProfile,
+ CableProfileChoices.TRUNK_2C4P_SHUFFLE: cable_profiles.Trunk2C4PShuffleCableProfile,
+ CableProfileChoices.TRUNK_2C6P: cable_profiles.Trunk2C6PCableProfile,
+ CableProfileChoices.TRUNK_2C8P: cable_profiles.Trunk2C8PCableProfile,
+ CableProfileChoices.TRUNK_2C12P: cable_profiles.Trunk2C12PCableProfile,
+ CableProfileChoices.TRUNK_4C1P: cable_profiles.Trunk4C1PCableProfile,
+ CableProfileChoices.TRUNK_4C2P: cable_profiles.Trunk4C2PCableProfile,
+ CableProfileChoices.TRUNK_4C4P: cable_profiles.Trunk4C4PCableProfile,
+ CableProfileChoices.TRUNK_4C4P_SHUFFLE: cable_profiles.Trunk4C4PShuffleCableProfile,
+ CableProfileChoices.TRUNK_4C6P: cable_profiles.Trunk4C6PCableProfile,
+ CableProfileChoices.TRUNK_4C8P: cable_profiles.Trunk4C8PCableProfile,
+ CableProfileChoices.TRUNK_8C4P: cable_profiles.Trunk8C4PCableProfile,
+ CableProfileChoices.BREAKOUT_1C4P_4C1P: cable_profiles.Breakout1C4Px4C1PCableProfile,
+ CableProfileChoices.BREAKOUT_1C6P_6C1P: cable_profiles.Breakout1C6Px6C1PCableProfile,
+ CableProfileChoices.BREAKOUT_2C4P_8C1P_SHUFFLE: cable_profiles.Breakout2C4Px8C1PShuffleCableProfile,
+ }.get(self.profile)
+
+ def _get_x_terminations(self, side):
+ """
+ Return the terminating objects for the given cable end (A or B).
+ """
+ if side not in (CableEndChoices.SIDE_A, CableEndChoices.SIDE_B):
+ raise ValueError(f"Unknown cable side: {side}")
+ attr = f'_{side.lower()}_terminations'
+
+ if hasattr(self, attr):
+ return getattr(self, attr)
if not self.pk:
return []
-
- # Query self.terminations.all() to leverage cached results
return [
- ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_A
+ # Query self.terminations.all() to leverage cached results
+ ct.termination for ct in self.terminations.all() if ct.cable_end == side
]
+ def _set_x_terminations(self, side, value):
+ """
+ Set the terminating objects for the given cable end (A or B).
+ """
+ if side not in (CableEndChoices.SIDE_A, CableEndChoices.SIDE_B):
+ raise ValueError(f"Unknown cable side: {side}")
+ _attr = f'_{side.lower()}_terminations'
+
+ # If the provided value is a list of CableTermination IDs, resolve them
+ # to their corresponding termination objects.
+ if all(isinstance(item, int) for item in value):
+ value = [
+ ct.termination for ct in CableTermination.objects.filter(pk__in=value).prefetch_related('termination')
+ ]
+
+ if not self.pk or getattr(self, _attr, []) != list(value):
+ self._terminations_modified = True
+
+ setattr(self, _attr, value)
+
+ @property
+ def a_terminations(self):
+ return self._get_x_terminations(CableEndChoices.SIDE_A)
+
@a_terminations.setter
def a_terminations(self, value):
- if not self.pk or self.a_terminations != list(value):
- self._terminations_modified = True
- self._a_terminations = value
+ self._set_x_terminations(CableEndChoices.SIDE_A, value)
@property
def b_terminations(self):
- if hasattr(self, '_b_terminations'):
- return self._b_terminations
-
- if not self.pk:
- return []
-
- # Query self.terminations.all() to leverage cached results
- return [
- ct.termination for ct in self.terminations.all() if ct.cable_end == CableEndChoices.SIDE_B
- ]
+ return self._get_x_terminations(CableEndChoices.SIDE_B)
@b_terminations.setter
def b_terminations(self, value):
- if not self.pk or self.b_terminations != list(value):
- self._terminations_modified = True
- self._b_terminations = value
+ self._set_x_terminations(CableEndChoices.SIDE_B, value)
@property
def color_name(self):
@@ -176,6 +235,10 @@ class Cable(PrimaryModel):
if self._state.adding and self.pk is None and (not self.a_terminations or not self.b_terminations):
raise ValidationError(_("Must define A and B terminations when creating a new cable."))
+ # Validate terminations against the assigned cable profile (if any)
+ if self.profile:
+ self.profile_class().clean(self)
+
if self._terminations_modified:
# Check that all termination objects for either end are of the same type
@@ -208,7 +271,7 @@ class Cable(PrimaryModel):
for termination in self.b_terminations:
CableTermination(cable=self, cable_end='B', termination=termination).clean()
- def save(self, *args, **kwargs):
+ def save(self, *args, force_insert=False, force_update=False, using=None, update_fields=None):
_created = self.pk is None
# Store the given length (if any) in meters for use in database ordering
@@ -221,39 +284,108 @@ class Cable(PrimaryModel):
if self.length is None:
self.length_unit = None
- super().save(*args, **kwargs)
+ # If this is a new Cable, save it before attempting to create its CableTerminations
+ if self._state.adding:
+ super().save(*args, force_insert=True, using=using, update_fields=update_fields)
+ # Update the private PK used in __str__()
+ self._pk = self.pk
- # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
- self._pk = self.pk
-
- # Retrieve existing A/B terminations for the Cable
- a_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='A')}
- b_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='B')}
-
- # Delete stale CableTerminations
if self._terminations_modified:
- for termination, ct in a_terminations.items():
- if termination.pk and termination not in self.a_terminations:
- ct.delete()
- for termination, ct in b_terminations.items():
- if termination.pk and termination not in self.b_terminations:
- ct.delete()
+ self.update_terminations()
+
+ super().save(*args, force_update=True, using=using, update_fields=update_fields)
- # Save new CableTerminations (if any)
- if self._terminations_modified:
- for termination in self.a_terminations:
- if not termination.pk or termination not in a_terminations:
- CableTermination(cable=self, cable_end='A', termination=termination).save()
- for termination in self.b_terminations:
- if not termination.pk or termination not in b_terminations:
- CableTermination(cable=self, cable_end='B', termination=termination).save()
try:
trace_paths.send(Cable, instance=self, created=_created)
except UnsupportedCablePath as e:
raise AbortRequest(e)
- def get_status_color(self):
- return LinkStatusChoices.colors.get(self.status)
+ def serialize_object(self, exclude=None):
+ data = serialize_object(self, exclude=exclude or [])
+
+ # Add A & B terminations to the serialized data
+ a_terminations, b_terminations = self.get_terminations()
+ data['a_terminations'] = sorted([ct.pk for ct in a_terminations.values()])
+ data['b_terminations'] = sorted([ct.pk for ct in b_terminations.values()])
+
+ return data
+
+ @classmethod
+ def deserialize_object(cls, data, pk=None):
+ a_terminations = data.pop('a_terminations', [])
+ b_terminations = data.pop('b_terminations', [])
+
+ instance = deserialize_object(cls, data, pk=pk)
+
+ # Assign A & B termination objects to the Cable instance
+ queryset = CableTermination.objects.prefetch_related('termination')
+ instance.a_terminations = [
+ ct.termination for ct in queryset.filter(pk__in=a_terminations)
+ ]
+ instance.b_terminations = [
+ ct.termination for ct in queryset.filter(pk__in=b_terminations)
+ ]
+
+ return instance
+
+ def get_terminations(self):
+ """
+ Return two dictionaries mapping A & B side terminating objects to their corresponding CableTerminations
+ for this Cable.
+ """
+ a_terminations = {}
+ b_terminations = {}
+
+ for ct in CableTermination.objects.filter(cable=self).prefetch_related('termination'):
+ if ct.cable_end == CableEndChoices.SIDE_A:
+ a_terminations[ct.termination] = ct
+ else:
+ b_terminations[ct.termination] = ct
+
+ return a_terminations, b_terminations
+
+ def update_terminations(self):
+ """
+ Create/delete CableTerminations for this Cable to reflect its current state.
+ """
+ a_terminations, b_terminations = self.get_terminations()
+
+ # Delete any stale CableTerminations
+ for termination, ct in a_terminations.items():
+ if termination.pk and termination not in self.a_terminations:
+ ct.delete()
+ for termination, ct in b_terminations.items():
+ if termination.pk and termination not in self.b_terminations:
+ ct.delete()
+
+ # Save any new CableTerminations
+ profile = self.profile_class() if self.profile else None
+ for i, termination in enumerate(self.a_terminations, start=1):
+ if not termination.pk or termination not in a_terminations:
+ connector = positions = None
+ if profile:
+ connector = i
+ positions = profile.get_position_list(profile.a_connectors[i])
+ CableTermination(
+ cable=self,
+ cable_end=CableEndChoices.SIDE_A,
+ connector=connector,
+ positions=positions,
+ termination=termination
+ ).save()
+ for i, termination in enumerate(self.b_terminations, start=1):
+ if not termination.pk or termination not in b_terminations:
+ connector = positions = None
+ if profile:
+ connector = i
+ positions = profile.get_position_list(profile.b_connectors[i])
+ CableTermination(
+ cable=self,
+ cable_end=CableEndChoices.SIDE_B,
+ connector=connector,
+ positions=positions,
+ termination=termination
+ ).save()
class CableTermination(ChangeLoggedModel):
@@ -280,6 +412,24 @@ class CableTermination(ChangeLoggedModel):
ct_field='termination_type',
fk_field='termination_id'
)
+ connector = models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=(
+ MinValueValidator(CABLE_CONNECTOR_MIN),
+ MaxValueValidator(CABLE_CONNECTOR_MAX)
+ ),
+ )
+ positions = ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=(
+ MinValueValidator(CABLE_POSITION_MIN),
+ MaxValueValidator(CABLE_POSITION_MAX)
+ )
+ ),
+ blank=True,
+ null=True,
+ )
# Cached associations to enable efficient filtering
_device = models.ForeignKey(
@@ -310,12 +460,16 @@ class CableTermination(ChangeLoggedModel):
objects = RestrictedQuerySet.as_manager()
class Meta:
- ordering = ('cable', 'cable_end', 'pk')
+ ordering = ('cable', 'cable_end', 'connector', 'pk')
constraints = (
models.UniqueConstraint(
fields=('termination_type', 'termination_id'),
name='%(app_label)s_%(class)s_unique_termination'
),
+ models.UniqueConstraint(
+ fields=('cable', 'cable_end', 'connector'),
+ name='%(app_label)s_%(class)s_unique_connector'
+ ),
)
verbose_name = _('cable termination')
verbose_name_plural = _('cable terminations')
@@ -326,6 +480,17 @@ class CableTermination(ChangeLoggedModel):
def clean(self):
super().clean()
+ # Disallow connecting a cable to any termination object that is
+ # explicitly flagged as "mark connected".
+ termination = getattr(self, 'termination', None)
+ if termination is not None and getattr(termination, "mark_connected", False):
+ raise ValidationError(
+ _("Cannot connect a cable to {obj_parent} > {obj} because it is marked as connected.").format(
+ obj_parent=termination.parent_object,
+ obj=termination,
+ )
+ )
+
# Check for existing termination
qs = CableTermination.objects.filter(
termination_type=self.termination_type,
@@ -337,14 +502,14 @@ class CableTermination(ChangeLoggedModel):
existing_termination = qs.first()
if existing_termination is not None:
raise ValidationError(
- _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}".format(
+ _("Duplicate termination found for {app_label}.{model} {termination_id}: cable {cable_pk}").format(
app_label=self.termination_type.app_label,
model=self.termination_type.model,
termination_id=self.termination_id,
cable_pk=existing_termination.cable.pk
- ))
+ )
)
- # Validate interface type (if applicable)
+ # Validate the interface type (if applicable)
if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
raise ValidationError(
_("Cables cannot be terminated to {type_display} interfaces").format(
@@ -366,8 +531,7 @@ class CableTermination(ChangeLoggedModel):
# Set the cable on the terminating object
termination = self.termination._meta.model.objects.get(pk=self.termination_id)
termination.snapshot()
- termination.cable = self.cable
- termination.cable_end = self.cable_end
+ termination.set_cable_termination(self)
termination.save()
def delete(self, *args, **kwargs):
@@ -375,8 +539,7 @@ class CableTermination(ChangeLoggedModel):
# Delete the cable association on the terminating object
termination = self.termination._meta.model.objects.get(pk=self.termination_id)
termination.snapshot()
- termination.cable = None
- termination.cable_end = None
+ termination.clear_cable_termination(self)
termination.save()
super().delete(*args, **kwargs)
@@ -537,7 +700,7 @@ class CablePath(models.Model):
Cable or WirelessLink connects (interfaces, console ports, circuit termination, etc.). All terminations must be
of the same type and must belong to the same parent object.
"""
- from circuits.models import CircuitTermination
+ from circuits.models import CircuitTermination, Circuit
if not terminations:
return None
@@ -552,15 +715,24 @@ class CablePath(models.Model):
is_active = True
is_split = False
+ logger.debug(f'Tracing cable path from {terminations}...')
+
+ segment = 0
while terminations:
+ segment += 1
+ logger.debug(f'[Path segment #{segment}] Position stack: {position_stack}')
+ logger.debug(f'[Path segment #{segment}] Local terminations: {terminations}')
# Terminations must all be of the same type
if not all(isinstance(t, type(terminations[0])) for t in terminations[1:]):
raise UnsupportedCablePath(_("All mid-span terminations must have the same termination type"))
# All mid-span terminations must all be attached to the same device
- if (not isinstance(terminations[0], PathEndpoint) and not
- all(t.parent_object == terminations[0].parent_object for t in terminations[1:])):
+ if (
+ not isinstance(terminations[0], PathEndpoint) and
+ not isinstance(terminations[0].parent_object, Circuit) and
+ not all(t.parent_object == terminations[0].parent_object for t in terminations[1:])
+ ):
raise UnsupportedCablePath(_("All mid-span terminations must have the same parent object"))
# Check for a split path (e.g. rear port fanning out to multiple front ports with
@@ -575,9 +747,15 @@ class CablePath(models.Model):
path.append([
object_to_path_node(t) for t in terminations
])
+ # If not null, push cable position onto the stack
+ if isinstance(terminations[0], PathEndpoint) and terminations[0].cable_positions:
+ position_stack.append([terminations[0].cable_positions[0]])
# Step 2: Determine the attached links (Cable or WirelessLink), if any
- links = [termination.link for termination in terminations if termination.link is not None]
+ links = list(dict.fromkeys(
+ termination.link for termination in terminations if termination.link is not None
+ ))
+ logger.debug(f'[Path segment #{segment}] Links: {links}')
if len(links) == 0:
if len(path) == 1:
# If this is the start of the path and no link exists, return None
@@ -609,33 +787,45 @@ class CablePath(models.Model):
# Step 6: Determine the far-end terminations
if isinstance(links[0], Cable):
- termination_type = ObjectType.objects.get_for_model(terminations[0])
- local_cable_terminations = CableTermination.objects.filter(
- termination_type=termination_type,
- termination_id__in=[t.pk for t in terminations]
- )
+ # Profile-based tracing
+ if links[0].profile:
+ cable_profile = links[0].profile_class()
+ term, position = cable_profile.get_peer_termination(terminations[0], position_stack.pop()[0])
+ remote_terminations = [term]
+ position_stack.append([position])
- q_filter = Q()
- for lct in local_cable_terminations:
- cable_end = 'A' if lct.cable_end == 'B' else 'B'
- q_filter |= Q(cable=lct.cable, cable_end=cable_end)
+ # Legacy (positionless) behavior
+ else:
+ termination_type = ObjectType.objects.get_for_model(terminations[0])
+ local_cable_terminations = CableTermination.objects.filter(
+ termination_type=termination_type,
+ termination_id__in=[t.pk for t in terminations]
+ )
- # Make sure this filter has been populated; if not, we have probably been given invalid data
- if not q_filter:
- break
+ q_filter = Q()
+ for lct in local_cable_terminations:
+ cable_end = 'A' if lct.cable_end == 'B' else 'B'
+ q_filter |= Q(cable=lct.cable, cable_end=cable_end)
- remote_cable_terminations = CableTermination.objects.filter(q_filter)
- remote_terminations = [ct.termination for ct in remote_cable_terminations]
+ # Make sure this filter has been populated; if not, we have probably been given invalid data
+ if not q_filter:
+ break
+
+ remote_cable_terminations = CableTermination.objects.filter(q_filter)
+ remote_terminations = [ct.termination for ct in remote_cable_terminations]
else:
# WirelessLink
remote_terminations = [
link.interface_b if link.interface_a is terminations[0] else link.interface_a for link in links
]
+ logger.debug(f'[Path segment #{segment}] Remote terminations: {remote_terminations}')
+
# Remote Terminations must all be of the same type, otherwise return a split path
if not all(isinstance(t, type(remote_terminations[0])) for t in remote_terminations[1:]):
is_complete = False
is_split = True
+ logger.debug('Remote termination types differ; aborting trace.')
break
# Step 7: Record the far-end termination object(s)
@@ -649,87 +839,89 @@ class CablePath(models.Model):
if isinstance(remote_terminations[0], FrontPort):
# Follow FrontPorts to their corresponding RearPorts
- rear_ports = RearPort.objects.filter(
- pk__in=[t.rear_port_id for t in remote_terminations]
- )
- if len(rear_ports) > 1 or rear_ports[0].positions > 1:
- position_stack.append([fp.rear_port_position for fp in remote_terminations])
-
- terminations = rear_ports
-
- elif isinstance(remote_terminations[0], RearPort):
- if len(remote_terminations) == 1 and remote_terminations[0].positions == 1:
- front_ports = FrontPort.objects.filter(
- rear_port_id__in=[rp.pk for rp in remote_terminations],
- rear_port_position=1
- )
- # Obtain the individual front ports based on the termination and all positions
- elif len(remote_terminations) > 1 and position_stack:
+ if remote_terminations[0].positions > 1 and position_stack:
positions = position_stack.pop()
-
- # Ensure we have a number of positions equal to the amount of remote terminations
- if len(remote_terminations) != len(positions):
- raise UnsupportedCablePath(
- _("All positions counts within the path on opposite ends of links must match")
- )
-
- # Get our front ports
q_filter = Q()
for rt in remote_terminations:
- position = positions.pop()
- q_filter |= Q(rear_port_id=rt.pk, rear_port_position=position)
- if q_filter is Q():
- raise UnsupportedCablePath(_("Remote termination position filter is missing"))
- front_ports = FrontPort.objects.filter(q_filter)
- # Obtain the individual front ports based on the termination and position
- elif position_stack:
- front_ports = FrontPort.objects.filter(
- rear_port_id=remote_terminations[0].pk,
- rear_port_position__in=position_stack.pop()
- )
- # If all rear ports have a single position, we can just get the front ports
- elif all([rp.positions == 1 for rp in remote_terminations]):
- front_ports = FrontPort.objects.filter(rear_port_id__in=[rp.pk for rp in remote_terminations])
-
- if len(front_ports) != len(remote_terminations):
- # Some rear ports does not have a front port
- is_split = True
- break
- else:
- # No position indicated: path has split, so we stop at the RearPorts
+ q_filter |= Q(front_port=rt, front_port_position__in=positions)
+ port_mappings = PortMapping.objects.filter(q_filter)
+ elif remote_terminations[0].positions > 1:
is_split = True
+ logger.debug(
+ 'Encountered front port mapped to multiple rear ports but position stack is empty; aborting '
+ 'trace.'
+ )
+ break
+ else:
+ port_mappings = PortMapping.objects.filter(front_port__in=remote_terminations)
+ if not port_mappings:
break
- terminations = front_ports
+ # Compile the list of RearPorts without duplication or altering their ordering
+ terminations = list(dict.fromkeys(mapping.rear_port for mapping in port_mappings))
+ if any(t.positions > 1 for t in terminations):
+ position_stack.append([mapping.rear_port_position for mapping in port_mappings])
+
+ elif isinstance(remote_terminations[0], RearPort):
+ # Follow RearPorts to their corresponding FrontPorts
+ if remote_terminations[0].positions > 1 and position_stack:
+ positions = position_stack.pop()
+ q_filter = Q()
+ for rt in remote_terminations:
+ q_filter |= Q(rear_port=rt, rear_port_position__in=positions)
+ port_mappings = PortMapping.objects.filter(q_filter)
+ elif remote_terminations[0].positions > 1:
+ is_split = True
+ logger.debug(
+ 'Encountered rear port mapped to multiple front ports but position stack is empty; aborting '
+ 'trace.'
+ )
+ break
+ else:
+ port_mappings = PortMapping.objects.filter(rear_port__in=remote_terminations)
+ if not port_mappings:
+ break
+
+ # Compile the list of FrontPorts without duplication or altering their ordering
+ terminations = list(dict.fromkeys(mapping.front_port for mapping in port_mappings))
+ if any(t.positions > 1 for t in terminations):
+ position_stack.append([mapping.front_port_position for mapping in port_mappings])
elif isinstance(remote_terminations[0], CircuitTermination):
# Follow a CircuitTermination to its corresponding CircuitTermination (A to Z or vice versa)
- if len(remote_terminations) > 1:
- is_split = True
+ qs = Q()
+ for remote_termination in remote_terminations:
+ qs |= Q(
+ circuit=remote_termination.circuit,
+ term_side='Z' if remote_termination.term_side == 'A' else 'A'
+ )
+
+ # Get all circuit terminations
+ circuit_terminations = CircuitTermination.objects.filter(qs)
+
+ if not circuit_terminations.exists():
break
- circuit_termination = CircuitTermination.objects.filter(
- circuit=remote_terminations[0].circuit,
- term_side='Z' if remote_terminations[0].term_side == 'A' else 'A'
- ).first()
- if circuit_termination is None:
- break
- elif circuit_termination._provider_network:
+ elif all([ct._provider_network for ct in circuit_terminations]):
# Circuit terminates to a ProviderNetwork
path.extend([
- [object_to_path_node(circuit_termination)],
- [object_to_path_node(circuit_termination._provider_network)],
+ [object_to_path_node(ct) for ct in circuit_terminations],
+ [object_to_path_node(ct._provider_network) for ct in circuit_terminations],
])
is_complete = True
break
- elif circuit_termination.termination and not circuit_termination.cable:
+ elif all([ct.termination and not ct.cable for ct in circuit_terminations]):
# Circuit terminates to a Region/Site/etc.
path.extend([
- [object_to_path_node(circuit_termination)],
- [object_to_path_node(circuit_termination.termination)],
+ [object_to_path_node(ct) for ct in circuit_terminations],
+ [object_to_path_node(ct.termination) for ct in circuit_terminations],
])
break
+ elif any([ct.cable in links for ct in circuit_terminations]):
+ # No valid path
+ is_split = True
+ break
- terminations = [circuit_termination]
+ terminations = circuit_terminations
else:
# Check for non-symmetric path
@@ -741,6 +933,7 @@ class CablePath(models.Model):
# Unsupported topology, mark as split and exit
is_complete = False
is_split = True
+ logger.warning('Encountered an unsupported topology; aborting trace.')
break
return cls(
@@ -819,16 +1012,23 @@ class CablePath(models.Model):
# RearPort splitting to multiple FrontPorts with no stack position
if type(nodes[0]) is RearPort:
- return FrontPort.objects.filter(rear_port__in=nodes)
+ return [
+ mapping.front_port for mapping in
+ PortMapping.objects.filter(rear_port__in=nodes).prefetch_related('front_port')
+ ]
# Cable terminating to multiple FrontPorts mapped to different
# RearPorts connected to different cables
- elif type(nodes[0]) is FrontPort:
- return RearPort.objects.filter(pk__in=[fp.rear_port_id for fp in nodes])
+ if type(nodes[0]) is FrontPort:
+ return [
+ mapping.rear_port for mapping in
+ PortMapping.objects.filter(front_port__in=nodes).prefetch_related('rear_port')
+ ]
# Cable terminating to multiple CircuitTerminations
- elif type(nodes[0]) is CircuitTermination:
+ if type(nodes[0]) is CircuitTermination:
return [
ct.get_peer_termination() for ct in nodes
]
+ return []
def get_asymmetric_nodes(self):
"""
diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py
index e0b05b388..22b48bc45 100644
--- a/netbox/dcim/models/device_component_templates.py
+++ b/netbox/dcim/models/device_component_templates.py
@@ -7,6 +7,8 @@ from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import *
from dcim.constants import *
+from dcim.models.base import PortMappingBase
+from dcim.models.mixins import InterfaceValidationMixin
from netbox.models import ChangeLoggedModel
from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager
@@ -27,6 +29,7 @@ __all__ = (
'InterfaceTemplate',
'InventoryItemTemplate',
'ModuleBayTemplate',
+ 'PortTemplateMapping',
'PowerOutletTemplate',
'PowerPortTemplate',
'RearPortTemplate',
@@ -338,6 +341,10 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
blank=True,
null=True
)
+ color = ColorField(
+ verbose_name=_('color'),
+ blank=True
+ )
power_port = models.ForeignKey(
to='dcim.PowerPortTemplate',
on_delete=models.SET_NULL,
@@ -388,6 +395,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
name=self.resolve_name(kwargs.get('module')),
label=self.resolve_label(kwargs.get('module')),
type=self.type,
+ color=self.color,
power_port=power_port,
feed_leg=self.feed_leg,
**kwargs
@@ -398,6 +406,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
return {
'name': self.name,
'type': self.type,
+ 'color': self.color,
'power_port': self.power_port.name if self.power_port else None,
'feed_leg': self.feed_leg,
'label': self.label,
@@ -405,7 +414,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
}
-class InterfaceTemplate(ModularComponentTemplateModel):
+class InterfaceTemplate(InterfaceValidationMixin, ModularComponentTemplateModel):
"""
A template for a physical data interface on a new Device.
"""
@@ -469,8 +478,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
super().clean()
if self.bridge:
- if self.pk and self.bridge_id == self.pk:
- raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
if self.device_type and self.device_type != self.bridge.device_type:
raise ValidationError({
'bridge': _(
@@ -484,11 +491,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
).format(bridge=self.bridge)
})
- if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
- raise ValidationError({
- 'rf_role': "Wireless role may be set only on wireless interfaces."
- })
-
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@@ -518,6 +520,53 @@ class InterfaceTemplate(ModularComponentTemplateModel):
}
+class PortTemplateMapping(PortMappingBase):
+ """
+ Maps a FrontPortTemplate & position to a RearPortTemplate & position.
+ """
+ device_type = models.ForeignKey(
+ to='dcim.DeviceType',
+ on_delete=models.CASCADE,
+ related_name='port_mappings',
+ blank=True,
+ null=True,
+ )
+ module_type = models.ForeignKey(
+ to='dcim.ModuleType',
+ on_delete=models.CASCADE,
+ related_name='port_mappings',
+ blank=True,
+ null=True,
+ )
+ front_port = models.ForeignKey(
+ to='dcim.FrontPortTemplate',
+ on_delete=models.CASCADE,
+ related_name='mappings',
+ )
+ rear_port = models.ForeignKey(
+ to='dcim.RearPortTemplate',
+ on_delete=models.CASCADE,
+ related_name='mappings',
+ )
+
+ def clean(self):
+ super().clean()
+
+ # Validate rear port assignment
+ if self.front_port.device_type_id != self.rear_port.device_type_id:
+ raise ValidationError({
+ "rear_port": _("Rear port ({rear_port}) must belong to the same device type").format(
+ rear_port=self.rear_port
+ )
+ })
+
+ def save(self, *args, **kwargs):
+ # Associate the mapping with the parent DeviceType/ModuleType
+ self.device_type = self.front_port.device_type
+ self.module_type = self.front_port.module_type
+ super().save(*args, **kwargs)
+
+
class FrontPortTemplate(ModularComponentTemplateModel):
"""
Template for a pass-through port on the front of a new Device.
@@ -531,18 +580,13 @@ class FrontPortTemplate(ModularComponentTemplateModel):
verbose_name=_('color'),
blank=True
)
- rear_port = models.ForeignKey(
- to='dcim.RearPortTemplate',
- on_delete=models.CASCADE,
- related_name='frontport_templates'
- )
- rear_port_position = models.PositiveSmallIntegerField(
- verbose_name=_('rear port position'),
+ positions = models.PositiveSmallIntegerField(
+ verbose_name=_('positions'),
default=1,
validators=[
- MinValueValidator(REARPORT_POSITIONS_MIN),
- MaxValueValidator(REARPORT_POSITIONS_MAX)
- ]
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX)
+ ],
)
component_model = FrontPort
@@ -557,10 +601,6 @@ class FrontPortTemplate(ModularComponentTemplateModel):
fields=('module_type', 'name'),
name='%(app_label)s_%(class)s_unique_module_type_name'
),
- models.UniqueConstraint(
- fields=('rear_port', 'rear_port_position'),
- name='%(app_label)s_%(class)s_unique_rear_port_position'
- ),
)
verbose_name = _('front port template')
verbose_name_plural = _('front port templates')
@@ -568,40 +608,23 @@ class FrontPortTemplate(ModularComponentTemplateModel):
def clean(self):
super().clean()
- try:
-
- # Validate rear port assignment
- if self.rear_port.device_type != self.device_type:
- raise ValidationError(
- _("Rear port ({name}) must belong to the same device type").format(name=self.rear_port)
- )
-
- # Validate rear port position assignment
- if self.rear_port_position > self.rear_port.positions:
- raise ValidationError(
- _("Invalid rear port position ({position}); rear port {name} has only {count} positions").format(
- position=self.rear_port_position,
- name=self.rear_port.name,
- count=self.rear_port.positions
- )
- )
-
- except RearPortTemplate.DoesNotExist:
- pass
+ # Check that positions is greater than or equal to the number of associated RearPortTemplates
+ if not self._state.adding:
+ mapping_count = self.mappings.count()
+ if self.positions < mapping_count:
+ raise ValidationError({
+ "positions": _(
+ "The number of positions cannot be less than the number of mapped rear port templates ({count})"
+ ).format(count=mapping_count)
+ })
def instantiate(self, **kwargs):
- if self.rear_port:
- rear_port_name = self.rear_port.resolve_name(kwargs.get('module'))
- rear_port = RearPort.objects.get(name=rear_port_name, **kwargs)
- else:
- rear_port = None
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
label=self.resolve_label(kwargs.get('module')),
type=self.type,
color=self.color,
- rear_port=rear_port,
- rear_port_position=self.rear_port_position,
+ positions=self.positions,
**kwargs
)
instantiate.do_not_call_in_templates = True
@@ -611,8 +634,7 @@ class FrontPortTemplate(ModularComponentTemplateModel):
'name': self.name,
'type': self.type,
'color': self.color,
- 'rear_port': self.rear_port.name,
- 'rear_port_position': self.rear_port_position,
+ 'positions': self.positions,
'label': self.label,
'description': self.description,
}
@@ -635,9 +657,9 @@ class RearPortTemplate(ModularComponentTemplateModel):
verbose_name=_('positions'),
default=1,
validators=[
- MinValueValidator(REARPORT_POSITIONS_MIN),
- MaxValueValidator(REARPORT_POSITIONS_MAX)
- ]
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX)
+ ],
)
component_model = RearPort
@@ -646,6 +668,20 @@ class RearPortTemplate(ModularComponentTemplateModel):
verbose_name = _('rear port template')
verbose_name_plural = _('rear port templates')
+ def clean(self):
+ super().clean()
+
+ # Check that positions is greater than or equal to the number of associated FrontPortTemplates
+ if not self._state.adding:
+ mapping_count = self.mappings.count()
+ if self.positions < mapping_count:
+ raise ValidationError({
+ "positions": _(
+ "The number of positions cannot be less than the number of mapped front port templates "
+ "({count})"
+ ).format(count=mapping_count)
+ })
+
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@@ -687,8 +723,8 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
def instantiate(self, **kwargs):
return self.component_model(
- name=self.name,
- label=self.label,
+ name=self.resolve_name(kwargs.get('module')),
+ label=self.resolve_label(kwargs.get('module')),
position=self.position,
**kwargs
)
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index f1e460d77..0b96cc0f7 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -1,6 +1,7 @@
from functools import cached_property
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
+from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
@@ -11,8 +12,11 @@ from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import *
from dcim.constants import *
from dcim.fields import WWNField
+from dcim.models.base import PortMappingBase
+from dcim.models.mixins import InterfaceValidationMixin
from netbox.choices import ColorChoices
from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models.mixins import OwnerMixin
from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface
@@ -33,13 +37,14 @@ __all__ = (
'InventoryItemRole',
'ModuleBay',
'PathEndpoint',
+ 'PortMapping',
'PowerOutlet',
'PowerPort',
'RearPort',
)
-class ComponentModel(NetBoxModel):
+class ComponentModel(OwnerMixin, NetBoxModel):
"""
An abstract model inherited by any model which has a parent Device.
"""
@@ -173,6 +178,24 @@ class CabledObjectModel(models.Model):
blank=True,
null=True
)
+ cable_connector = models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=(
+ MinValueValidator(CABLE_CONNECTOR_MIN),
+ MaxValueValidator(CABLE_CONNECTOR_MAX)
+ ),
+ )
+ cable_positions = ArrayField(
+ base_field=models.PositiveSmallIntegerField(
+ validators=(
+ MinValueValidator(CABLE_POSITION_MIN),
+ MaxValueValidator(CABLE_POSITION_MAX)
+ )
+ ),
+ blank=True,
+ null=True,
+ )
mark_connected = models.BooleanField(
verbose_name=_('mark connected'),
default=False,
@@ -192,18 +215,36 @@ class CabledObjectModel(models.Model):
def clean(self):
super().clean()
- if self.cable and not self.cable_end:
- raise ValidationError({
- "cable_end": _("Must specify cable end (A or B) when attaching a cable.")
- })
- if self.cable_end and not self.cable:
- raise ValidationError({
- "cable_end": _("Cable end must not be set without a cable.")
- })
- if self.mark_connected and self.cable:
- raise ValidationError({
- "mark_connected": _("Cannot mark as connected with a cable attached.")
- })
+ if self.cable:
+ if not self.cable_end:
+ raise ValidationError({
+ "cable_end": _("Must specify cable end (A or B) when attaching a cable.")
+ })
+ if self.cable_connector and not self.cable_positions:
+ raise ValidationError({
+ "cable_positions": _("Must specify position(s) when specifying a cable connector.")
+ })
+ if self.cable_positions and not self.cable_connector:
+ raise ValidationError({
+ "cable_positions": _("Cable positions cannot be set without a cable connector.")
+ })
+ if self.mark_connected:
+ raise ValidationError({
+ "mark_connected": _("Cannot mark as connected with a cable attached.")
+ })
+ else:
+ if self.cable_end:
+ raise ValidationError({
+ "cable_end": _("Cable end must not be set without a cable.")
+ })
+ if self.cable_connector:
+ raise ValidationError({
+ "cable_connector": _("Cable connector must not be set without a cable.")
+ })
+ if self.cable_positions:
+ raise ValidationError({
+ "cable_positions": _("Cable termination positions must not be set without a cable.")
+ })
@property
def link(self):
@@ -238,6 +279,22 @@ class CabledObjectModel(models.Model):
return None
return CableEndChoices.SIDE_A if self.cable_end == CableEndChoices.SIDE_B else CableEndChoices.SIDE_B
+ def set_cable_termination(self, termination):
+ """Save attributes from the given CableTermination on the terminating object."""
+ self.cable = termination.cable
+ self.cable_end = termination.cable_end
+ self.cable_connector = termination.connector
+ self.cable_positions = termination.positions
+ set_cable_termination.alters_data = True
+
+ def clear_cable_termination(self, termination):
+ """Clear all cable termination attributes from the terminating object."""
+ self.cable = None
+ self.cable_end = None
+ self.cable_connector = None
+ self.cable_positions = None
+ clear_cable_termination.alters_data = True
+
class PathEndpoint(models.Model):
"""
@@ -632,10 +689,17 @@ class BaseInterface(models.Model):
})
# Check that the primary MAC address (if any) is assigned to this interface
- if self.primary_mac_address and self.primary_mac_address.assigned_object != self:
+ if (
+ self.primary_mac_address and
+ self.primary_mac_address.assigned_object is not None and
+ self.primary_mac_address.assigned_object != self
+ ):
raise ValidationError({
- 'primary_mac_address': _("MAC address {mac_address} is not assigned to this interface.").format(
- mac_address=self.primary_mac_address
+ 'primary_mac_address': _(
+ "MAC address {mac_address} is assigned to a different interface ({interface})."
+ ).format(
+ mac_address=self.primary_mac_address,
+ interface=self.primary_mac_address.assigned_object,
)
})
@@ -669,7 +733,14 @@ class BaseInterface(models.Model):
return self.primary_mac_address.mac_address
-class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin):
+class Interface(
+ InterfaceValidationMixin,
+ ModularComponentModel,
+ BaseInterface,
+ CabledObjectModel,
+ PathEndpoint,
+ TrackingModelMixin,
+):
"""
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
"""
@@ -872,25 +943,21 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
"The selected parent interface ({interface}) belongs to a different device ({device})"
).format(interface=self.parent, device=self.parent.device)
})
- elif self.parent.device.virtual_chassis != self.parent.virtual_chassis:
+ elif self.parent.device.virtual_chassis != self.device.virtual_chassis:
raise ValidationError({
'parent': _(
"The selected parent interface ({interface}) belongs to {device}, which is not part of "
"virtual chassis {virtual_chassis}."
).format(
interface=self.parent,
- device=self.parent_device,
+ device=self.parent.device,
virtual_chassis=self.device.virtual_chassis
)
})
# Bridge validation
- # An interface cannot be bridged to itself
- if self.pk and self.bridge_id == self.pk:
- raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
-
- # A bridged interface belong to the same device or virtual chassis
+ # A bridged interface belongs to the same device or virtual chassis
if self.bridge and self.bridge.device != self.device:
if self.device.virtual_chassis is None:
raise ValidationError({
@@ -935,29 +1002,9 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
)
})
- # PoE validation
-
- # Only physical interfaces may have a PoE mode/type assigned
- if self.poe_mode and self.is_virtual:
- raise ValidationError({
- 'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
- })
- if self.poe_type and self.is_virtual:
- raise ValidationError({
- 'poe_type': _("Virtual interfaces cannot have a PoE type.")
- })
-
- # An interface with a PoE type set must also specify a mode
- if self.poe_type and not self.poe_mode:
- raise ValidationError({
- 'poe_type': _("Must specify PoE mode when designating a PoE type.")
- })
-
# Wireless validation
- # RF role & channel may only be set for wireless interfaces
- if self.rf_role and not self.is_wireless:
- raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")})
+ # RF channel may only be set for wireless interfaces
if self.rf_channel and not self.is_wireless:
raise ValidationError({'rf_channel': _("Channel may be set only on wireless interfaces.")})
@@ -1059,6 +1106,43 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
# Pass-through ports
#
+class PortMapping(PortMappingBase):
+ """
+ Maps a FrontPort & position to a RearPort & position.
+ """
+ device = models.ForeignKey(
+ to='dcim.Device',
+ on_delete=models.CASCADE,
+ related_name='port_mappings',
+ )
+ front_port = models.ForeignKey(
+ to='dcim.FrontPort',
+ on_delete=models.CASCADE,
+ related_name='mappings',
+ )
+ rear_port = models.ForeignKey(
+ to='dcim.RearPort',
+ on_delete=models.CASCADE,
+ related_name='mappings',
+ )
+
+ def clean(self):
+ super().clean()
+
+ # Both ports must belong to the same device
+ if self.front_port.device_id != self.rear_port.device_id:
+ raise ValidationError({
+ "rear_port": _("Rear port ({rear_port}) must belong to the same device").format(
+ rear_port=self.rear_port
+ )
+ })
+
+ def save(self, *args, **kwargs):
+ # Associate the mapping with the parent Device
+ self.device = self.front_port.device
+ super().save(*args, **kwargs)
+
+
class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
"""
A pass-through port on the front of a Device.
@@ -1072,22 +1156,16 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
verbose_name=_('color'),
blank=True
)
- rear_port = models.ForeignKey(
- to='dcim.RearPort',
- on_delete=models.CASCADE,
- related_name='frontports'
- )
- rear_port_position = models.PositiveSmallIntegerField(
- verbose_name=_('rear port position'),
+ positions = models.PositiveSmallIntegerField(
+ verbose_name=_('positions'),
default=1,
validators=[
- MinValueValidator(REARPORT_POSITIONS_MIN),
- MaxValueValidator(REARPORT_POSITIONS_MAX)
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX)
],
- help_text=_('Mapped position on corresponding rear port')
)
- clone_fields = ('device', 'type', 'color')
+ clone_fields = ('device', 'type', 'color', 'positions')
class Meta(ModularComponentModel.Meta):
constraints = (
@@ -1095,10 +1173,6 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
fields=('device', 'name'),
name='%(app_label)s_%(class)s_unique_device_name'
),
- models.UniqueConstraint(
- fields=('rear_port', 'rear_port_position'),
- name='%(app_label)s_%(class)s_unique_rear_port_position'
- ),
)
verbose_name = _('front port')
verbose_name_plural = _('front ports')
@@ -1106,27 +1180,14 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
def clean(self):
super().clean()
- if hasattr(self, 'rear_port'):
-
- # Validate rear port assignment
- if self.rear_port.device != self.device:
+ # Check that positions is greater than or equal to the number of associated RearPorts
+ if not self._state.adding:
+ mapping_count = self.mappings.count()
+ if self.positions < mapping_count:
raise ValidationError({
- "rear_port": _(
- "Rear port ({rear_port}) must belong to the same device"
- ).format(rear_port=self.rear_port)
- })
-
- # Validate rear port position assignment
- if self.rear_port_position > self.rear_port.positions:
- raise ValidationError({
- "rear_port_position": _(
- "Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} "
- "positions."
- ).format(
- rear_port_position=self.rear_port_position,
- name=self.rear_port.name,
- positions=self.rear_port.positions
- )
+ "positions": _(
+ "The number of positions cannot be less than the number of mapped rear ports ({count})"
+ ).format(count=mapping_count)
})
@@ -1147,11 +1208,11 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
verbose_name=_('positions'),
default=1,
validators=[
- MinValueValidator(REARPORT_POSITIONS_MIN),
- MaxValueValidator(REARPORT_POSITIONS_MAX)
+ MinValueValidator(PORT_POSITION_MIN),
+ MaxValueValidator(PORT_POSITION_MAX)
],
- help_text=_('Number of front ports which may be mapped')
)
+
clone_fields = ('device', 'type', 'color', 'positions')
class Meta(ModularComponentModel.Meta):
@@ -1163,13 +1224,13 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
# Check that positions count is greater than or equal to the number of associated FrontPorts
if not self._state.adding:
- frontport_count = self.frontports.count()
- if self.positions < frontport_count:
+ mapping_count = self.mappings.count()
+ if self.positions < mapping_count:
raise ValidationError({
"positions": _(
"The number of positions cannot be less than the number of mapped front ports "
- "({frontport_count})"
- ).format(frontport_count=frontport_count)
+ "({count})"
+ ).format(count=mapping_count)
})
diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py
index be93f33b9..601f7b5e8 100644
--- a/netbox/dcim/models/devices.py
+++ b/netbox/dcim/models/devices.py
@@ -1,8 +1,7 @@
import decimal
-import yaml
-
from functools import cached_property
+import yaml
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
@@ -19,14 +18,14 @@ from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
from dcim.fields import MACAddressField
-from dcim.utils import update_interface_bridges
+from dcim.utils import create_port_mappings, update_interface_bridges
from extras.models import ConfigContextModel, CustomField
from extras.querysets import ConfigContextModelQuerySet
from netbox.choices import ColorChoices
from netbox.config import ConfigItem
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
-from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
+from netbox.models.mixins import WeightMixin
from utilities.fields import ColorField, CounterCacheField
from utilities.prefetch import get_prefetchable_fields
from utilities.tracking import TrackingModelMixin
@@ -34,7 +33,6 @@ from .device_components import *
from .mixins import RenderConfigMixin
from .modules import Module
-
__all__ = (
'Device',
'DeviceRole',
@@ -185,6 +183,10 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
to_model='dcim.InventoryItemTemplate',
to_field='device_type'
)
+ device_count = CounterCacheField(
+ to_model='dcim.Device',
+ to_field='device_type'
+ )
clone_fields = (
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
@@ -646,6 +648,10 @@ class Device(
decimal_places=6,
blank=True,
null=True,
+ validators=[
+ MinValueValidator(decimal.Decimal('-90.0')),
+ MaxValueValidator(decimal.Decimal('90.0'))
+ ],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
longitude = models.DecimalField(
@@ -654,6 +660,10 @@ class Device(
decimal_places=6,
blank=True,
null=True,
+ validators=[
+ MinValueValidator(decimal.Decimal('-180.0')),
+ MaxValueValidator(decimal.Decimal('180.0'))
+ ],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
services = GenericRelation(
@@ -997,6 +1007,8 @@ class Device(
self._instantiate_components(self.device_type.interfacetemplates.all())
self._instantiate_components(self.device_type.rearporttemplates.all())
self._instantiate_components(self.device_type.frontporttemplates.all())
+ # Replicate any front/rear port mappings from the DeviceType
+ create_port_mappings(self, self.device_type)
# Disable bulk_create to accommodate MPTT
self._instantiate_components(self.device_type.modulebaytemplates.all(), bulk_create=False)
self._instantiate_components(self.device_type.devicebaytemplates.all())
@@ -1154,7 +1166,6 @@ class VirtualChassis(PrimaryModel):
})
def delete(self, *args, **kwargs):
-
# Check for LAG interfaces split across member chassis
interfaces = Interface.objects.filter(
device__in=self.members.all(),
@@ -1168,6 +1179,13 @@ class VirtualChassis(PrimaryModel):
"interfaces."
).format(self=self, interfaces=InterfaceSpeedChoices))
+ # Clear vc_position and vc_priority on member devices BEFORE calling super().delete()
+ # This must be done here because on_delete=SET_NULL executes before pre_delete signal
+ for device in self.members.all():
+ device.vc_position = None
+ device.vc_priority = None
+ device.save()
+
return super().delete(*args, **kwargs)
@@ -1300,7 +1318,10 @@ class MACAddress(PrimaryModel):
)
class Meta:
- ordering = ('mac_address', 'pk',)
+ ordering = ('mac_address', 'pk')
+ indexes = (
+ models.Index(fields=('assigned_object_type', 'assigned_object_id')),
+ )
verbose_name = _('MAC address')
verbose_name_plural = _('MAC addresses')
diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py
index e9484264c..d05498590 100644
--- a/netbox/dcim/models/mixins.py
+++ b/netbox/dcim/models/mixins.py
@@ -4,8 +4,11 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
+from dcim.constants import VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES
+
__all__ = (
'CachedScopeMixin',
+ 'InterfaceValidationMixin',
'RenderConfigMixin',
)
@@ -87,11 +90,9 @@ class CachedScopeMixin(models.Model):
def clean(self):
if self.scope_type and not (self.scope or self.scope_id):
scope_type = self.scope_type.model_class()
- raise ValidationError({
- 'scope': _(
- "Please select a {scope_type}."
- ).format(scope_type=scope_type._meta.model_name)
- })
+ raise ValidationError(
+ _("Please select a {scope_type}.").format(scope_type=scope_type._meta.model_name)
+ )
super().clean()
def save(self, *args, **kwargs):
@@ -118,3 +119,33 @@ class CachedScopeMixin(models.Model):
self._site = self.scope.site
self._location = self.scope
cache_related_objects.alters_data = True
+
+
+class InterfaceValidationMixin:
+
+ def clean(self):
+ super().clean()
+
+ # An interface cannot be bridged to itself
+ if self.pk and self.bridge_id == self.pk:
+ raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
+
+ # Only physical interfaces may have a PoE mode/type assigned
+ if self.poe_mode and self.type in VIRTUAL_IFACE_TYPES:
+ raise ValidationError({
+ 'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
+ })
+ if self.poe_type and self.type in VIRTUAL_IFACE_TYPES:
+ raise ValidationError({
+ 'poe_type': _("Virtual interfaces cannot have a PoE type.")
+ })
+
+ # An interface with a PoE type set must also specify a mode
+ if self.poe_type and not self.poe_mode:
+ raise ValidationError({
+ 'poe_type': _("Must specify PoE mode when designating a PoE type.")
+ })
+
+ # RF role may be set only for wireless interfaces
+ if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
+ raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")})
diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py
index 4376f40aa..64f349a29 100644
--- a/netbox/dcim/models/modules.py
+++ b/netbox/dcim/models/modules.py
@@ -7,14 +7,15 @@ from django.utils.translation import gettext_lazy as _
from jsonschema.exceptions import ValidationError as JSONValidationError
from dcim.choices import *
-from dcim.constants import MODULE_TOKEN
from dcim.utils import update_interface_bridges
from extras.models import ConfigContextModel, CustomField
from netbox.models import PrimaryModel
from netbox.models.features import ImageAttachmentsMixin
from netbox.models.mixins import WeightMixin
+from utilities.fields import CounterCacheField
from utilities.jsonschema import validate_schema
from utilities.string import title
+from utilities.tracking import TrackingModelMixin
from .device_components import *
__all__ = (
@@ -92,6 +93,10 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
null=True,
verbose_name=_('attributes')
)
+ module_count = CounterCacheField(
+ to_model='dcim.Module',
+ to_field='module_type'
+ )
clone_fields = ('profile', 'manufacturer', 'weight', 'weight_unit', 'airflow')
prerequisite_models = (
@@ -186,7 +191,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
return yaml.dump(dict(data), sort_keys=False)
-class Module(PrimaryModel, ConfigContextModel):
+class Module(TrackingModelMixin, PrimaryModel, ConfigContextModel):
"""
A Module represents a field-installable component within a Device which may itself hold multiple device components
(for example, a line card within a chassis switch). Modules are instantiated from ModuleTypes.
@@ -331,7 +336,6 @@ class Module(PrimaryModel, ConfigContextModel):
else:
# ModuleBays must be saved individually for MPTT
for instance in create_instances:
- instance.name = instance.name.replace(MODULE_TOKEN, str(self.module_bay.position))
instance.save()
update_fields = ['module']
diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py
index 02bce2019..d7afb7896 100644
--- a/netbox/dcim/models/racks.py
+++ b/netbox/dcim/models/racks.py
@@ -19,9 +19,11 @@ from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.conversion import to_grams
from utilities.data import array_to_string, drange
-from utilities.fields import ColorField
+from utilities.fields import ColorField, CounterCacheField
+from utilities.tracking import TrackingModelMixin
from .device_components import PowerPort
-from .devices import Device, Module
+from .devices import Device
+from .modules import Module
from .power import PowerFeed
__all__ = (
@@ -144,6 +146,10 @@ class RackType(RackBase):
max_length=100,
unique=True
)
+ rack_count = CounterCacheField(
+ to_model='dcim.Rack',
+ to_field='rack_type'
+ )
clone_fields = (
'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
@@ -234,7 +240,7 @@ class RackRole(OrganizationalModel):
verbose_name_plural = _('rack roles')
-class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
+class Rack(ContactsMixin, ImageAttachmentsMixin, TrackingModelMixin, RackBase):
"""
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
Each Rack is assigned to a Site and (optionally) a Location.
@@ -509,7 +515,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
return [u for u in elevation.values()]
- def get_available_units(self, u_height=1, rack_face=None, exclude=None, ignore_excluded_devices=False):
+ def get_available_units(self, u_height=1.0, rack_face=None, exclude=None, ignore_excluded_devices=False):
"""
Return a list of units within the rack available to accommodate a device of a given U height (default 1).
Optionally exclude one or more devices when calculating empty units (needed when moving a device from one
@@ -581,9 +587,10 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
:param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total
height of the elevation
:param legend_width: Width of the unit legend, in pixels
- :param margin_width: Width of the rigth-hand margin, in pixels
+ :param margin_width: Width of the right-hand margin, in pixels
:param include_images: Embed front/rear device images where available
:param base_url: Base URL for links and images. If none, URLs will be relative.
+ :param highlight_params: Dictionary of parameters to be passed to the RackElevationSVG.render_highlight() method
"""
elevation = RackElevationSVG(
self,
diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py
index 7880a067f..d18c22c7e 100644
--- a/netbox/dcim/models/sites.py
+++ b/netbox/dcim/models/sites.py
@@ -1,5 +1,8 @@
+import decimal
+
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
+from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from timezone_field import TimeZoneField
@@ -210,6 +213,10 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
+ validators=[
+ MinValueValidator(decimal.Decimal('-90.0')),
+ MaxValueValidator(decimal.Decimal('90.0'))
+ ],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)
longitude = models.DecimalField(
@@ -218,6 +225,10 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
+ validators=[
+ MinValueValidator(decimal.Decimal('-180.0')),
+ MaxValueValidator(decimal.Decimal('180.0'))
+ ],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)
diff --git a/netbox/dcim/object_actions.py b/netbox/dcim/object_actions.py
index 67cb188e8..9436a3d4f 100644
--- a/netbox/dcim/object_actions.py
+++ b/netbox/dcim/object_actions.py
@@ -1,4 +1,4 @@
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from netbox.object_actions import ObjectAction
diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py
index 8ef6a1d44..00dcbfad5 100644
--- a/netbox/dcim/search.py
+++ b/netbox/dcim/search.py
@@ -137,6 +137,18 @@ class InventoryItemIndex(SearchIndex):
display_attrs = ('device', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'description')
+@register_search
+class InventoryItemRoleIndex(SearchIndex):
+ model = models.InventoryItemRole
+ fields = (
+ ('name', 100),
+ ('slug', 110),
+ ('description', 500),
+ ('comments', 5000),
+ )
+ display_attrs = ('description',)
+
+
@register_search
class LocationIndex(SearchIndex):
model = models.Location
@@ -157,6 +169,7 @@ class ManufacturerIndex(SearchIndex):
('name', 100),
('slug', 110),
('description', 500),
+ ('comments', 5000),
)
display_attrs = ('description',)
@@ -308,6 +321,7 @@ class RackRoleIndex(SearchIndex):
('name', 100),
('slug', 110),
('description', 500),
+ ('comments', 5000),
)
display_attrs = ('description',)
diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py
index c7d3533fb..5ec1f68d7 100644
--- a/netbox/dcim/signals.py
+++ b/netbox/dcim/signals.py
@@ -1,16 +1,18 @@
import logging
-from django.db.models.signals import post_save, post_delete, pre_delete
+from django.db.models import Q
+from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from dcim.choices import CableEndChoices, LinkStatusChoices
+from virtualization.models import VMInterface
from .models import (
Cable, CablePath, CableTermination, ConsolePort, ConsoleServerPort, Device, DeviceBay, FrontPort, Interface,
- InventoryItem, ModuleBay, PathEndpoint, PowerOutlet, PowerPanel, PowerPort, Rack, RearPort, Location,
+ InventoryItem, ModuleBay, PathEndpoint, PortMapping, PowerOutlet, PowerPanel, PowerPort, Rack, RearPort, Location,
VirtualChassis,
)
from .models.cables import trace_paths
-from .utils import create_cablepath, rebuild_paths
+from .utils import create_cablepaths, rebuild_paths
COMPONENT_MODELS = (
ConsolePort,
@@ -84,18 +86,6 @@ def assign_virtualchassis_master(instance, created, **kwargs):
master.save()
-@receiver(pre_delete, sender=VirtualChassis)
-def clear_virtualchassis_members(instance, **kwargs):
- """
- When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
- """
- devices = Device.objects.filter(virtual_chassis=instance.pk)
- for device in devices:
- device.vc_position = None
- device.vc_priority = None
- device.save()
-
-
#
# Cables
#
@@ -125,7 +115,7 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
if not nodes:
continue
if isinstance(nodes[0], PathEndpoint):
- create_cablepath(nodes)
+ create_cablepaths(nodes)
else:
rebuild_paths(nodes)
@@ -146,6 +136,17 @@ def retrace_cable_paths(instance, **kwargs):
cablepath.retrace()
+@receiver((post_delete, post_save), sender=PortMapping)
+def update_passthrough_port_paths(instance, **kwargs):
+ """
+ When a PortMapping is created or deleted, retrace any CablePaths which traverse its front and/or rear ports.
+ """
+ for cablepath in CablePath.objects.filter(
+ Q(_nodes__contains=instance.front_port) | Q(_nodes__contains=instance.rear_port)
+ ):
+ cablepath.retrace()
+
+
@receiver(post_delete, sender=CableTermination)
def nullify_connected_endpoints(instance, **kwargs):
"""
@@ -161,12 +162,13 @@ def nullify_connected_endpoints(instance, **kwargs):
cablepath.retrace()
-@receiver(post_save, sender=FrontPort)
-def extend_rearport_cable_paths(instance, created, raw, **kwargs):
+@receiver(post_save, sender=Interface)
+@receiver(post_save, sender=VMInterface)
+def update_mac_address_interface(instance, created, raw, **kwargs):
"""
- When a new FrontPort is created, add it to any CablePaths which end at its corresponding RearPort.
+ When creating a new Interface or VMInterface, check whether a MACAddress has been designated as its primary. If so,
+ assign the MACAddress to the interface.
"""
- if created and not raw:
- rearport = instance.rear_port
- for cablepath in CablePath.objects.filter(_nodes__contains=rearport):
- cablepath.retrace()
+ if created and not raw and instance.primary_mac_address:
+ instance.primary_mac_address.assigned_object = instance
+ instance.primary_mac_address.save()
diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py
index 321eb79f5..72220591e 100644
--- a/netbox/dcim/tables/cables.py
+++ b/netbox/dcim/tables/cables.py
@@ -1,11 +1,11 @@
-from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
-from django_tables2.utils import Accessor
from django.utils.html import escape
from django.utils.safestring import mark_safe
+from django.utils.translation import gettext_lazy as _
+from django_tables2.utils import Accessor
from dcim.models import Cable
-from netbox.tables import NetBoxTable, columns
+from netbox.tables import PrimaryModelTable, columns
from tenancy.tables import TenancyColumnsMixin
from .template_code import CABLE_LENGTH
@@ -48,7 +48,7 @@ class CableTerminationsColumn(tables.Column):
# Cables
#
-class CableTable(TenancyColumnsMixin, NetBoxTable):
+class CableTable(TenancyColumnsMixin, PrimaryModelTable):
a_terminations = CableTerminationsColumn(
cable_end='A',
orderable=False,
@@ -108,6 +108,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
verbose_name=_('Site B')
)
status = columns.ChoiceFieldColumn()
+ profile = columns.ChoiceFieldColumn()
length = columns.TemplateColumn(
template_code=CABLE_LENGTH,
order_by=('_abs_length')
@@ -117,17 +118,16 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
verbose_name=_('Color Name'),
orderable=False
)
- comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='dcim:cable_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = Cable
fields = (
'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b',
- 'location_a', 'location_b', 'site_a', 'site_b', 'status', 'type', 'tenant', 'tenant_group', 'color',
- 'color_name', 'length', 'description', 'comments', 'tags', 'created', 'last_updated',
+ 'location_a', 'location_b', 'site_a', 'site_b', 'status', 'profile', 'type', 'tenant', 'tenant_group',
+ 'color', 'color_name', 'length', 'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'status', 'type',
diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py
index 8287e3666..f01a3ed2f 100644
--- a/netbox/dcim/tables/devices.py
+++ b/netbox/dcim/tables/devices.py
@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
from django_tables2.utils import Accessor
from dcim import models
-from netbox.tables import NetBoxTable, columns
+from netbox.tables import NestedGroupModelTable, NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
from .template_code import *
@@ -58,15 +58,7 @@ MACADDRESS_COPY_BUTTON = """
# Device roles
#
-class DeviceRoleTable(NetBoxTable):
- name = columns.MPTTColumn(
- verbose_name=_('Name'),
- linkify=True
- )
- parent = tables.Column(
- verbose_name=_('Parent'),
- linkify=True,
- )
+class DeviceRoleTable(NestedGroupModelTable):
device_count = columns.LinkedCountColumn(
viewname='dcim:device_list',
url_params={'role_id': 'pk'},
@@ -89,7 +81,7 @@ class DeviceRoleTable(NetBoxTable):
url_name='dcim:devicerole_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(NestedGroupModelTable.Meta):
model = models.DeviceRole
fields = (
'pk', 'id', 'name', 'parent', 'device_count', 'vm_count', 'color', 'vm_role', 'config_template',
@@ -102,15 +94,7 @@ class DeviceRoleTable(NetBoxTable):
# Platforms
#
-class PlatformTable(NetBoxTable):
- name = columns.MPTTColumn(
- verbose_name=_('Name'),
- linkify=True
- )
- parent = tables.Column(
- verbose_name=_('Parent'),
- linkify=True,
- )
+class PlatformTable(NestedGroupModelTable):
manufacturer = tables.Column(
verbose_name=_('Manufacturer'),
linkify=True
@@ -133,7 +117,7 @@ class PlatformTable(NetBoxTable):
url_name='dcim:platform_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(NestedGroupModelTable.Meta):
model = models.Platform
fields = (
'pk', 'id', 'name', 'parent', 'manufacturer', 'device_count', 'vm_count', 'slug', 'config_template',
@@ -148,7 +132,7 @@ class PlatformTable(NetBoxTable):
# Devices
#
-class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
+class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
name = tables.TemplateColumn(
verbose_name=_('Name'),
template_code=DEVICE_LINK,
@@ -195,6 +179,11 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
linkify=True,
verbose_name=_('Type')
)
+ u_height = columns.TemplateColumn(
+ accessor=tables.A('device_type__u_height'),
+ verbose_name=_('U Height'),
+ template_code='{{ value|floatformat }}'
+ )
platform = tables.Column(
linkify=True,
verbose_name=_('Platform')
@@ -244,7 +233,6 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
accessor='parent_bay',
linkify=True
)
- comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='dcim:device_list'
)
@@ -279,7 +267,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
verbose_name=_('Inventory items')
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = models.Device
fields = (
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'role', 'manufacturer', 'device_type',
@@ -307,6 +295,16 @@ class DeviceComponentTable(NetBoxTable):
verbose_name=_('Name'),
linkify=True,
)
+ device_location = tables.Column(
+ accessor=tables.A('device__location'),
+ verbose_name=_('Device Location'),
+ linkify=True,
+ )
+ device_site = tables.Column(
+ accessor=tables.A('device__site'),
+ verbose_name=_('Device Site'),
+ linkify=True,
+ )
device_status = columns.ChoiceFieldColumn(
accessor=tables.A('device__status'),
verbose_name=_('Device Status'),
@@ -751,12 +749,9 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
color = columns.ColorColumn(
verbose_name=_('Color'),
)
- rear_port_position = tables.Column(
- verbose_name=_('Position')
- )
- rear_port = tables.Column(
- verbose_name=_('Rear Port'),
- linkify=True
+ mappings = columns.ManyToManyColumn(
+ verbose_name=_('Mappings'),
+ transform=lambda obj: f'{obj.rear_port}:{obj.rear_port_position}'
)
tags = columns.TagColumn(
url_name='dcim:frontport_list'
@@ -765,12 +760,12 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
class Meta(DeviceComponentTable.Meta):
model = models.FrontPort
fields = (
- 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
- 'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer',
- 'inventory_items', 'tags', 'created', 'last_updated',
+ 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'mappings',
+ 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created',
+ 'last_updated',
)
default_columns = (
- 'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
+ 'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'mappings', 'description',
)
@@ -788,11 +783,11 @@ class DeviceFrontPortTable(FrontPortTable):
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
model = models.FrontPort
fields = (
- 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position',
+ 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'mappings',
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
)
default_columns = (
- 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer',
+ 'pk', 'name', 'label', 'type', 'color', 'positions', 'mappings', 'description', 'cable', 'link_peer',
)
@@ -807,6 +802,10 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
color = columns.ColorColumn(
verbose_name=_('Color'),
)
+ mappings = columns.ManyToManyColumn(
+ verbose_name=_('Mappings'),
+ transform=lambda obj: f'{obj.front_port}:{obj.front_port_position}'
+ )
tags = columns.TagColumn(
url_name='dcim:rearport_list'
)
@@ -814,10 +813,13 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
class Meta(DeviceComponentTable.Meta):
model = models.RearPort
fields = (
- 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
- 'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created', 'last_updated',
+ 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'mappings',
+ 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created',
+ 'last_updated',
+ )
+ default_columns = (
+ 'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'mappings', 'description',
)
- default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
class DeviceRearPortTable(RearPortTable):
@@ -834,11 +836,11 @@ class DeviceRearPortTable(RearPortTable):
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
model = models.RearPort
fields = (
- 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected',
- 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
+ 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'mappings',
+ 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
)
default_columns = (
- 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer',
+ 'pk', 'name', 'label', 'type', 'color', 'positions', 'mappings', 'description', 'cable', 'link_peer',
)
@@ -1035,7 +1037,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
)
-class InventoryItemRoleTable(NetBoxTable):
+class InventoryItemRoleTable(OrganizationalModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -1052,10 +1054,10 @@ class InventoryItemRoleTable(NetBoxTable):
url_name='dcim:inventoryitemrole_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(OrganizationalModelTable.Meta):
model = models.InventoryItemRole
fields = (
- 'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
+ 'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'comments', 'tags', 'actions',
)
default_columns = ('pk', 'name', 'inventoryitem_count', 'color', 'description')
@@ -1064,7 +1066,7 @@ class InventoryItemRoleTable(NetBoxTable):
# Virtual chassis
#
-class VirtualChassisTable(NetBoxTable):
+class VirtualChassisTable(PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -1078,14 +1080,11 @@ class VirtualChassisTable(NetBoxTable):
url_params={'virtual_chassis_id': 'pk'},
verbose_name=_('Members')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:virtualchassis_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = models.VirtualChassis
fields = (
'pk', 'id', 'name', 'domain', 'master', 'member_count', 'description', 'comments', 'tags', 'created',
@@ -1094,7 +1093,7 @@ class VirtualChassisTable(NetBoxTable):
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')
-class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
+class VirtualDeviceContextTable(TenancyColumnsMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -1125,14 +1124,11 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
url_params={'vdc_id': 'pk'},
verbose_name=_('Interfaces')
)
-
- comments = columns.MarkdownColumn()
-
tags = columns.TagColumn(
url_name='dcim:virtualdevicecontext_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = models.VirtualDeviceContext
fields = (
'pk', 'id', 'name', 'status', 'identifier', 'tenant', 'tenant_group', 'primary_ip', 'primary_ip4',
@@ -1143,7 +1139,7 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
)
-class MACAddressTable(NetBoxTable):
+class MACAddressTable(PrimaryModelTable):
mac_address = tables.TemplateColumn(
template_code=MACADDRESS_LINK,
verbose_name=_('MAC Address')
@@ -1159,6 +1155,9 @@ class MACAddressTable(NetBoxTable):
orderable=False,
verbose_name=_('Parent')
)
+ is_primary = columns.BooleanColumn(
+ verbose_name=_('Primary')
+ )
tags = columns.TagColumn(
url_name='dcim:macaddress_list'
)
@@ -1166,10 +1165,10 @@ class MACAddressTable(NetBoxTable):
extra_buttons=MACADDRESS_COPY_BUTTON
)
- class Meta(DeviceComponentTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = models.MACAddress
fields = (
- 'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'comments', 'tags',
- 'created', 'last_updated',
+ 'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'is_primary',
+ 'comments', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description')
diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py
index 91f9f3b47..ee11fe787 100644
--- a/netbox/dcim/tables/devicetypes.py
+++ b/netbox/dcim/tables/devicetypes.py
@@ -2,7 +2,7 @@ import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from dcim import models
-from netbox.tables import NetBoxTable, columns
+from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, WEIGHT
@@ -26,7 +26,7 @@ __all__ = (
# Manufacturers
#
-class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
+class ManufacturerTable(ContactsColumnMixin, OrganizationalModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -60,11 +60,12 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
url_name='dcim:manufacturer_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(OrganizationalModelTable.Meta):
model = models.Manufacturer
fields = (
'pk', 'id', 'name', 'racktype_count', 'devicetype_count', 'moduletype_count', 'inventoryitem_count',
- 'platform_count', 'description', 'slug', 'tags', 'contacts', 'actions', 'created', 'last_updated',
+ 'platform_count', 'description', 'slug', 'comments', 'tags', 'contacts', 'actions', 'created',
+ 'last_updated',
)
default_columns = (
'pk', 'name', 'racktype_count', 'devicetype_count', 'moduletype_count', 'inventoryitem_count',
@@ -76,7 +77,7 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
# Device types
#
-class DeviceTypeTable(NetBoxTable):
+class DeviceTypeTable(PrimaryModelTable):
model = tables.Column(
linkify=True,
verbose_name=_('Device Type')
@@ -93,9 +94,6 @@ class DeviceTypeTable(NetBoxTable):
verbose_name=_('Full Depth'),
false_mark=None
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:devicetype_list'
)
@@ -112,10 +110,10 @@ class DeviceTypeTable(NetBoxTable):
template_code=WEIGHT,
order_by=('_abs_weight', 'weight_unit')
)
- instance_count = columns.LinkedCountColumn(
+ device_count = columns.LinkedCountColumn(
viewname='dcim:device_list',
url_params={'device_type_id': 'pk'},
- verbose_name=_('Instances')
+ verbose_name=_('Device Count'),
)
console_port_template_count = tables.Column(
verbose_name=_('Console Ports')
@@ -148,15 +146,15 @@ class DeviceTypeTable(NetBoxTable):
verbose_name=_('Inventory Items')
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = models.DeviceType
fields = (
'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height',
'exclude_from_utilization', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
- 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
+ 'description', 'comments', 'device_count', 'tags', 'created', 'last_updated',
)
default_columns = (
- 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',
+ 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'device_count',
)
@@ -211,6 +209,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
class PowerOutletTemplateTable(ComponentTemplateTable):
+ color = columns.ColorColumn(
+ verbose_name=_('Color'),
+ )
actions = columns.ActionsColumn(
actions=('edit', 'delete'),
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -218,7 +219,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta):
model = models.PowerOutletTemplate
- fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
+ fields = ('pk', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description', 'actions')
empty_text = "None"
@@ -249,12 +250,13 @@ class InterfaceTemplateTable(ComponentTemplateTable):
class FrontPortTemplateTable(ComponentTemplateTable):
- rear_port_position = tables.Column(
- verbose_name=_('Position')
- )
color = columns.ColorColumn(
verbose_name=_('Color'),
)
+ mappings = columns.ManyToManyColumn(
+ verbose_name=_('Mappings'),
+ transform=lambda obj: f'{obj.rear_port}:{obj.rear_port_position}'
+ )
actions = columns.ActionsColumn(
actions=('edit', 'delete'),
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -262,7 +264,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta):
model = models.FrontPortTemplate
- fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions')
+ fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'mappings', 'description', 'actions')
empty_text = "None"
@@ -270,6 +272,10 @@ class RearPortTemplateTable(ComponentTemplateTable):
color = columns.ColorColumn(
verbose_name=_('Color'),
)
+ mappings = columns.ManyToManyColumn(
+ verbose_name=_('Mappings'),
+ transform=lambda obj: f'{obj.front_port}:{obj.front_port_position}'
+ )
actions = columns.ActionsColumn(
actions=('edit', 'delete'),
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -277,7 +283,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta):
model = models.RearPortTemplate
- fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions')
+ fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'mappings', 'description', 'actions')
empty_text = "None"
diff --git a/netbox/dcim/tables/modules.py b/netbox/dcim/tables/modules.py
index 52edea8b4..92f5183b7 100644
--- a/netbox/dcim/tables/modules.py
+++ b/netbox/dcim/tables/modules.py
@@ -1,8 +1,8 @@
-from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
+from django.utils.translation import gettext_lazy as _
from dcim.models import Module, ModuleType, ModuleTypeProfile
-from netbox.tables import NetBoxTable, columns
+from netbox.tables import PrimaryModelTable, columns
from .template_code import MODULETYPEPROFILE_ATTRIBUTES, WEIGHT
__all__ = (
@@ -12,7 +12,7 @@ __all__ = (
)
-class ModuleTypeProfileTable(NetBoxTable):
+class ModuleTypeProfileTable(PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -23,14 +23,11 @@ class ModuleTypeProfileTable(NetBoxTable):
orderable=False,
verbose_name=_('Attributes')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:moduletypeprofile_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = ModuleTypeProfile
fields = (
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
@@ -40,7 +37,7 @@ class ModuleTypeProfileTable(NetBoxTable):
)
-class ModuleTypeTable(NetBoxTable):
+class ModuleTypeTable(PrimaryModelTable):
profile = tables.Column(
verbose_name=_('Profile'),
linkify=True
@@ -59,30 +56,27 @@ class ModuleTypeTable(NetBoxTable):
order_by=('_abs_weight', 'weight_unit')
)
attributes = columns.DictColumn()
- instance_count = columns.LinkedCountColumn(
+ module_count = columns.LinkedCountColumn(
viewname='dcim:module_list',
url_params={'module_type_id': 'pk'},
- verbose_name=_('Instances')
- )
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
+ verbose_name=_('Module Count'),
)
tags = columns.TagColumn(
url_name='dcim:moduletype_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = ModuleType
fields = (
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
- 'attributes', 'comments', 'tags', 'created', 'last_updated',
+ 'attributes', 'module_count', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
- 'pk', 'model', 'profile', 'manufacturer', 'part_number',
+ 'pk', 'model', 'profile', 'manufacturer', 'part_number', 'module_count',
)
-class ModuleTable(NetBoxTable):
+class ModuleTable(PrimaryModelTable):
device = tables.Column(
verbose_name=_('Device'),
linkify=True
@@ -103,14 +97,11 @@ class ModuleTable(NetBoxTable):
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:module_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = Module
fields = (
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py
index 40a58ad81..d7d62ea17 100644
--- a/netbox/dcim/tables/power.py
+++ b/netbox/dcim/tables/power.py
@@ -1,10 +1,9 @@
-from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
+from django.utils.translation import gettext_lazy as _
+
from dcim.models import PowerFeed, PowerPanel
+from netbox.tables import PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
-
-from netbox.tables import NetBoxTable, columns
-
from .devices import CableTerminationTable
__all__ = (
@@ -17,7 +16,7 @@ __all__ = (
# Power panels
#
-class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
+class PowerPanelTable(ContactsColumnMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -35,14 +34,11 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
url_params={'power_panel_id': 'pk'},
verbose_name=_('Power Feeds')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:powerpanel_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = PowerPanel
fields = (
'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'description', 'comments', 'tags',
@@ -57,7 +53,7 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
# We're not using PathEndpointTable for PowerFeed because power connections
# cannot traverse pass-through ports.
-class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable):
+class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -92,14 +88,11 @@ class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable):
linkify=True,
verbose_name=_('Site'),
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:powerfeed_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(CableTerminationTable.Meta, PrimaryModelTable.Meta):
model = PowerFeed
fields = (
'pk', 'id', 'name', 'power_panel', 'site', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage',
diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py
index afb2c44c8..c61d82434 100644
--- a/netbox/dcim/tables/racks.py
+++ b/netbox/dcim/tables/racks.py
@@ -1,9 +1,9 @@
-from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
+from django.utils.translation import gettext_lazy as _
from django_tables2.utils import Accessor
from dcim.models import Rack, RackReservation, RackRole, RackType
-from netbox.tables import NetBoxTable, columns
+from netbox.tables import OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
from .template_code import OUTER_UNIT, WEIGHT
@@ -15,11 +15,7 @@ __all__ = (
)
-#
-# Rack roles
-#
-
-class RackRoleTable(NetBoxTable):
+class RackRoleTable(OrganizationalModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -36,20 +32,16 @@ class RackRoleTable(NetBoxTable):
url_name='dcim:rackrole_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(OrganizationalModelTable.Meta):
model = RackRole
fields = (
- 'pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions', 'created',
+ 'pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'comments', 'tags', 'actions', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'rack_count', 'color', 'description')
-#
-# Rack Types
-#
-
-class RackTypeTable(NetBoxTable):
+class RackTypeTable(PrimaryModelTable):
model = tables.Column(
verbose_name=_('Model'),
linkify=True
@@ -84,35 +76,28 @@ class RackTypeTable(NetBoxTable):
template_code=WEIGHT,
order_by=('_abs_max_weight', 'weight_unit')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
- instance_count = columns.LinkedCountColumn(
+ rack_count = columns.LinkedCountColumn(
viewname='dcim:rack_list',
url_params={'rack_type_id': 'pk'},
- verbose_name=_('Instances')
+ verbose_name=_('Rack Count'),
)
tags = columns.TagColumn(
url_name='dcim:rack_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = RackType
fields = (
'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
- 'outer_height', 'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description',
- 'comments', 'instance_count', 'tags', 'created', 'last_updated',
+ 'outer_height', 'outer_depth', 'mounting_depth', 'weight', 'max_weight', 'description', 'comments',
+ 'rack_count', 'tags', 'created', 'last_updated',
)
default_columns = (
- 'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'instance_count',
+ 'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'rack_count',
)
-#
-# Racks
-#
-
-class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
+class RackTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -144,9 +129,6 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
template_code="{{ value }}U",
verbose_name=_('Height')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
device_count = columns.LinkedCountColumn(
viewname='dcim:device_list',
url_params={'rack_id': 'pk'},
@@ -186,7 +168,7 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
order_by=('_abs_max_weight', 'weight_unit')
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = Rack
fields = (
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role',
@@ -201,11 +183,7 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
)
-#
-# Rack reservations
-#
-
-class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
+class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
reservation = tables.Column(
verbose_name=_('Reservation'),
accessor='pk',
@@ -232,14 +210,11 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:rackreservation_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = RackReservation
fields = (
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'status', 'user', 'created', 'tenant',
diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py
index 0f8fb9372..544fb3cf8 100644
--- a/netbox/dcim/tables/sites.py
+++ b/netbox/dcim/tables/sites.py
@@ -1,10 +1,9 @@
-from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
+from django.utils.translation import gettext_lazy as _
+
from dcim.models import Location, Region, Site, SiteGroup
+from netbox.tables import NestedGroupModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
-
-from netbox.tables import NetBoxTable, columns
-
from .template_code import LOCATION_BUTTONS
__all__ = (
@@ -15,19 +14,7 @@ __all__ = (
)
-#
-# Regions
-#
-
-class RegionTable(ContactsColumnMixin, NetBoxTable):
- name = columns.MPTTColumn(
- verbose_name=_('Name'),
- linkify=True
- )
- parent = tables.Column(
- verbose_name=_('Parent'),
- linkify=True,
- )
+class RegionTable(ContactsColumnMixin, NestedGroupModelTable):
site_count = columns.LinkedCountColumn(
viewname='dcim:site_list',
url_params={'region_id': 'pk'},
@@ -36,11 +23,8 @@ class RegionTable(ContactsColumnMixin, NetBoxTable):
tags = columns.TagColumn(
url_name='dcim:region_list'
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
- class Meta(NetBoxTable.Meta):
+ class Meta(NestedGroupModelTable.Meta):
model = Region
fields = (
'pk', 'id', 'name', 'parent', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
@@ -49,19 +33,7 @@ class RegionTable(ContactsColumnMixin, NetBoxTable):
default_columns = ('pk', 'name', 'site_count', 'description')
-#
-# Site groups
-#
-
-class SiteGroupTable(ContactsColumnMixin, NetBoxTable):
- name = columns.MPTTColumn(
- verbose_name=_('Name'),
- linkify=True
- )
- parent = tables.Column(
- verbose_name=_('Parent'),
- linkify=True,
- )
+class SiteGroupTable(ContactsColumnMixin, NestedGroupModelTable):
site_count = columns.LinkedCountColumn(
viewname='dcim:site_list',
url_params={'group_id': 'pk'},
@@ -70,11 +42,8 @@ class SiteGroupTable(ContactsColumnMixin, NetBoxTable):
tags = columns.TagColumn(
url_name='dcim:sitegroup_list'
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
- class Meta(NetBoxTable.Meta):
+ class Meta(NestedGroupModelTable.Meta):
model = SiteGroup
fields = (
'pk', 'id', 'name', 'parent', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
@@ -83,11 +52,7 @@ class SiteGroupTable(ContactsColumnMixin, NetBoxTable):
default_columns = ('pk', 'name', 'site_count', 'description')
-#
-# Sites
-#
-
-class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
+class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -117,14 +82,11 @@ class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
url_params={'site_id': 'pk'},
verbose_name=_('Devices')
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
tags = columns.TagColumn(
url_name='dcim:site_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = Site
fields = (
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'tenant_group', 'asns',
@@ -134,19 +96,7 @@ class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'description')
-#
-# Locations
-#
-
-class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
- name = columns.MPTTColumn(
- verbose_name=_('Name'),
- linkify=True
- )
- parent = tables.Column(
- verbose_name=_('Parent'),
- linkify=True,
- )
+class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NestedGroupModelTable):
site = tables.Column(
verbose_name=_('Site'),
linkify=True
@@ -175,11 +125,8 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
actions = columns.ActionsColumn(
extra_buttons=LOCATION_BUTTONS
)
- comments = columns.MarkdownColumn(
- verbose_name=_('Comments'),
- )
- class Meta(NetBoxTable.Meta):
+ class Meta(NestedGroupModelTable.Meta):
model = Location
fields = (
'pk', 'id', 'name', 'parent', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count',
diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py
index 6a819a3c0..3bda18755 100644
--- a/netbox/dcim/tests/test_api.py
+++ b/netbox/dcim/tests/test_api.py
@@ -13,7 +13,8 @@ from ipam.choices import VLANQinQRoleChoices
from ipam.models import ASN, RIR, VLAN, VRF
from netbox.api.serializers import GenericObjectSerializer
from tenancy.models import Tenant
-from users.models import User
+from users.constants import TOKEN_PREFIX
+from users.models import Token, User
from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_logging
from virtualization.models import Cluster, ClusterType
from wireless.choices import WirelessChannelChoices
@@ -316,7 +317,7 @@ class RackRoleTest(APIViewTestCases.APIViewTestCase):
class RackTypeTest(APIViewTestCases.APIViewTestCase):
model = RackType
- brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'slug', 'url']
+ brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'rack_count', 'slug', 'url']
bulk_update_data = {
'description': 'new description',
}
@@ -609,7 +610,7 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
model = ModuleType
- brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'profile', 'url']
+ brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'module_count', 'profile', 'url']
bulk_update_data = {
'part_number': 'ABC123',
}
@@ -972,72 +973,99 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 4', type=PortTypeChoices.TYPE_8P8C),
- RearPortTemplate(module_type=moduletype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C),
- RearPortTemplate(module_type=moduletype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C),
- RearPortTemplate(module_type=moduletype, name='Rear Port Template 7', type=PortTypeChoices.TYPE_8P8C),
- RearPortTemplate(module_type=moduletype, name='Rear Port Template 8', type=PortTypeChoices.TYPE_8P8C),
+ RearPortTemplate(device_type=devicetype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C),
+ RearPortTemplate(device_type=devicetype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C),
)
RearPortTemplate.objects.bulk_create(rear_port_templates)
-
front_port_templates = (
- FrontPortTemplate(
- device_type=devicetype,
- name='Front Port Template 1',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_port_templates[0]
- ),
- FrontPortTemplate(
- device_type=devicetype,
- name='Front Port Template 2',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_port_templates[1]
- ),
- FrontPortTemplate(
- module_type=moduletype,
- name='Front Port Template 5',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_port_templates[4]
- ),
- FrontPortTemplate(
- module_type=moduletype,
- name='Front Port Template 6',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_port_templates[5]
- ),
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 2', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=moduletype, name='Front Port Template 3', type=PortTypeChoices.TYPE_8P8C),
)
FrontPortTemplate.objects.bulk_create(front_port_templates)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(
+ device_type=devicetype,
+ front_port=front_port_templates[0],
+ rear_port=rear_port_templates[0],
+ ),
+ PortTemplateMapping(
+ device_type=devicetype,
+ front_port=front_port_templates[1],
+ rear_port=rear_port_templates[1],
+ ),
+ PortTemplateMapping(
+ module_type=moduletype,
+ front_port=front_port_templates[2],
+ rear_port=rear_port_templates[2],
+ ),
+ ])
cls.create_data = [
{
'device_type': devicetype.pk,
'name': 'Front Port Template 3',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_port_templates[2].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_port_templates[3].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
{
'device_type': devicetype.pk,
'name': 'Front Port Template 4',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_port_templates[3].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_port_templates[4].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
{
'module_type': moduletype.pk,
'name': 'Front Port Template 7',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_port_templates[6].pk,
- 'rear_port_position': 1,
- },
- {
- 'module_type': moduletype.pk,
- 'name': 'Front Port Template 8',
- 'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_port_templates[7].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_port_templates[5].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
]
+ cls.update_data = {
+ 'type': PortTypeChoices.TYPE_LC,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_port_templates[3].pk,
+ 'rear_port_position': 1,
+ },
+ ],
+ }
+
+ def test_update_object(self):
+ super().test_update_object()
+
+ # Check that the port mapping was updated after modifying the front port template
+ front_port_template = FrontPortTemplate.objects.get(name='Front Port Template 1')
+ rear_port_template = RearPortTemplate.objects.get(name='Rear Port Template 4')
+ self.assertTrue(
+ PortTemplateMapping.objects.filter(
+ front_port=front_port_template,
+ front_port_position=1,
+ rear_port=rear_port_template,
+ rear_port_position=1,
+ ).exists()
+ )
+
class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
model = RearPortTemplate
@@ -1056,36 +1084,104 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
manufacturer=manufacturer, model='Module Type 1'
)
+ front_port_templates = (
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 2', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=moduletype, name='Front Port Template 3', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=moduletype, name='Front Port Template 4', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=moduletype, name='Front Port Template 5', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=moduletype, name='Front Port Template 6', type=PortTypeChoices.TYPE_8P8C),
+ )
+ FrontPortTemplate.objects.bulk_create(front_port_templates)
rear_port_templates = (
RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C),
)
RearPortTemplate.objects.bulk_create(rear_port_templates)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(
+ device_type=devicetype,
+ front_port=front_port_templates[0],
+ rear_port=rear_port_templates[0],
+ ),
+ PortTemplateMapping(
+ device_type=devicetype,
+ front_port=front_port_templates[1],
+ rear_port=rear_port_templates[1],
+ ),
+ PortTemplateMapping(
+ module_type=moduletype,
+ front_port=front_port_templates[2],
+ rear_port=rear_port_templates[2],
+ ),
+ ])
cls.create_data = [
{
'device_type': devicetype.pk,
'name': 'Rear Port Template 4',
'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_port_templates[3].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
{
'device_type': devicetype.pk,
'name': 'Rear Port Template 5',
'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_port_templates[4].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
{
'module_type': moduletype.pk,
'name': 'Rear Port Template 6',
'type': PortTypeChoices.TYPE_8P8C,
- },
- {
- 'module_type': moduletype.pk,
- 'name': 'Rear Port Template 7',
- 'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_port_templates[5].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
]
+ cls.update_data = {
+ 'type': PortTypeChoices.TYPE_LC,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_port_templates[3].pk,
+ 'front_port_position': 1,
+ },
+ ],
+ }
+
+ def test_update_object(self):
+ super().test_update_object()
+
+ # Check that the port mapping was updated after modifying the rear port template
+ front_port_template = FrontPortTemplate.objects.get(name='Front Port Template 4')
+ rear_port_template = RearPortTemplate.objects.get(name='Rear Port Template 1')
+ self.assertTrue(
+ PortTemplateMapping.objects.filter(
+ front_port=front_port_template,
+ front_port_position=1,
+ rear_port=rear_port_template,
+ rear_port_position=1,
+ ).exists()
+ )
+
class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
model = ModuleBayTemplate
@@ -1306,7 +1402,6 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
}
user_permissions = (
'dcim.view_site', 'dcim.view_rack', 'dcim.view_location', 'dcim.view_devicerole', 'dcim.view_devicetype',
- 'extras.view_configtemplate',
)
@classmethod
@@ -1486,12 +1581,58 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
device.config_template = configtemplate
device.save()
- self.add_permissions('dcim.add_device')
- url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) + 'render-config/'
+ self.add_permissions('dcim.render_config_device', 'dcim.view_device')
+ url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
response = self.client.post(url, {}, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(response.data['content'], f'Config for device {device.name}')
+ def test_render_config_without_permission(self):
+ configtemplate = ConfigTemplate.objects.create(
+ name='Config Template 1',
+ template_code='Config for device {{ device.name }}'
+ )
+
+ device = Device.objects.first()
+ device.config_template = configtemplate
+ device.save()
+
+ # No permissions added - user has no render_config permission
+ url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
+ response = self.client.post(url, {}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
+
+ def test_render_config_token_write_enabled(self):
+ configtemplate = ConfigTemplate.objects.create(
+ name='Config Template 1',
+ template_code='Config for device {{ device.name }}'
+ )
+
+ device = Device.objects.first()
+ device.config_template = configtemplate
+ device.save()
+
+ self.add_permissions('dcim.render_config_device', 'dcim.view_device')
+ url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
+
+ # Request without token auth should fail with PermissionDenied
+ response = self.client.post(url, {}, format='json')
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+
+ # Create token with write_enabled=False
+ token = Token.objects.create(version=2, user=self.user, write_enabled=False)
+ token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}'
+
+ # Request with write-disabled token should fail
+ response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+
+ # Enable write and retry
+ token.write_enabled = True
+ token.save()
+ response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+
class ModuleTest(APIViewTestCases.APIViewTestCase):
model = Module
@@ -1969,51 +2110,90 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
RearPort(device=device, name='Rear Port 6', type=PortTypeChoices.TYPE_8P8C),
)
RearPort.objects.bulk_create(rear_ports)
-
front_ports = (
- FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
- FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
- FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[2]),
+ FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C),
)
FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=device, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=device, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortMapping(device=device, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
cls.create_data = [
{
'device': device.pk,
'name': 'Front Port 4',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_ports[3].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_ports[3].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
{
'device': device.pk,
'name': 'Front Port 5',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_ports[4].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_ports[4].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
{
'device': device.pk,
'name': 'Front Port 6',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rear_ports[5].pk,
- 'rear_port_position': 1,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_ports[5].pk,
+ 'rear_port_position': 1,
+ },
+ ],
},
]
+ cls.update_data = {
+ 'type': PortTypeChoices.TYPE_LC,
+ 'rear_ports': [
+ {
+ 'position': 1,
+ 'rear_port': rear_ports[3].pk,
+ 'rear_port_position': 1,
+ },
+ ],
+ }
+
+ def test_update_object(self):
+ super().test_update_object()
+
+ # Check that the port mapping was updated after modifying the front port
+ front_port = FrontPort.objects.get(name='Front Port 1')
+ rear_port = RearPort.objects.get(name='Rear Port 4')
+ self.assertTrue(
+ PortMapping.objects.filter(
+ front_port=front_port,
+ front_port_position=1,
+ rear_port=rear_port,
+ rear_port_position=1,
+ ).exists()
+ )
+
@tag('regression') # Issue #18991
def test_front_port_paths(self):
device = Device.objects.first()
- rear_port = RearPort.objects.create(
- device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C
- )
interface1 = Interface.objects.create(device=device, name='Interface 1')
- front_port = FrontPort.objects.create(
- device=device,
- name='Rear Port 10',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_port,
- )
+ rear_port = RearPort.objects.create(device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C)
+ front_port = FrontPort.objects.create(device=device, name='Front Port 10', type=PortTypeChoices.TYPE_8P8C)
+ PortMapping.objects.create(device=device, front_port=front_port, rear_port=rear_port)
Cable.objects.create(a_terminations=[interface1], b_terminations=[front_port])
self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
@@ -2040,6 +2220,15 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
+ front_ports = (
+ FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 4', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 5', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=device, name='Front Port 6', type=PortTypeChoices.TYPE_8P8C),
+ )
+ FrontPort.objects.bulk_create(front_ports)
rear_ports = (
RearPort(device=device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
RearPort(device=device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
@@ -2052,19 +2241,66 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
'device': device.pk,
'name': 'Rear Port 4',
'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_ports[3].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
{
'device': device.pk,
'name': 'Rear Port 5',
'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_ports[4].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
{
'device': device.pk,
'name': 'Rear Port 6',
'type': PortTypeChoices.TYPE_8P8C,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_ports[5].pk,
+ 'front_port_position': 1,
+ },
+ ],
},
]
+ cls.update_data = {
+ 'type': PortTypeChoices.TYPE_LC,
+ 'front_ports': [
+ {
+ 'position': 1,
+ 'front_port': front_ports[3].pk,
+ 'front_port_position': 1,
+ },
+ ],
+ }
+
+ def test_update_object(self):
+ super().test_update_object()
+
+ # Check that the port mapping was updated after modifying the rear port
+ front_port = FrontPort.objects.get(name='Front Port 4')
+ rear_port = RearPort.objects.get(name='Rear Port 1')
+ self.assertTrue(
+ PortMapping.objects.filter(
+ front_port=front_port,
+ front_port_position=1,
+ rear_port=rear_port,
+ rear_port_position=1,
+ ).exists()
+ )
+
@tag('regression') # Issue #18991
def test_rear_port_paths(self):
device = Device.objects.first()
@@ -2350,6 +2586,7 @@ class CableTest(APIViewTestCases.APIViewTestCase):
'object_id': interfaces[14].pk,
}],
'label': 'Cable 4',
+ 'profile': CableProfileChoices.SINGLE_1C1P,
},
{
'a_terminations': [{
@@ -2361,6 +2598,7 @@ class CableTest(APIViewTestCases.APIViewTestCase):
'object_id': interfaces[15].pk,
}],
'label': 'Cable 5',
+ 'profile': CableProfileChoices.SINGLE_1C1P,
},
{
'a_terminations': [{
@@ -2372,10 +2610,40 @@ class CableTest(APIViewTestCases.APIViewTestCase):
'object_id': interfaces[16].pk,
}],
'label': 'Cable 6',
+ # No profile (legacy behavior)
},
]
+class CableTerminationTest(
+ APIViewTestCases.GetObjectViewTestCase,
+ APIViewTestCases.ListObjectsViewTestCase,
+):
+ model = CableTermination
+ brief_fields = [
+ 'cable', 'cable_end', 'connector', 'display', 'id', 'positions', 'termination_id', 'termination_type', 'url',
+ ]
+
+ @classmethod
+ def setUpTestData(cls):
+ device1 = create_test_device('Device 1')
+ device2 = create_test_device('Device 2')
+
+ interfaces = []
+ for device in (device1, device2):
+ for i in range(0, 10):
+ interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
+ Interface.objects.bulk_create(interfaces)
+
+ cables = (
+ Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[10]], label='Cable 1'),
+ Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[11]], label='Cable 2'),
+ Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[12]], label='Cable 3'),
+ )
+ for cable in cables:
+ cable.save()
+
+
class ConnectedDeviceTest(APITestCase):
@classmethod
diff --git a/netbox/dcim/tests/test_cablepaths.py b/netbox/dcim/tests/test_cablepaths.py
index 399478e70..1bd613e3b 100644
--- a/netbox/dcim/tests/test_cablepaths.py
+++ b/netbox/dcim/tests/test_cablepaths.py
@@ -1,100 +1,21 @@
-from django.test import TestCase
-
from circuits.models import *
from dcim.choices import LinkStatusChoices
from dcim.models import *
from dcim.svg import CableTraceSVG
-from dcim.utils import object_to_path_node
+from dcim.tests.utils import CablePathTestCase
from utilities.exceptions import AbortRequest
-class CablePathTestCase(TestCase):
+class LegacyCablePathTests(CablePathTestCase):
"""
- Test NetBox's ability to trace and retrace CablePaths in response to data model changes. Tests are numbered
- as follows:
+ Test NetBox's ability to trace and retrace CablePaths in response to data model changes, without cable profiles.
+ Tests are numbered as follows:
1XX: Test direct connections between different endpoint types
2XX: Test different cable topologies
3XX: Test responses to changes in existing objects
4XX: Test to exclude specific cable topologies
"""
- @classmethod
- def setUpTestData(cls):
-
- # Create a single device that will hold all components
- cls.site = Site.objects.create(name='Site', slug='site')
-
- manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
- device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device')
- role = DeviceRole.objects.create(name='Device Role', slug='device-role')
- cls.device = Device.objects.create(site=cls.site, device_type=device_type, role=role, name='Test Device')
-
- cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel')
-
- provider = Provider.objects.create(name='Provider', slug='provider')
- circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type')
- cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1')
-
- def _get_cablepath(self, nodes, **kwargs):
- """
- Return a given cable path
-
- :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
-
- :return: The matching CablePath (if any)
- """
- path = []
- for step in nodes:
- if type(step) in (list, tuple):
- path.append([object_to_path_node(node) for node in step])
- else:
- path.append([object_to_path_node(step)])
- return CablePath.objects.filter(path=path, **kwargs).first()
-
- def assertPathExists(self, nodes, **kwargs):
- """
- Assert that a CablePath from origin to destination with a specific intermediate path exists. Returns the
- first matching CablePath, if found.
-
- :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
- """
- cablepath = self._get_cablepath(nodes, **kwargs)
- self.assertIsNotNone(cablepath, msg='CablePath not found')
-
- return cablepath
-
- def assertPathDoesNotExist(self, nodes, **kwargs):
- """
- Assert that a specific CablePath does *not* exist.
-
- :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
- """
- cablepath = self._get_cablepath(nodes, **kwargs)
- self.assertIsNone(cablepath, msg='Unexpected CablePath found')
-
- def assertPathIsSet(self, origin, cablepath, msg=None):
- """
- Assert that a specific CablePath instance is set as the path on the origin.
-
- :param origin: The originating path endpoint
- :param cablepath: The CablePath instance originating from this endpoint
- :param msg: Custom failure message (optional)
- """
- if msg is None:
- msg = f"Path #{cablepath.pk} not set on originating endpoint {origin}"
- self.assertEqual(origin._path_id, cablepath.pk, msg=msg)
-
- def assertPathIsNotSet(self, origin, msg=None):
- """
- Assert that a specific CablePath instance is set as the path on the origin.
-
- :param origin: The originating path endpoint
- :param msg: Custom failure message (optional)
- """
- if msg is None:
- msg = f"Path #{origin._path_id} set as origin on {origin}; should be None!"
- self.assertIsNone(origin._path_id, msg=msg)
-
def test_101_interface_to_interface(self):
"""
[IF1] --C1-- [IF2]
@@ -360,9 +281,14 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1
)
# Create cable 1
@@ -419,9 +345,14 @@ class CablePathTestCase(TestCase):
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1
)
# Create cable 1
@@ -482,18 +413,40 @@ class CablePathTestCase(TestCase):
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1
- )
- frontport2_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1')
+ frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1-2
cable1 = Cable(
@@ -600,18 +553,40 @@ class CablePathTestCase(TestCase):
interface8 = Interface.objects.create(device=self.device, name='Interface 8')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1
- )
- frontport2_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1')
+ frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1-2
cable1 = Cable(
@@ -759,27 +734,59 @@ class CablePathTestCase(TestCase):
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1)
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3')
rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=self.device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 4:1', rear_port=rearport4, rear_port_position=1
- )
- frontport4_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 4:2', rear_port=rearport4, rear_port_position=2
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4_1 = FrontPort.objects.create(device=self.device, name='Front Port 4:1')
+ frontport4_2 = FrontPort.objects.create(device=self.device, name='Front Port 4:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4_1,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4_2,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1-2, 6-7
cable1 = Cable(
@@ -880,30 +887,72 @@ class CablePathTestCase(TestCase):
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=4)
rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1
- )
- frontport2_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
- )
- frontport3_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 3:1', rear_port=rearport3, rear_port_position=1
- )
- frontport3_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 3:2', rear_port=rearport3, rear_port_position=2
- )
- frontport4_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 4:1', rear_port=rearport4, rear_port_position=1
- )
- frontport4_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 4:2', rear_port=rearport4, rear_port_position=2
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1')
+ frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2')
+ frontport3_1 = FrontPort.objects.create(device=self.device, name='Front Port 3:1')
+ frontport3_2 = FrontPort.objects.create(device=self.device, name='Front Port 3:2')
+ frontport4_1 = FrontPort.objects.create(device=self.device, name='Front Port 4:1')
+ frontport4_2 = FrontPort.objects.create(device=self.device, name='Front Port 4:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3_1,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3_2,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4_1,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4_2,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1-3, 6-8
cable1 = Cable(
@@ -1007,23 +1056,50 @@ class CablePathTestCase(TestCase):
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 5', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 5', rear_port=rearport2, rear_port_position=1
- )
- frontport3_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport3, rear_port_position=1
- )
- frontport3_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport3, rear_port_position=2
- )
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=4)
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3_1 = FrontPort.objects.create(device=self.device, name='Front Port 3:1')
+ frontport3_2 = FrontPort.objects.create(device=self.device, name='Front Port 3:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3_1,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3_2,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1-2, 5-6
cable1 = Cable(
@@ -1111,13 +1187,25 @@ class CablePathTestCase(TestCase):
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2)
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ ])
# Create cables 1
cable1 = Cable(
@@ -1177,10 +1265,11 @@ class CablePathTestCase(TestCase):
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2]
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ PortMapping.objects.create(
+ front_port=frontport1, front_port_position=1, rear_port=rearport1, rear_port_position=1,
)
# Create cables
@@ -1492,18 +1581,40 @@ class CablePathTestCase(TestCase):
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport2_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1
- )
- frontport2_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1')
+ frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ ])
circuittermination1 = CircuitTermination.objects.create(
circuit=self.circuit,
termination=self.site,
@@ -1680,22 +1791,44 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1)
- rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=self.device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4 = FrontPort.objects.create(
- device=self.device, name='Front Port 4', rear_port=rearport4, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3')
+ rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 4')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ ])
# Create cables 1-2
cable1 = Cable(
@@ -1767,30 +1900,72 @@ class CablePathTestCase(TestCase):
interface4 = Interface.objects.create(device=self.device, name='Interface 4')
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4)
- frontport1_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:1', rear_port=rearport1, rear_port_position=1
- )
- frontport1_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:2', rear_port=rearport1, rear_port_position=2
- )
- frontport1_3 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:3', rear_port=rearport1, rear_port_position=3
- )
- frontport1_4 = FrontPort.objects.create(
- device=self.device, name='Front Port 1:4', rear_port=rearport1, rear_port_position=4
- )
- frontport2_1 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:1', rear_port=rearport2, rear_port_position=1
- )
- frontport2_2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
- )
- frontport2_3 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:3', rear_port=rearport2, rear_port_position=3
- )
- frontport2_4 = FrontPort.objects.create(
- device=self.device, name='Front Port 2:4', rear_port=rearport2, rear_port_position=4
- )
+ frontport1_1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1')
+ frontport1_2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2')
+ frontport1_3 = FrontPort.objects.create(device=self.device, name='Front Port 1:3')
+ frontport1_4 = FrontPort.objects.create(device=self.device, name='Front Port 1:4')
+ frontport2_1 = FrontPort.objects.create(device=self.device, name='Front Port 2:1')
+ frontport2_2 = FrontPort.objects.create(device=self.device, name='Front Port 2:2')
+ frontport2_3 = FrontPort.objects.create(device=self.device, name='Front Port 2:3')
+ frontport2_4 = FrontPort.objects.create(device=self.device, name='Front Port 2:4')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_3,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=3,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1_4,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=4,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_3,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=3,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2_4,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=4,
+ ),
+ ])
# Create cables 1-2
cable1 = Cable(
@@ -1937,22 +2112,44 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1)
- rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=self.device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4 = FrontPort.objects.create(
- device=self.device, name='Front Port 4', rear_port=rearport4, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3')
+ rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 4')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ ])
cable2 = Cable(
a_terminations=[rearport1],
@@ -2016,22 +2213,44 @@ class CablePathTestCase(TestCase):
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1)
- rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=self.device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4 = FrontPort.objects.create(
- device=self.device, name='Front Port 4', rear_port=rearport4, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3')
+ rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 4')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ ])
cable2 = Cable(
a_terminations=[rearport1],
@@ -2112,30 +2331,62 @@ class CablePathTestCase(TestCase):
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3', positions=1)
- rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4', positions=1)
- rearport5 = RearPort.objects.create(device=self.device, name='Rear Port 5', positions=1)
- rearport6 = RearPort.objects.create(device=self.device, name='Rear Port 6', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=self.device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4 = FrontPort.objects.create(
- device=self.device, name='Front Port 4', rear_port=rearport4, rear_port_position=1
- )
- frontport5 = FrontPort.objects.create(
- device=self.device, name='Front Port 5', rear_port=rearport5, rear_port_position=1
- )
- frontport6 = FrontPort.objects.create(
- device=self.device, name='Front Port 6', rear_port=rearport6, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=self.device, name='Rear Port 3')
+ rearport4 = RearPort.objects.create(device=self.device, name='Rear Port 4')
+ rearport5 = RearPort.objects.create(device=self.device, name='Rear Port 5')
+ rearport6 = RearPort.objects.create(device=self.device, name='Rear Port 6')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 4')
+ frontport5 = FrontPort.objects.create(device=self.device, name='Front Port 5')
+ frontport6 = FrontPort.objects.create(device=self.device, name='Front Port 6')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport5,
+ front_port_position=1,
+ rear_port=rearport5,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport6,
+ front_port_position=1,
+ rear_port=rearport6,
+ rear_port_position=1,
+ ),
+ ])
cable2 = Cable(
a_terminations=[rearport1],
@@ -2234,14 +2485,26 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ ])
cable1 = Cable(
a_terminations=[interface1],
@@ -2270,20 +2533,167 @@ class CablePathTestCase(TestCase):
CableTraceSVG(interface1).render()
CableTraceSVG(interface2).render()
+ def test_223_interface_to_interface_via_multiple_circuit_terminations(self):
+ provider = Provider.objects.first()
+ circuit_type = CircuitType.objects.first()
+ circuit1 = self.circuit
+ circuit2 = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 2')
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
+ circuittermination1_A = CircuitTermination.objects.create(
+ circuit=circuit1,
+ termination=self.site,
+ term_side='A'
+ )
+ circuittermination1_Z = CircuitTermination.objects.create(
+ circuit=circuit1,
+ termination=self.site,
+ term_side='Z'
+ )
+ circuittermination2_A = CircuitTermination.objects.create(
+ circuit=circuit2,
+ termination=self.site,
+ term_side='A'
+ )
+ circuittermination2_Z = CircuitTermination.objects.create(
+ circuit=circuit2,
+ termination=self.site,
+ term_side='Z'
+ )
+
+ # Create cables
+ cable1 = Cable(
+ a_terminations=[interface1],
+ b_terminations=[circuittermination1_A, circuittermination2_A]
+ )
+ cable2 = Cable(
+ a_terminations=[interface2],
+ b_terminations=[circuittermination1_Z, circuittermination2_Z]
+ )
+ cable1.save()
+ cable2.save()
+
+ self.assertEqual(CablePath.objects.count(), 2)
+
+ path1 = self.assertPathExists(
+ (
+ interface1,
+ cable1,
+ (circuittermination1_A, circuittermination2_A),
+ (circuittermination1_Z, circuittermination2_Z),
+ cable2,
+ interface2
+
+ ),
+ is_active=True,
+ is_complete=True,
+ )
+ interface1.refresh_from_db()
+ self.assertPathIsSet(interface1, path1)
+
+ path2 = self.assertPathExists(
+ (
+ interface2,
+ cable2,
+ (circuittermination1_Z, circuittermination2_Z),
+ (circuittermination1_A, circuittermination2_A),
+ cable1,
+ interface1
+
+ ),
+ is_active=True,
+ is_complete=True,
+ )
+ interface2.refresh_from_db()
+ self.assertPathIsSet(interface2, path2)
+
+ def test_224_single_path_via_multiple_pass_throughs_with_breakouts(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [IF3]
+ [IF2] [FP2] [RP2] [IF4]
+ """
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
+ interface3 = Interface.objects.create(device=self.device, name='Interface 3')
+ interface4 = Interface.objects.create(device=self.device, name='Interface 4')
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ a_terminations=[interface1, interface2],
+ b_terminations=[frontport1, frontport2]
+ )
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[rearport1, rearport2],
+ b_terminations=[interface3, interface4]
+ )
+ cable2.save()
+
+ # Validate paths
+ self.assertPathExists(
+ (
+ [interface1, interface2], cable1, [frontport1, frontport2],
+ [rearport1, rearport2], cable2, [interface3, interface4],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (
+ [interface3, interface4], cable2, [rearport1, rearport2],
+ [frontport1, frontport2], cable1, [interface1, interface2],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 2)
+
def test_301_create_path_via_existing_cable(self):
"""
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2] [FP2] --C3-- [IF2]
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ ])
# Create cable 2
cable2 = Cable(
@@ -2329,10 +2739,17 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ ])
# Create cables 1 and 2
cable1 = Cable(
@@ -2434,22 +2851,44 @@ class CablePathTestCase(TestCase):
)
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
- rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
- rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
- rearport3 = RearPort.objects.create(device=device, name='Rear Port 3', positions=1)
- rearport4 = RearPort.objects.create(device=device, name='Rear Port 4', positions=1)
- frontport1 = FrontPort.objects.create(
- device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
- )
- frontport2 = FrontPort.objects.create(
- device=self.device, name='Front Port 2', rear_port=rearport2, rear_port_position=1
- )
- frontport3 = FrontPort.objects.create(
- device=device, name='Front Port 3', rear_port=rearport3, rear_port_position=1
- )
- frontport4 = FrontPort.objects.create(
- device=device, name='Front Port 4', rear_port=rearport4, rear_port_position=1
- )
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ rearport3 = RearPort.objects.create(device=device, name='Rear Port 3')
+ rearport4 = RearPort.objects.create(device=device, name='Rear Port 4')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 3')
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 4')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport3,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport4,
+ rear_port_position=1,
+ ),
+ ])
cable2 = Cable(
a_terminations=[rearport1],
@@ -2510,3 +2949,33 @@ class CablePathTestCase(TestCase):
is_active=True
)
self.assertEqual(CablePath.objects.count(), 0)
+
+ def test_402_exclude_circuit_loopback(self):
+ interface = Interface.objects.create(device=self.device, name='Interface 1')
+ circuittermination1 = CircuitTermination.objects.create(
+ circuit=self.circuit,
+ termination=self.site,
+ term_side='A'
+ )
+ circuittermination2 = CircuitTermination.objects.create(
+ circuit=self.circuit,
+ termination=self.site,
+ term_side='Z'
+ )
+
+ # Create cables
+ cable = Cable(
+ a_terminations=[interface],
+ b_terminations=[circuittermination1, circuittermination2]
+ )
+ cable.save()
+
+ path = self.assertPathExists(
+ (interface, cable, (circuittermination1, circuittermination2)),
+ is_active=True,
+ is_complete=False,
+ is_split=True
+ )
+ self.assertEqual(CablePath.objects.count(), 1)
+ interface.refresh_from_db()
+ self.assertPathIsSet(interface, path)
diff --git a/netbox/dcim/tests/test_cablepaths2.py b/netbox/dcim/tests/test_cablepaths2.py
new file mode 100644
index 000000000..27ecf962f
--- /dev/null
+++ b/netbox/dcim/tests/test_cablepaths2.py
@@ -0,0 +1,1541 @@
+from unittest import skip
+
+from circuits.models import CircuitTermination
+from dcim.choices import CableProfileChoices
+from dcim.models import *
+from dcim.svg import CableTraceSVG
+from dcim.tests.utils import CablePathTestCase
+
+
+class CablePathTests(CablePathTestCase):
+ """
+ Test the creation of CablePaths for Cables with different profiles applied.
+
+ Tests are numbered as follows:
+ 1XX: Test direct connections using each profile
+ 2XX: Topology tests replicated from the legacy test case and adapted to use profiles
+ """
+
+ def test_101_cable_profile_single_1c1p(self):
+ """
+ [IF1] --C1-- [IF2]
+
+ Cable profile: Single connector, single position
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ ]
+
+ # Create cable 1
+ cable1 = Cable(
+ profile=CableProfileChoices.SINGLE_1C1P,
+ a_terminations=[interfaces[0]],
+ b_terminations=[interfaces[1]],
+ )
+ cable1.clean()
+ cable1.save()
+
+ path1 = self.assertPathExists(
+ (interfaces[0], cable1, interfaces[1]),
+ is_complete=True,
+ is_active=True
+ )
+ path2 = self.assertPathExists(
+ (interfaces[1], cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 2)
+ interfaces[0].refresh_from_db()
+ interfaces[1].refresh_from_db()
+ self.assertPathIsSet(interfaces[0], path1)
+ self.assertPathIsSet(interfaces[1], path2)
+ self.assertEqual(interfaces[0].cable_connector, 1)
+ self.assertEqual(interfaces[0].cable_positions, [1])
+ self.assertEqual(interfaces[1].cable_connector, 1)
+ self.assertEqual(interfaces[1].cable_positions, [1])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ # Delete cable 1
+ cable1.delete()
+
+ # Check that all CablePaths have been deleted
+ self.assertEqual(CablePath.objects.count(), 0)
+
+ def test_102_cable_profile_single_1c2p(self):
+ """
+ [IF1] --C1-- [FP1][RP1] --C3-- [RP2][FP3] --C4-- [IF3]
+ [IF2] --C2-- [FP2] [FP4] --C5-- [IF4]
+
+ Cable profile: Single connector, multiple positions
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ ]
+ rear_ports = [
+ RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
+ RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
+ ]
+ front_ports = [
+ FrontPort.objects.create(device=self.device, name='Front Port 1'),
+ FrontPort.objects.create(device=self.device, name='Front Port 2'),
+ FrontPort.objects.create(device=self.device, name='Front Port 3'),
+ FrontPort.objects.create(device=self.device, name='Front Port 4'),
+ ]
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[0],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[1],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[2],
+ front_port_position=1,
+ rear_port=rear_ports[1],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[3],
+ front_port_position=1,
+ rear_port=rear_ports[1],
+ rear_port_position=2,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ a_terminations=[interfaces[0]],
+ b_terminations=[front_ports[0]],
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[interfaces[1]],
+ b_terminations=[front_ports[1]],
+ )
+ cable2.clean()
+ cable2.save()
+ cable3 = Cable(
+ profile=CableProfileChoices.SINGLE_1C2P,
+ a_terminations=[rear_ports[0]],
+ b_terminations=[rear_ports[1]],
+ )
+ cable3.clean()
+ cable3.save()
+ cable4 = Cable(
+ a_terminations=[interfaces[2]],
+ b_terminations=[front_ports[2]],
+ )
+ cable4.clean()
+ cable4.save()
+ cable5 = Cable(
+ a_terminations=[interfaces[3]],
+ b_terminations=[front_ports[3]],
+ )
+ cable5.clean()
+ cable5.save()
+
+ path1 = self.assertPathExists(
+ (
+ interfaces[0], cable1, front_ports[0], rear_ports[0], cable3, rear_ports[1], front_ports[2], cable4,
+ interfaces[2],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path2 = self.assertPathExists(
+ (
+ interfaces[1], cable2, front_ports[1], rear_ports[0], cable3, rear_ports[1], front_ports[3], cable5,
+ interfaces[3],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path3 = self.assertPathExists(
+ (
+ interfaces[2], cable4, front_ports[2], rear_ports[1], cable3, rear_ports[0], front_ports[0], cable1,
+ interfaces[0],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path4 = self.assertPathExists(
+ (
+ interfaces[3], cable5, front_ports[3], rear_ports[1], cable3, rear_ports[0], front_ports[1], cable2,
+ interfaces[1],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 4)
+ for iface in interfaces:
+ iface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], path1)
+ self.assertPathIsSet(interfaces[1], path2)
+ self.assertPathIsSet(interfaces[2], path3)
+ self.assertPathIsSet(interfaces[3], path4)
+ for rear_port in rear_ports:
+ rear_port.refresh_from_db()
+ self.assertEqual(rear_ports[0].cable_connector, 1)
+ self.assertEqual(rear_ports[0].cable_positions, [1, 2])
+ self.assertEqual(rear_ports[1].cable_connector, 1)
+ self.assertEqual(rear_ports[1].cable_positions, [1, 2])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_103_cable_profile_trunk_2c1p(self):
+ """
+ [IF1] --C1-- [IF3]
+ [IF2] [IF4]
+
+ Cable profile: Multiple connectors, single position
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ ]
+
+ # Create cable 1
+ cable1 = Cable(
+ profile=CableProfileChoices.TRUNK_2C1P,
+ a_terminations=[interfaces[0], interfaces[1]],
+ b_terminations=[interfaces[2], interfaces[3]],
+ )
+ cable1.clean()
+ cable1.save()
+
+ path1 = self.assertPathExists(
+ (interfaces[0], cable1, interfaces[2]),
+ is_complete=True,
+ is_active=True
+ )
+ path2 = self.assertPathExists(
+ (interfaces[1], cable1, interfaces[3]),
+ is_complete=True,
+ is_active=True
+ )
+ path3 = self.assertPathExists(
+ (interfaces[2], cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True
+ )
+ path4 = self.assertPathExists(
+ (interfaces[3], cable1, interfaces[1]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 4)
+
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], path1)
+ self.assertPathIsSet(interfaces[1], path2)
+ self.assertPathIsSet(interfaces[2], path3)
+ self.assertPathIsSet(interfaces[3], path4)
+ self.assertEqual(interfaces[0].cable_connector, 1)
+ self.assertEqual(interfaces[0].cable_positions, [1])
+ self.assertEqual(interfaces[1].cable_connector, 2)
+ self.assertEqual(interfaces[1].cable_positions, [1])
+ self.assertEqual(interfaces[2].cable_connector, 1)
+ self.assertEqual(interfaces[2].cable_positions, [1])
+ self.assertEqual(interfaces[3].cable_connector, 2)
+ self.assertEqual(interfaces[3].cable_positions, [1])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ # Delete cable 1
+ cable1.delete()
+
+ # Check that all CablePaths have been deleted
+ self.assertEqual(CablePath.objects.count(), 0)
+
+ def test_104_cable_profile_trunk_2c2p(self):
+ """
+ [IF1] --C1-- [FP1][RP1] --C9-- [RP3][FP5] --C5-- [IF5]
+ [IF2] --C2-- [FP2] [FP6] --C6-- [IF6]
+ [IF3] --C3-- [FP3][RP2] [RP4][FP7] --C7-- [IF7]
+ [IF4] --C4-- [FP4] [FP8] --C8-- [IF8]
+
+ Cable profile: Multiple connectors, multiple positions
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ Interface.objects.create(device=self.device, name='Interface 5'),
+ Interface.objects.create(device=self.device, name='Interface 6'),
+ Interface.objects.create(device=self.device, name='Interface 7'),
+ Interface.objects.create(device=self.device, name='Interface 8'),
+ ]
+ rear_ports = [
+ RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
+ RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
+ RearPort.objects.create(device=self.device, name='Rear Port 3', positions=2),
+ RearPort.objects.create(device=self.device, name='Rear Port 4', positions=2),
+ ]
+ front_ports = [
+ FrontPort.objects.create(device=self.device, name='Front Port 1'),
+ FrontPort.objects.create(device=self.device, name='Front Port 2'),
+ FrontPort.objects.create(device=self.device, name='Front Port 3'),
+ FrontPort.objects.create(device=self.device, name='Front Port 4'),
+ FrontPort.objects.create(device=self.device, name='Front Port 5'),
+ FrontPort.objects.create(device=self.device, name='Front Port 6'),
+ FrontPort.objects.create(device=self.device, name='Front Port 7'),
+ FrontPort.objects.create(device=self.device, name='Front Port 8'),
+ ]
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[0],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[1],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[2],
+ front_port_position=1,
+ rear_port=rear_ports[1],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[3],
+ front_port_position=1,
+ rear_port=rear_ports[1],
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[4],
+ front_port_position=1,
+ rear_port=rear_ports[2],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[5],
+ front_port_position=1,
+ rear_port=rear_ports[2],
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[6],
+ front_port_position=1,
+ rear_port=rear_ports[3],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[7],
+ front_port_position=1,
+ rear_port=rear_ports[3],
+ rear_port_position=2,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(a_terminations=[interfaces[1]], b_terminations=[front_ports[1]])
+ cable2.clean()
+ cable2.save()
+ cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[front_ports[2]])
+ cable3.clean()
+ cable3.save()
+ cable4 = Cable(a_terminations=[interfaces[3]], b_terminations=[front_ports[3]])
+ cable4.clean()
+ cable4.save()
+ cable5 = Cable(a_terminations=[interfaces[4]], b_terminations=[front_ports[4]])
+ cable5.clean()
+ cable5.save()
+ cable6 = Cable(a_terminations=[interfaces[5]], b_terminations=[front_ports[5]])
+ cable6.clean()
+ cable6.save()
+ cable7 = Cable(a_terminations=[interfaces[6]], b_terminations=[front_ports[6]])
+ cable7.clean()
+ cable7.save()
+ cable8 = Cable(a_terminations=[interfaces[7]], b_terminations=[front_ports[7]])
+ cable8.clean()
+ cable8.save()
+ cable9 = Cable(
+ profile=CableProfileChoices.TRUNK_2C2P,
+ a_terminations=[rear_ports[0], rear_ports[1]],
+ b_terminations=[rear_ports[2], rear_ports[3]]
+ )
+ cable9.clean()
+ cable9.save()
+
+ path1 = self.assertPathExists(
+ (
+ interfaces[0], cable1, front_ports[0], rear_ports[0], cable9, rear_ports[2], front_ports[4], cable5,
+ interfaces[4],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path2 = self.assertPathExists(
+ (
+ interfaces[1], cable2, front_ports[1], rear_ports[0], cable9, rear_ports[2], front_ports[5], cable6,
+ interfaces[5],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path3 = self.assertPathExists(
+ (
+ interfaces[2], cable3, front_ports[2], rear_ports[1], cable9, rear_ports[3], front_ports[6], cable7,
+ interfaces[6],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path4 = self.assertPathExists(
+ (
+ interfaces[3], cable4, front_ports[3], rear_ports[1], cable9, rear_ports[3], front_ports[7], cable8,
+ interfaces[7],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path5 = self.assertPathExists(
+ (
+ interfaces[4], cable5, front_ports[4], rear_ports[2], cable9, rear_ports[0], front_ports[0], cable1,
+ interfaces[0],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path6 = self.assertPathExists(
+ (
+ interfaces[5], cable6, front_ports[5], rear_ports[2], cable9, rear_ports[0], front_ports[1], cable2,
+ interfaces[1],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path7 = self.assertPathExists(
+ (
+ interfaces[6], cable7, front_ports[6], rear_ports[3], cable9, rear_ports[1], front_ports[2], cable3,
+ interfaces[2],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ path8 = self.assertPathExists(
+ (
+ interfaces[7], cable8, front_ports[7], rear_ports[3], cable9, rear_ports[1], front_ports[3], cable4,
+ interfaces[3],
+ ),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 8)
+ for iface in interfaces:
+ iface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], path1)
+ self.assertPathIsSet(interfaces[1], path2)
+ self.assertPathIsSet(interfaces[2], path3)
+ self.assertPathIsSet(interfaces[3], path4)
+ self.assertPathIsSet(interfaces[4], path5)
+ self.assertPathIsSet(interfaces[5], path6)
+ self.assertPathIsSet(interfaces[6], path7)
+ self.assertPathIsSet(interfaces[7], path8)
+ for rear_port in rear_ports:
+ rear_port.refresh_from_db()
+ self.assertEqual(rear_ports[0].cable_connector, 1)
+ self.assertEqual(rear_ports[0].cable_positions, [1, 2])
+ self.assertEqual(rear_ports[1].cable_connector, 2)
+ self.assertEqual(rear_ports[1].cable_positions, [1, 2])
+ self.assertEqual(rear_ports[2].cable_connector, 1)
+ self.assertEqual(rear_ports[2].cable_positions, [1, 2])
+ self.assertEqual(rear_ports[3].cable_connector, 2)
+ self.assertEqual(rear_ports[3].cable_positions, [1, 2])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_105_cable_profile_breakout(self):
+ """
+ [IF1] --C1-- [FP1][RP1] --C2-- [IF5]
+ [IF2] --C3-- [FP2] [IF6]
+ [IF3] --C4-- [FP3] [IF7]
+ [IF4] --C5-- [FP4] [IF8]
+
+ Cable profile: 1:4 breakout
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ Interface.objects.create(device=self.device, name='Interface 5'),
+ Interface.objects.create(device=self.device, name='Interface 6'),
+ Interface.objects.create(device=self.device, name='Interface 7'),
+ Interface.objects.create(device=self.device, name='Interface 8'),
+ ]
+ rear_ports = [
+ RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4),
+ ]
+ front_ports = [
+ FrontPort.objects.create(device=self.device, name='Front Port 1'),
+ FrontPort.objects.create(device=self.device, name='Front Port 2'),
+ FrontPort.objects.create(device=self.device, name='Front Port 3'),
+ FrontPort.objects.create(device=self.device, name='Front Port 4'),
+ ]
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[0],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[1],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[2],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=3,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[3],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=4,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(a_terminations=[interfaces[1]], b_terminations=[front_ports[1]])
+ cable2.clean()
+ cable2.save()
+ cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[front_ports[2]])
+ cable3.clean()
+ cable3.save()
+ cable4 = Cable(a_terminations=[interfaces[3]], b_terminations=[front_ports[3]])
+ cable4.clean()
+ cable4.save()
+ cable5 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[rear_ports[0]],
+ b_terminations=interfaces[4:8],
+ )
+ cable5.clean()
+ cable5.save()
+
+ path1 = self.assertPathExists(
+ (interfaces[0], cable1, front_ports[0], rear_ports[0], cable5, interfaces[4]),
+ is_complete=True,
+ is_active=True
+ )
+ path2 = self.assertPathExists(
+ (interfaces[1], cable2, front_ports[1], rear_ports[0], cable5, interfaces[5]),
+ is_complete=True,
+ is_active=True
+ )
+ path3 = self.assertPathExists(
+ (interfaces[2], cable3, front_ports[2], rear_ports[0], cable5, interfaces[6]),
+ is_complete=True,
+ is_active=True
+ )
+ path4 = self.assertPathExists(
+ (interfaces[3], cable4, front_ports[3], rear_ports[0], cable5, interfaces[7]),
+ is_complete=True,
+ is_active=True
+ )
+ path5 = self.assertPathExists(
+ (interfaces[4], cable5, rear_ports[0], front_ports[0], cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True
+ )
+ path6 = self.assertPathExists(
+ (interfaces[5], cable5, rear_ports[0], front_ports[1], cable2, interfaces[1]),
+ is_complete=True,
+ is_active=True
+ )
+ path7 = self.assertPathExists(
+ (interfaces[6], cable5, rear_ports[0], front_ports[2], cable3, interfaces[2]),
+ is_complete=True,
+ is_active=True
+ )
+ path8 = self.assertPathExists(
+ (interfaces[7], cable5, rear_ports[0], front_ports[3], cable4, interfaces[3]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 8)
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], path1)
+ self.assertPathIsSet(interfaces[1], path2)
+ self.assertPathIsSet(interfaces[2], path3)
+ self.assertPathIsSet(interfaces[3], path4)
+ self.assertPathIsSet(interfaces[4], path5)
+ self.assertPathIsSet(interfaces[5], path6)
+ self.assertPathIsSet(interfaces[6], path7)
+ self.assertPathIsSet(interfaces[7], path8)
+ self.assertEqual(interfaces[4].cable_connector, 1)
+ self.assertEqual(interfaces[4].cable_positions, [1])
+ self.assertEqual(interfaces[5].cable_connector, 2)
+ self.assertEqual(interfaces[5].cable_positions, [1])
+ self.assertEqual(interfaces[6].cable_connector, 3)
+ self.assertEqual(interfaces[6].cable_positions, [1])
+ self.assertEqual(interfaces[7].cable_connector, 4)
+ self.assertEqual(interfaces[7].cable_positions, [1])
+ rear_ports[0].refresh_from_db()
+ self.assertEqual(rear_ports[0].cable_connector, 1)
+ self.assertEqual(rear_ports[0].cable_positions, [1, 2, 3, 4])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_106_cable_profile_shuffle(self):
+ """
+ [IF1] --C1-- [FP1][RP1] --C17-- [RP3][FP9] --C9-- [IF9]
+ [IF2] --C2-- [FP2] [FP10] --C10-- [IF10]
+ [IF3] --C3-- [FP3] [FP11] --C11-- [IF11]
+ [IF4] --C4-- [FP4] [FP12] --C12-- [IF12]
+ [IF5] --C5-- [FP5][RP2] [RP4][FP13] --C13-- [IF9]
+ [IF6] --C6-- [FP6] [FP14] --C14-- [IF10]
+ [IF7] --C7-- [FP7] [FP15] --C15-- [IF11]
+ [IF8] --C8-- [FP8] [FP16] --C16-- [IF12]
+
+ Cable profile: Shuffle (2x2 MPO8)
+ """
+ interfaces = [
+ # A side
+ Interface.objects.create(device=self.device, name='Interface 1:1'),
+ Interface.objects.create(device=self.device, name='Interface 1:2'),
+ Interface.objects.create(device=self.device, name='Interface 1:3'),
+ Interface.objects.create(device=self.device, name='Interface 1:4'),
+ Interface.objects.create(device=self.device, name='Interface 2:1'),
+ Interface.objects.create(device=self.device, name='Interface 2:2'),
+ Interface.objects.create(device=self.device, name='Interface 2:3'),
+ Interface.objects.create(device=self.device, name='Interface 2:4'),
+ # B side
+ Interface.objects.create(device=self.device, name='Interface 3:1'),
+ Interface.objects.create(device=self.device, name='Interface 3:2'),
+ Interface.objects.create(device=self.device, name='Interface 3:3'),
+ Interface.objects.create(device=self.device, name='Interface 3:4'),
+ Interface.objects.create(device=self.device, name='Interface 4:1'),
+ Interface.objects.create(device=self.device, name='Interface 4:2'),
+ Interface.objects.create(device=self.device, name='Interface 4:3'),
+ Interface.objects.create(device=self.device, name='Interface 4:4'),
+ ]
+ rear_ports = [
+ RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4),
+ RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4),
+ RearPort.objects.create(device=self.device, name='Rear Port 3', positions=4),
+ RearPort.objects.create(device=self.device, name='Rear Port 4', positions=4),
+ ]
+ front_ports = [
+ FrontPort.objects.create(device=self.device, name='Front Port 1'),
+ FrontPort.objects.create(device=self.device, name='Front Port 2'),
+ FrontPort.objects.create(device=self.device, name='Front Port 3'),
+ FrontPort.objects.create(device=self.device, name='Front Port 4'),
+ FrontPort.objects.create(device=self.device, name='Front Port 5'),
+ FrontPort.objects.create(device=self.device, name='Front Port 6'),
+ FrontPort.objects.create(device=self.device, name='Front Port 7'),
+ FrontPort.objects.create(device=self.device, name='Front Port 8'),
+ FrontPort.objects.create(device=self.device, name='Front Port 9'),
+ FrontPort.objects.create(device=self.device, name='Front Port 10'),
+ FrontPort.objects.create(device=self.device, name='Front Port 11'),
+ FrontPort.objects.create(device=self.device, name='Front Port 12'),
+ FrontPort.objects.create(device=self.device, name='Front Port 13'),
+ FrontPort.objects.create(device=self.device, name='Front Port 14'),
+ FrontPort.objects.create(device=self.device, name='Front Port 15'),
+ FrontPort.objects.create(device=self.device, name='Front Port 16'),
+ ]
+ port_mappings = []
+ for i, front_port in enumerate(front_ports):
+ port_mappings.append(
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[i],
+ front_port_position=1,
+ rear_port=rear_ports[int(i / 4)],
+ rear_port_position=(i % 4) + 1,
+ ),
+ )
+ PortMapping.objects.bulk_create(port_mappings)
+
+ # Create cables
+ cables = []
+ for interface, front_port in zip(interfaces, front_ports):
+ cable = Cable(a_terminations=[interface], b_terminations=[front_port])
+ cable.clean()
+ cable.save()
+ cables.append(cable)
+ shuffle_cable = Cable(
+ profile=CableProfileChoices.TRUNK_2C4P_SHUFFLE,
+ a_terminations=rear_ports[0:2],
+ b_terminations=rear_ports[2:4],
+ )
+ shuffle_cable.clean()
+ shuffle_cable.save()
+
+ paths = [
+ # A-to-B paths
+ self.assertPathExists(
+ (
+ interfaces[0], cables[0], front_ports[0], rear_ports[0], shuffle_cable, rear_ports[2],
+ front_ports[8], cables[8], interfaces[8],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[1], cables[1], front_ports[1], rear_ports[0], shuffle_cable, rear_ports[2],
+ front_ports[9], cables[9], interfaces[9],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[2], cables[2], front_ports[2], rear_ports[0], shuffle_cable, rear_ports[3],
+ front_ports[12], cables[12], interfaces[12],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[3], cables[3], front_ports[3], rear_ports[0], shuffle_cable, rear_ports[3],
+ front_ports[13], cables[13], interfaces[13],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[4], cables[4], front_ports[4], rear_ports[1], shuffle_cable, rear_ports[2],
+ front_ports[10], cables[10], interfaces[10],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[5], cables[5], front_ports[5], rear_ports[1], shuffle_cable, rear_ports[2],
+ front_ports[11], cables[11], interfaces[11],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[6], cables[6], front_ports[6], rear_ports[1], shuffle_cable, rear_ports[3],
+ front_ports[14], cables[14], interfaces[14],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[7], cables[7], front_ports[7], rear_ports[1], shuffle_cable, rear_ports[3],
+ front_ports[15], cables[15], interfaces[15],
+ ),
+ is_complete=True,
+ is_active=True
+ ),
+ ]
+ self.assertEqual(CablePath.objects.count(), len(paths) * 2)
+
+ for i, (interface, path) in enumerate(zip(interfaces, paths)):
+ interface.refresh_from_db()
+ self.assertPathIsSet(interface, path)
+ for i, rear_port in enumerate(rear_ports):
+ rear_port.refresh_from_db()
+ self.assertEqual(rear_port.cable_connector, (i % 2) + 1)
+ self.assertEqual(rear_port.cable_positions, [1, 2, 3, 4])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_202_single_path_via_pass_through_with_breakouts(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [IF3]
+ [IF2] [IF4]
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ ]
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1', positions=4)
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=2,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=3,
+ rear_port=rearport1,
+ rear_port_position=3,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=4,
+ rear_port=rearport1,
+ rear_port_position=4,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[frontport1],
+ b_terminations=[interfaces[0], interfaces[1]],
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[rearport1],
+ b_terminations=[interfaces[2], interfaces[3]]
+ )
+ cable2.clean()
+ cable2.save()
+
+ paths = [
+ self.assertPathExists(
+ (interfaces[0], cable1, frontport1, rearport1, cable2, interfaces[2]),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (interfaces[1], cable1, frontport1, rearport1, cable2, interfaces[3]),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (interfaces[2], cable2, rearport1, frontport1, cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True
+ ),
+ self.assertPathExists(
+ (interfaces[3], cable2, rearport1, frontport1, cable1, interfaces[1]),
+ is_complete=True,
+ is_active=True
+ ),
+ ]
+ self.assertEqual(CablePath.objects.count(), 4)
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], paths[0])
+ self.assertPathIsSet(interfaces[1], paths[1])
+ self.assertPathIsSet(interfaces[2], paths[2])
+ self.assertPathIsSet(interfaces[3], paths[3])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_204_multiple_paths_via_pass_through_with_breakouts(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C3-- [RP2] [FP3] --C4-- [IF5]
+ [IF2] [IF6]
+ [IF3] --C2-- [FP2] [FP4] --C5-- [IF7]
+ [IF4] [IF8]
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ Interface.objects.create(device=self.device, name='Interface 5'),
+ Interface.objects.create(device=self.device, name='Interface 6'),
+ Interface.objects.create(device=self.device, name='Interface 7'),
+ Interface.objects.create(device=self.device, name='Interface 8'),
+ ]
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=8)
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=8)
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1', positions=4)
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2', positions=4)
+ frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 2:1', positions=4)
+ frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 2:2', positions=4)
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=2,
+ rear_port=rearport1,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=5,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=2,
+ rear_port=rearport1,
+ rear_port_position=6,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport3,
+ front_port_position=2,
+ rear_port=rearport2,
+ rear_port_position=2,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=5,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport4,
+ front_port_position=2,
+ rear_port=rearport2,
+ rear_port_position=6,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[frontport1],
+ b_terminations=[interfaces[0], interfaces[1]],
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[frontport2],
+ b_terminations=[interfaces[2], interfaces[3]],
+ )
+ cable2.clean()
+ cable2.save()
+ cable3 = Cable(
+ profile=CableProfileChoices.SINGLE_1C8P,
+ a_terminations=[rearport1],
+ b_terminations=[rearport2]
+ )
+ cable3.clean()
+ cable3.save()
+ cable4 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[frontport3],
+ b_terminations=[interfaces[4], interfaces[5]],
+ )
+ cable4.clean()
+ cable4.save()
+ cable5 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[frontport4],
+ b_terminations=[interfaces[6], interfaces[7]],
+ )
+ cable5.clean()
+ cable5.save()
+
+ paths = [
+ self.assertPathExists(
+ (
+ interfaces[0], cable1, frontport1, rearport1, cable3, rearport2, frontport3, cable4,
+ interfaces[4],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[1], cable1, frontport1, rearport1, cable3, rearport2, frontport3, cable4,
+ interfaces[5],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[2], cable2, frontport2, rearport1, cable3, rearport2, frontport4, cable5,
+ interfaces[6],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[3], cable2, frontport2, rearport1, cable3, rearport2, frontport4, cable5,
+ interfaces[7],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[4], cable4, frontport3, rearport2, cable3, rearport1, frontport1, cable1,
+ interfaces[0],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[5], cable4, frontport3, rearport2, cable3, rearport1, frontport1, cable1,
+ interfaces[1],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[6], cable5, frontport4, rearport2, cable3, rearport1, frontport2, cable2,
+ interfaces[2],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (
+ interfaces[7], cable5, frontport4, rearport2, cable3, rearport1, frontport2, cable2,
+ interfaces[3],
+ ),
+ is_complete=True,
+ is_active=True,
+ ),
+ ]
+ self.assertEqual(CablePath.objects.count(), 8)
+
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], paths[0])
+ self.assertPathIsSet(interfaces[1], paths[1])
+ self.assertPathIsSet(interfaces[2], paths[2])
+ self.assertPathIsSet(interfaces[3], paths[3])
+ self.assertPathIsSet(interfaces[4], paths[4])
+ self.assertPathIsSet(interfaces[5], paths[5])
+ self.assertPathIsSet(interfaces[6], paths[6])
+ self.assertPathIsSet(interfaces[7], paths[7])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_212_interface_to_interface_via_circuit_with_breakouts(self):
+ """
+ [IF1] --C1-- [CT1] [CT2] --C2-- [IF3]
+ [IF2] [IF4]
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ ]
+ circuittermination1 = CircuitTermination.objects.create(
+ circuit=self.circuit,
+ termination=self.site,
+ term_side='A'
+ )
+ circuittermination2 = CircuitTermination.objects.create(
+ circuit=self.circuit,
+ termination=self.site,
+ term_side='Z'
+ )
+
+ # Create cables
+ cable1 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[circuittermination1],
+ b_terminations=[interfaces[0], interfaces[1]],
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
+ a_terminations=[circuittermination2],
+ b_terminations=[interfaces[2], interfaces[3]]
+ )
+ cable2.clean()
+ cable2.save()
+
+ # Check for two complete paths in either direction
+ paths = [
+ self.assertPathExists(
+ (interfaces[0], cable1, circuittermination1, circuittermination2, cable2, interfaces[2]),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (interfaces[1], cable1, circuittermination1, circuittermination2, cable2, interfaces[3]),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (interfaces[2], cable2, circuittermination2, circuittermination1, cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True,
+ ),
+ self.assertPathExists(
+ (interfaces[3], cable2, circuittermination2, circuittermination1, cable1, interfaces[1]),
+ is_complete=True,
+ is_active=True,
+ ),
+ ]
+ self.assertEqual(CablePath.objects.count(), 4)
+
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], paths[0])
+ self.assertPathIsSet(interfaces[1], paths[1])
+ self.assertPathIsSet(interfaces[2], paths[2])
+ self.assertPathIsSet(interfaces[3], paths[3])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ # TBD: Is this a topology we want to support?
+ @skip("Test applicability TBD")
+ def test_217_interface_to_interface_via_rear_ports(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [RP3] [FP3] --C3-- [IF2]
+ [FP2] [RP2] [RP4] [FP4]
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ ]
+ rear_ports = [
+ RearPort.objects.create(device=self.device, name='Rear Port 1'),
+ RearPort.objects.create(device=self.device, name='Rear Port 2'),
+ RearPort.objects.create(device=self.device, name='Rear Port 3'),
+ RearPort.objects.create(device=self.device, name='Rear Port 4'),
+ ]
+ front_ports = [
+ FrontPort.objects.create(device=self.device, name='Front Port 1'),
+ FrontPort.objects.create(device=self.device, name='Front Port 2'),
+ FrontPort.objects.create(device=self.device, name='Front Port 3'),
+ FrontPort.objects.create(device=self.device, name='Front Port 4'),
+ ]
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[0],
+ front_port_position=1,
+ rear_port=rear_ports[0],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[1],
+ front_port_position=1,
+ rear_port=rear_ports[1],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[2],
+ front_port_position=1,
+ rear_port=rear_ports[2],
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=front_ports[3],
+ front_port_position=1,
+ rear_port=rear_ports[3],
+ rear_port_position=1,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ profile=CableProfileChoices.SINGLE_2C1P,
+ a_terminations=[interfaces[0]],
+ b_terminations=[front_ports[0], front_ports[1]]
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[rear_ports[0], rear_ports[1]],
+ b_terminations=[rear_ports[2], rear_ports[3]]
+ )
+ cable2.clean()
+ cable2.save()
+ cable3 = Cable(
+ profile=CableProfileChoices.SINGLE_2C1P,
+ a_terminations=[interfaces[1]],
+ b_terminations=[front_ports[2], front_ports[3]]
+ )
+ cable3.clean()
+ cable3.save()
+
+ # Check for one complete path in either direction
+ paths = [
+ self.assertPathExists(
+ (
+ interfaces[0], cable1, (front_ports[0], front_ports[1]), (rear_ports[0], rear_ports[1]), cable2,
+ (rear_ports[2], rear_ports[3]), (front_ports[2], front_ports[3]), cable3, interfaces[1]
+ ),
+ is_complete=True
+ ),
+ self.assertPathExists(
+ (
+ interfaces[1], cable3, (front_ports[2], front_ports[3]), (rear_ports[2], rear_ports[3]), cable2,
+ (rear_ports[0], rear_ports[1]), (front_ports[0], front_ports[1]), cable1, interfaces[0]
+ ),
+ is_complete=True
+ ),
+ ]
+ self.assertEqual(CablePath.objects.count(), 2)
+
+ for interface in interfaces:
+ interface.refresh_from_db()
+ self.assertPathIsSet(interfaces[0], paths[0])
+ self.assertPathIsSet(interfaces[1], paths[1])
+
+ # Test SVG generation
+ CableTraceSVG(interfaces[0]).render()
+
+ def test_223_single_path_via_multiple_pass_throughs_with_breakouts(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [IF3]
+ [IF2] [FP2] [RP2] [IF4]
+ """
+ interfaces = [
+ Interface.objects.create(device=self.device, name='Interface 1'),
+ Interface.objects.create(device=self.device, name='Interface 2'),
+ Interface.objects.create(device=self.device, name='Interface 3'),
+ Interface.objects.create(device=self.device, name='Interface 4'),
+ ]
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ PortMapping.objects.bulk_create([
+ PortMapping(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ ),
+ PortMapping(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ ),
+ ])
+
+ # Create cables
+ cable1 = Cable(
+ profile=CableProfileChoices.TRUNK_2C2P,
+ a_terminations=[interfaces[0], interfaces[1]],
+ b_terminations=[frontport1, frontport2]
+ )
+ cable1.clean()
+ cable1.save()
+ cable2 = Cable(
+ profile=CableProfileChoices.TRUNK_2C2P,
+ a_terminations=[rearport1, rearport2],
+ b_terminations=[interfaces[2], interfaces[3]]
+ )
+ cable2.clean()
+ cable2.save()
+
+ # Validate paths
+ self.assertPathExists(
+ (interfaces[0], cable1, frontport1, rearport1, cable2, interfaces[2]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interfaces[1], cable1, frontport2, rearport2, cable2, interfaces[3]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interfaces[2], cable2, rearport1, frontport1, cable1, interfaces[0]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interfaces[3], cable2, rearport2, frontport2, cable1, interfaces[1]),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertEqual(CablePath.objects.count(), 4)
+
+ def test_304_add_port_mapping_between_connected_ports(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [IF2]
+ """
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ cable1 = Cable(
+ a_terminations=[interface1],
+ b_terminations=[frontport1]
+ )
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[interface2],
+ b_terminations=[rearport1]
+ )
+ cable2.save()
+
+ # Check for incomplete paths
+ self.assertPathExists(
+ (interface1, cable1, frontport1),
+ is_complete=False,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface2, cable2, rearport1),
+ is_complete=False,
+ is_active=True
+ )
+
+ # Create a PortMapping between frontport1 and rearport1
+ PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ )
+
+ # Check that paths are now complete
+ self.assertPathExists(
+ (interface1, cable1, frontport1, rearport1, cable2, interface2),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface2, cable2, rearport1, frontport1, cable1, interface1),
+ is_complete=True,
+ is_active=True
+ )
+
+ def test_305_delete_port_mapping_between_connected_ports(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C2-- [IF2]
+ """
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ cable1 = Cable(
+ a_terminations=[interface1],
+ b_terminations=[frontport1]
+ )
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[interface2],
+ b_terminations=[rearport1]
+ )
+ cable2.save()
+ portmapping1 = PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ )
+
+ # Check for complete paths
+ self.assertPathExists(
+ (interface1, cable1, frontport1, rearport1, cable2, interface2),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface2, cable2, rearport1, frontport1, cable1, interface1),
+ is_complete=True,
+ is_active=True
+ )
+
+ # Delete the PortMapping between frontport1 and rearport1
+ portmapping1.delete()
+
+ # Check that paths are no longer complete
+ self.assertPathExists(
+ (interface1, cable1, frontport1),
+ is_complete=False,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface2, cable2, rearport1),
+ is_complete=False,
+ is_active=True
+ )
+
+ def test_306_change_port_mapping_between_connected_ports(self):
+ """
+ [IF1] --C1-- [FP1] [RP1] --C3-- [IF3]
+ [IF2] --C2-- [FP2] [RP3] --C4-- [IF4]
+ """
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
+ interface3 = Interface.objects.create(device=self.device, name='Interface 3')
+ interface4 = Interface.objects.create(device=self.device, name='Interface 4')
+ frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
+ frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
+ rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
+ rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
+ cable1 = Cable(
+ a_terminations=[interface1],
+ b_terminations=[frontport1]
+ )
+ cable1.save()
+ cable2 = Cable(
+ a_terminations=[interface2],
+ b_terminations=[frontport2]
+ )
+ cable2.save()
+ cable3 = Cable(
+ a_terminations=[interface3],
+ b_terminations=[rearport1]
+ )
+ cable3.save()
+ cable4 = Cable(
+ a_terminations=[interface4],
+ b_terminations=[rearport2]
+ )
+ cable4.save()
+ portmapping1 = PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport1,
+ rear_port_position=1,
+ )
+
+ # Verify expected initial paths
+ self.assertPathExists(
+ (interface1, cable1, frontport1, rearport1, cable3, interface3),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface3, cable3, rearport1, frontport1, cable1, interface1),
+ is_complete=True,
+ is_active=True
+ )
+
+ # Delete and replace the PortMapping to connect interface1 to interface4
+ portmapping1.delete()
+ portmapping2 = PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport1,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ )
+
+ # Verify expected new paths
+ self.assertPathExists(
+ (interface1, cable1, frontport1, rearport2, cable4, interface4),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface4, cable4, rearport2, frontport1, cable1, interface1),
+ is_complete=True,
+ is_active=True
+ )
+
+ # Delete and replace the PortMapping to connect interface2 to interface4
+ portmapping2.delete()
+ PortMapping.objects.create(
+ device=self.device,
+ front_port=frontport2,
+ front_port_position=1,
+ rear_port=rearport2,
+ rear_port_position=1,
+ )
+
+ # Verify expected new paths
+ self.assertPathExists(
+ (interface2, cable2, frontport2, rearport2, cable4, interface4),
+ is_complete=True,
+ is_active=True
+ )
+ self.assertPathExists(
+ (interface4, cable4, rearport2, frontport2, cable2, interface2),
+ is_complete=True,
+ is_active=True
+ )
diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py
index c05d07ab0..96a0f14fb 100644
--- a/netbox/dcim/tests/test_filtersets.py
+++ b/netbox/dcim/tests/test_filtersets.py
@@ -10,7 +10,7 @@ from netbox.choices import ColorChoices, WeightUnitChoices
from tenancy.models import Tenant, TenantGroup
from users.models import User
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
-from virtualization.models import Cluster, ClusterType, ClusterGroup, VMInterface, VirtualMachine
+from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
from wireless.models import WirelessLink
@@ -43,6 +43,13 @@ class DeviceComponentFilterSetTests:
params = {'device_status': ['active', 'planned']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_tenant(self):
+ tenants = Tenant.objects.all()[:2]
+ params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'tenant': [tenants[0].slug, tenants[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class DeviceComponentTemplateFilterSetTests:
@@ -1355,22 +1362,15 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
)
RearPortTemplate.objects.bulk_create(rear_ports)
- FrontPortTemplate.objects.bulk_create(
- (
- FrontPortTemplate(
- device_type=device_types[0],
- name='Front Port 1',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_ports[0],
- ),
- FrontPortTemplate(
- device_type=device_types[1],
- name='Front Port 2',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_ports[1],
- ),
- )
+ front_ports = (
+ FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
)
+ FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(device_type=device_types[0], front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(device_type=device_types[1], front_port=front_ports[1], rear_port=rear_ports[1]),
+ ])
ModuleBayTemplate.objects.bulk_create((
ModuleBayTemplate(device_type=device_types[0], name='Module Bay 1'),
ModuleBayTemplate(device_type=device_types[1], name='Module Bay 2'),
@@ -1626,22 +1626,15 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
RearPortTemplate(module_type=module_types[1], name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
)
RearPortTemplate.objects.bulk_create(rear_ports)
- FrontPortTemplate.objects.bulk_create(
- (
- FrontPortTemplate(
- module_type=module_types[0],
- name='Front Port 1',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_ports[0],
- ),
- FrontPortTemplate(
- module_type=module_types[1],
- name='Front Port 2',
- type=PortTypeChoices.TYPE_8P8C,
- rear_port=rear_ports[1],
- ),
- )
+ front_ports = (
+ FrontPortTemplate(module_type=module_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPortTemplate(module_type=module_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
)
+ FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(module_type=module_types[0], front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(module_type=module_types[1], front_port=front_ports[1], rear_port=rear_ports[1]),
+ ])
def test_q(self):
params = {'q': 'foobar1'}
@@ -1919,18 +1912,21 @@ class PowerOutletTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTest
device_type=device_types[0],
name='Power Outlet 1',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A,
+ color=ColorChoices.COLOR_RED,
description='foobar1'
),
PowerOutletTemplate(
device_type=device_types[1],
name='Power Outlet 2',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B,
+ color=ColorChoices.COLOR_GREEN,
description='foobar2'
),
PowerOutletTemplate(
device_type=device_types[2],
name='Power Outlet 3',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C,
+ color=ColorChoices.COLOR_BLUE,
description='foobar3'
),
))
@@ -1943,6 +1939,10 @@ class PowerOutletTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTest
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_color(self):
+ params = {'color': [ColorChoices.COLOR_RED, ColorChoices.COLOR_GREEN]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class InterfaceTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests, ChangeLoggedFilterSetTests):
queryset = InterfaceTemplate.objects.all()
@@ -2050,32 +2050,38 @@ class FrontPortTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests,
)
RearPortTemplate.objects.bulk_create(rear_ports)
- FrontPortTemplate.objects.bulk_create((
+ front_ports = (
FrontPortTemplate(
device_type=device_types[0],
name='Front Port 1',
- rear_port=rear_ports[0],
type=PortTypeChoices.TYPE_8P8C,
+ positions=1,
color=ColorChoices.COLOR_RED,
description='foobar1'
),
FrontPortTemplate(
device_type=device_types[1],
name='Front Port 2',
- rear_port=rear_ports[1],
type=PortTypeChoices.TYPE_110_PUNCH,
+ positions=2,
color=ColorChoices.COLOR_GREEN,
description='foobar2'
),
FrontPortTemplate(
device_type=device_types[2],
name='Front Port 3',
- rear_port=rear_ports[2],
type=PortTypeChoices.TYPE_BNC,
+ positions=3,
color=ColorChoices.COLOR_BLUE,
description='foobar3'
),
- ))
+ )
+ FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(device_type=device_types[0], front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(device_type=device_types[1], front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortTemplateMapping(device_type=device_types[2], front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
def test_name(self):
params = {'name': ['Front Port 1', 'Front Port 2']}
@@ -2089,6 +2095,10 @@ class FrontPortTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests,
params = {'color': [ColorChoices.COLOR_RED, ColorChoices.COLOR_GREEN]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_positions(self):
+ params = {'positions': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class RearPortTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests, ChangeLoggedFilterSetTests):
queryset = RearPortTemplate.objects.all()
@@ -2745,10 +2755,15 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
RearPort(device=devices[1], name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
)
RearPort.objects.bulk_create(rear_ports)
- FrontPort.objects.bulk_create((
- FrontPort(device=devices[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
- FrontPort(device=devices[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
- ))
+ front_ports = (
+ FrontPort(device=devices[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
+ FrontPort(device=devices[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
+ )
+ FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=devices[0], front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=devices[1], front_port=front_ports[1], rear_port=rear_ports[1]),
+ ])
ModuleBay.objects.create(device=devices[0], name='Module Bay 1')
ModuleBay.objects.create(device=devices[1], name='Module Bay 2')
DeviceBay.objects.bulk_create((
@@ -3317,6 +3332,7 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = ConsolePort.objects.all()
filterset = ConsolePortFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -3377,9 +3393,17 @@ class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -3389,6 +3413,7 @@ class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -3398,6 +3423,7 @@ class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -3557,6 +3583,7 @@ class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = ConsoleServerPort.objects.all()
filterset = ConsoleServerPortFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -3617,9 +3644,17 @@ class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeL
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -3629,6 +3664,7 @@ class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeL
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -3638,6 +3674,7 @@ class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeL
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -3797,6 +3834,7 @@ class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeL
class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = PowerPort.objects.all()
filterset = PowerPortFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -3857,9 +3895,17 @@ class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -3869,6 +3915,7 @@ class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -3878,6 +3925,7 @@ class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -4051,6 +4099,7 @@ class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = PowerOutlet.objects.all()
filterset = PowerOutletFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -4111,9 +4160,17 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -4123,6 +4180,7 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -4132,6 +4190,7 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -4325,7 +4384,7 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = Interface.objects.all()
filterset = InterfaceFilterSet
- ignore_fields = ('tagged_vlans', 'untagged_vlan', 'qinq_svlan', 'vdcs')
+ ignore_fields = ('tagged_vlans', 'untagged_vlan', 'qinq_svlan', 'vdcs', 'cable_positions')
@classmethod
def setUpTestData(cls):
@@ -4390,9 +4449,17 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
virtual_chassis = VirtualChassis(name='Virtual Chassis')
virtual_chassis.save()
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1A',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -4405,6 +4472,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 1B',
+ tenant=tenants[1],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -4417,6 +4485,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 2',
+ tenant=tenants[2],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -4426,6 +4495,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -4951,6 +5021,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = FrontPort.objects.all()
filterset = FrontPortFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -5011,9 +5082,17 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -5023,6 +5102,7 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -5032,6 +5112,7 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -5083,8 +5164,6 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
label='A',
type=PortTypeChoices.TYPE_8P8C,
color=ColorChoices.COLOR_RED,
- rear_port=rear_ports[0],
- rear_port_position=1,
description='First',
_site=devices[0].site,
_location=devices[0].location,
@@ -5097,8 +5176,6 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
label='B',
type=PortTypeChoices.TYPE_110_PUNCH,
color=ColorChoices.COLOR_GREEN,
- rear_port=rear_ports[1],
- rear_port_position=2,
description='Second',
_site=devices[1].site,
_location=devices[1].location,
@@ -5111,8 +5188,6 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
label='C',
type=PortTypeChoices.TYPE_BNC,
color=ColorChoices.COLOR_BLUE,
- rear_port=rear_ports[2],
- rear_port_position=3,
description='Third',
_site=devices[2].site,
_location=devices[2].location,
@@ -5123,8 +5198,7 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
name='Front Port 4',
label='D',
type=PortTypeChoices.TYPE_FC,
- rear_port=rear_ports[3],
- rear_port_position=1,
+ positions=2,
_site=devices[3].site,
_location=devices[3].location,
_rack=devices[3].rack,
@@ -5134,8 +5208,7 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
name='Front Port 5',
label='E',
type=PortTypeChoices.TYPE_FC,
- rear_port=rear_ports[4],
- rear_port_position=1,
+ positions=3,
_site=devices[3].site,
_location=devices[3].location,
_rack=devices[3].rack,
@@ -5145,14 +5218,21 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
name='Front Port 6',
label='F',
type=PortTypeChoices.TYPE_FC,
- rear_port=rear_ports[5],
- rear_port_position=1,
+ positions=4,
_site=devices[3].site,
_location=devices[3].location,
_rack=devices[3].rack,
),
)
FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=devices[0], front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=devices[1], front_port=front_ports[1], rear_port=rear_ports[1], rear_port_position=2),
+ PortMapping(device=devices[2], front_port=front_ports[2], rear_port=rear_ports[2], rear_port_position=3),
+ PortMapping(device=devices[3], front_port=front_ports[3], rear_port=rear_ports[3]),
+ PortMapping(device=devices[3], front_port=front_ports[4], rear_port=rear_ports[4]),
+ PortMapping(device=devices[3], front_port=front_ports[5], rear_port=rear_ports[5]),
+ ])
# Cables
Cable(a_terminations=[front_ports[0]], b_terminations=[front_ports[3]]).save()
@@ -5175,6 +5255,10 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
params = {'color': [ColorChoices.COLOR_RED, ColorChoices.COLOR_GREEN]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_positions(self):
+ params = {'positions': [2, 3]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
def test_description(self):
params = {'description': ['First', 'Second']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -5242,6 +5326,7 @@ class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = RearPort.objects.all()
filterset = RearPortFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -5302,9 +5387,17 @@ class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilt
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -5314,6 +5407,7 @@ class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilt
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -5323,6 +5417,7 @@ class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilt
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -5579,9 +5674,17 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -5591,6 +5694,7 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -5600,6 +5704,7 @@ class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -5752,9 +5857,17 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
)
Rack.objects.bulk_create(racks)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
Device(
name='Device 1',
+ tenant=tenants[0],
device_type=device_types[0],
role=roles[0],
site=sites[0],
@@ -5764,6 +5877,7 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 2',
+ tenant=tenants[1],
device_type=device_types[1],
role=roles[1],
site=sites[1],
@@ -5773,6 +5887,7 @@ class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
),
Device(
name='Device 3',
+ tenant=tenants[2],
device_type=device_types[2],
role=roles[2],
site=sites[2],
@@ -6413,13 +6528,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
power_port = PowerPort.objects.create(device=devices[0], name='Power Port 1')
power_outlet = PowerOutlet.objects.create(device=devices[0], name='Power Outlet 1')
- rear_port = RearPort.objects.create(device=devices[0], name='Rear Port 1', positions=1)
- front_port = FrontPort.objects.create(
- device=devices[0],
- name='Front Port 1',
- rear_port=rear_port,
- rear_port_position=1
- )
+ rear_port = RearPort.objects.create(device=devices[0], name='Rear Port 1')
+ front_port = FrontPort.objects.create(device=devices[0], name='Front Port 1')
+ PortMapping.objects.create(device=devices[0], front_port=front_port, rear_port=rear_port)
power_panel = PowerPanel.objects.create(name='Power Panel 1', site=sites[0])
power_feed = PowerFeed.objects.create(name='Power Feed 1', power_panel=power_panel)
@@ -6754,6 +6865,7 @@ class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = PowerFeed.objects.all()
filterset = PowerFeedFilterSet
+ ignore_fields = ('cable_positions',)
@classmethod
def setUpTestData(cls):
@@ -7164,9 +7276,20 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
MACAddress(mac_address='00-00-00-05-01-01', assigned_object=vm_interfaces[1]),
MACAddress(mac_address='00-00-00-06-01-01', assigned_object=vm_interfaces[2]),
MACAddress(mac_address='00-00-00-06-01-02', assigned_object=vm_interfaces[2]),
+ # unassigned
+ MACAddress(mac_address='00-00-00-07-01-01'),
)
MACAddress.objects.bulk_create(mac_addresses)
+ # Set MAC addresses as primary
+ for idx, interface in enumerate(interfaces):
+ interface.primary_mac_address = mac_addresses[idx]
+ interface.save()
+ for idx, vm_interface in enumerate(vm_interfaces):
+ # Offset by 4 for device MACs
+ vm_interface.primary_mac_address = mac_addresses[idx + 4]
+ vm_interface.save()
+
def test_mac_address(self):
params = {'mac_address': ['00-00-00-01-01-01', '00-00-00-02-01-01']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -7198,3 +7321,15 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_assigned(self):
+ params = {'assigned': True}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
+ params = {'assigned': False}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_primary(self):
+ params = {'primary': True}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
+ params = {'primary': False}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py
index fa654f789..a911cbf25 100644
--- a/netbox/dcim/tests/test_forms.py
+++ b/netbox/dcim/tests/test_forms.py
@@ -193,7 +193,8 @@ class FrontPortTestCase(TestCase):
'name': 'FrontPort[1-4]',
'label': 'Port[1-4]',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
+ 'positions': 1,
+ 'rear_ports': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
}
form = FrontPortCreateForm(front_port_data)
@@ -208,7 +209,8 @@ class FrontPortTestCase(TestCase):
'name': 'FrontPort[1-4]',
'label': 'Port[1-2]',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
+ 'positions': 1,
+ 'rear_ports': [f'{rear_port.pk}:1' for rear_port in self.rear_ports],
}
form = FrontPortCreateForm(bad_front_port_data)
diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py
index be9f067d4..175eb1165 100644
--- a/netbox/dcim/tests/test_models.py
+++ b/netbox/dcim/tests/test_models.py
@@ -444,13 +444,19 @@ class DeviceTestCase(TestCase):
)
rearport.save()
- FrontPortTemplate(
+ frontport = FrontPortTemplate(
device_type=device_type,
name='Front Port 1',
type=PortTypeChoices.TYPE_8P8C,
+ )
+ frontport.save()
+
+ PortTemplateMapping.objects.create(
+ device_type=device_type,
+ front_port=frontport,
rear_port=rearport,
- rear_port_position=2
- ).save()
+ rear_port_position=2,
+ )
ModuleBayTemplate(
device_type=device_type,
@@ -528,11 +534,12 @@ class DeviceTestCase(TestCase):
device=device,
name='Front Port 1',
type=PortTypeChoices.TYPE_8P8C,
- rear_port=rearport,
- rear_port_position=2
+ positions=1
)
self.assertEqual(frontport.cf['cf1'], 'foo')
+ self.assertTrue(PortMapping.objects.filter(front_port=frontport, rear_port=rearport).exists())
+
modulebay = ModuleBay.objects.get(
device=device,
name='Module Bay 1'
@@ -792,8 +799,54 @@ class ModuleBayTestCase(TestCase):
)
device.consoleports.first()
- def test_nested_module_token(self):
- pass
+ @tag('regression') # #19918
+ def test_nested_module_bay_label_resolution(self):
+ """Test that nested module bay labels properly resolve {module} placeholders"""
+ manufacturer = Manufacturer.objects.first()
+ site = Site.objects.first()
+ device_role = DeviceRole.objects.first()
+
+ # Create device type with module bay template (position='A')
+ device_type = DeviceType.objects.create(
+ manufacturer=manufacturer,
+ model='Device with Bays',
+ slug='device-with-bays'
+ )
+ ModuleBayTemplate.objects.create(
+ device_type=device_type,
+ name='Bay A',
+ position='A'
+ )
+
+ # Create module type with nested bay template using {module} placeholder
+ module_type = ModuleType.objects.create(
+ manufacturer=manufacturer,
+ model='Module with Nested Bays'
+ )
+ ModuleBayTemplate.objects.create(
+ module_type=module_type,
+ name='SFP {module}-21',
+ label='{module}-21',
+ position='21'
+ )
+
+ # Create device and install module
+ device = Device.objects.create(
+ name='Test Device',
+ device_type=device_type,
+ role=device_role,
+ site=site
+ )
+ module_bay = device.modulebays.get(name='Bay A')
+ module = Module.objects.create(
+ device=device,
+ module_bay=module_bay,
+ module_type=module_type
+ )
+
+ # Verify nested bay label resolves {module} to parent position
+ nested_bay = module.modulebays.get(name='SFP A-21')
+ self.assertEqual(nested_bay.label, 'A-21')
class CableTestCase(TestCase):
@@ -835,12 +888,18 @@ class CableTestCase(TestCase):
)
RearPort.objects.bulk_create(rear_ports)
front_ports = (
- FrontPort(device=patch_panel, name='FP1', type='8p8c', rear_port=rear_ports[0], rear_port_position=1),
- FrontPort(device=patch_panel, name='FP2', type='8p8c', rear_port=rear_ports[1], rear_port_position=1),
- FrontPort(device=patch_panel, name='FP3', type='8p8c', rear_port=rear_ports[2], rear_port_position=1),
- FrontPort(device=patch_panel, name='FP4', type='8p8c', rear_port=rear_ports[3], rear_port_position=1),
+ FrontPort(device=patch_panel, name='FP1', type='8p8c'),
+ FrontPort(device=patch_panel, name='FP2', type='8p8c'),
+ FrontPort(device=patch_panel, name='FP3', type='8p8c'),
+ FrontPort(device=patch_panel, name='FP4', type='8p8c'),
)
FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=patch_panel, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=patch_panel, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortMapping(device=patch_panel, front_port=front_ports[2], rear_port=rear_ports[2]),
+ PortMapping(device=patch_panel, front_port=front_ports[3], rear_port=rear_ports[3]),
+ ])
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider)
@@ -967,6 +1026,18 @@ class CableTestCase(TestCase):
with self.assertRaises(ValidationError):
cable.clean()
+ def test_cannot_cable_to_mark_connected(self):
+ """
+ Test that a cable cannot be connected to an interface marked as connected.
+ """
+ device1 = Device.objects.get(name='TestDevice1')
+ interface1 = Interface.objects.get(device__name='TestDevice2', name='eth1')
+
+ mark_connected_interface = Interface(device=device1, name='mark_connected1', mark_connected=True)
+ cable = Cable(a_terminations=[mark_connected_interface], b_terminations=[interface1])
+ with self.assertRaises(ValidationError):
+ cable.clean()
+
class VirtualDeviceContextTestCase(TestCase):
@@ -1019,3 +1090,92 @@ class VirtualDeviceContextTestCase(TestCase):
vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
with self.assertRaises(ValidationError):
vdc2.full_clean()
+
+
+class VirtualChassisTestCase(TestCase):
+
+ @classmethod
+ def setUpTestData(cls):
+ site = Site.objects.create(name='Test Site 1', slug='test-site-1')
+ manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+ devicetype = DeviceType.objects.create(
+ manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
+ )
+ role = DeviceRole.objects.create(
+ name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
+ )
+ Device.objects.create(
+ device_type=devicetype, role=role, name='TestDevice1', site=site
+ )
+ Device.objects.create(
+ device_type=devicetype, role=role, name='TestDevice2', site=site
+ )
+
+ def test_virtualchassis_deletion_clears_vc_position(self):
+ """
+ Test that when a VirtualChassis is deleted, member devices have their
+ vc_position and vc_priority fields set to None.
+ """
+ devices = Device.objects.all()
+ device1 = devices[0]
+ device2 = devices[1]
+
+ # Create a VirtualChassis with two member devices
+ vc = VirtualChassis.objects.create(name='Test VC', master=device1)
+
+ device1.virtual_chassis = vc
+ device1.vc_position = 1
+ device1.vc_priority = 10
+ device1.save()
+
+ device2.virtual_chassis = vc
+ device2.vc_position = 2
+ device2.vc_priority = 20
+ device2.save()
+
+ # Verify devices are members of the VC with positions set
+ device1.refresh_from_db()
+ device2.refresh_from_db()
+ self.assertEqual(device1.virtual_chassis, vc)
+ self.assertEqual(device1.vc_position, 1)
+ self.assertEqual(device1.vc_priority, 10)
+ self.assertEqual(device2.virtual_chassis, vc)
+ self.assertEqual(device2.vc_position, 2)
+ self.assertEqual(device2.vc_priority, 20)
+
+ # Delete the VirtualChassis
+ vc.delete()
+
+ # Verify devices have vc_position and vc_priority set to None
+ device1.refresh_from_db()
+ device2.refresh_from_db()
+ self.assertIsNone(device1.virtual_chassis)
+ self.assertIsNone(device1.vc_position)
+ self.assertIsNone(device1.vc_priority)
+ self.assertIsNone(device2.virtual_chassis)
+ self.assertIsNone(device2.vc_position)
+ self.assertIsNone(device2.vc_priority)
+
+ def test_virtualchassis_duplicate_vc_position(self):
+ """
+ Test that two devices cannot be assigned to the same vc_position
+ within the same VirtualChassis.
+ """
+ devices = Device.objects.all()
+ device1 = devices[0]
+ device2 = devices[1]
+
+ # Create a VirtualChassis
+ vc = VirtualChassis.objects.create(name='Test VC')
+
+ # Assign first device to vc_position 1
+ device1.virtual_chassis = vc
+ device1.vc_position = 1
+ device1.full_clean()
+ device1.save()
+
+ # Try to assign second device to the same vc_position
+ device2.virtual_chassis = vc
+ device2.vc_position = 1
+ with self.assertRaises(ValidationError):
+ device2.full_clean()
diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py
index b23f7e16d..5a17b01c9 100644
--- a/netbox/dcim/tests/test_views.py
+++ b/netbox/dcim/tests/test_views.py
@@ -7,13 +7,15 @@ from django.test import override_settings, tag
from django.urls import reverse
from netaddr import EUI
+from core.models import ObjectType
from dcim.choices import *
from dcim.constants import *
from dcim.models import *
+from extras.models import ConfigTemplate
from ipam.models import ASN, RIR, VLAN, VRF
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, WeightUnitChoices
from tenancy.models import Tenant
-from users.models import User
+from users.models import ObjectPermission, User
from utilities.testing import ViewTestCases, create_tags, create_test_device, post_data
from wireless.models import WirelessLAN
@@ -740,17 +742,16 @@ class DeviceTypeTestCase(
)
RearPortTemplate.objects.bulk_create(rear_ports)
front_ports = (
- FrontPortTemplate(
- device_type=devicetype, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1
- ),
- FrontPortTemplate(
- device_type=devicetype, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1
- ),
- FrontPortTemplate(
- device_type=devicetype, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1
- ),
+ FrontPortTemplate(device_type=devicetype, name='Front Port 1'),
+ FrontPortTemplate(device_type=devicetype, name='Front Port 2'),
+ FrontPortTemplate(device_type=devicetype, name='Front Port 3'),
)
FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
url = reverse('dcim:devicetype_frontports', kwargs={'pk': devicetype.pk})
self.assertHttpStatus(self.client.get(url), 200)
@@ -865,12 +866,16 @@ rear-ports:
front-ports:
- name: Front Port 1
type: 8p8c
- rear_port: Rear Port 1
- name: Front Port 2
type: 8p8c
- rear_port: Rear Port 2
- name: Front Port 3
type: 8p8c
+port-mappings:
+ - front_port: Front Port 1
+ rear_port: Rear Port 1
+ - front_port: Front Port 2
+ rear_port: Rear Port 2
+ - front_port: Front Port 3
rear_port: Rear Port 3
module-bays:
- name: Module Bay 1
@@ -970,8 +975,12 @@ inventory-items:
self.assertEqual(device_type.frontporttemplates.count(), 3)
fp1 = FrontPortTemplate.objects.first()
self.assertEqual(fp1.name, 'Front Port 1')
- self.assertEqual(fp1.rear_port, rp1)
- self.assertEqual(fp1.rear_port_position, 1)
+
+ self.assertEqual(device_type.port_mappings.count(), 3)
+ mapping1 = PortTemplateMapping.objects.first()
+ self.assertEqual(mapping1.device_type, device_type)
+ self.assertEqual(mapping1.front_port, fp1)
+ self.assertEqual(mapping1.rear_port, rp1)
self.assertEqual(device_type.modulebaytemplates.count(), 3)
mb1 = ModuleBayTemplate.objects.first()
@@ -985,6 +994,131 @@ inventory-items:
ii1 = InventoryItemTemplate.objects.first()
self.assertEqual(ii1.name, 'Inventory Item 1')
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_import_error_numbering(self):
+ # Add all required permissions to the test user
+ self.add_permissions(
+ 'dcim.view_devicetype',
+ 'dcim.add_devicetype',
+ 'dcim.add_consoleporttemplate',
+ 'dcim.add_consoleserverporttemplate',
+ 'dcim.add_powerporttemplate',
+ 'dcim.add_poweroutlettemplate',
+ 'dcim.add_interfacetemplate',
+ 'dcim.add_frontporttemplate',
+ 'dcim.add_rearporttemplate',
+ 'dcim.add_modulebaytemplate',
+ 'dcim.add_devicebaytemplate',
+ 'dcim.add_inventoryitemtemplate',
+ )
+
+ import_data = '''
+---
+manufacturer: Manufacturer 1
+model: TEST-2001
+slug: test-2001
+u_height: 1
+module-bays:
+ - name: Module Bay 1-1
+ - name: Module Bay 1-2
+---
+- manufacturer: Manufacturer 1
+ model: TEST-2002
+ slug: test-2002
+ u_height: 1
+ module-bays:
+ - name: Module Bay 2-1
+ - name: Module Bay 2-2
+ - not_name: Module Bay 2-3
+- manufacturer: Manufacturer 1
+ model: TEST-2003
+ slug: test-2003
+ u_height: 1
+ module-bays:
+ - name: Module Bay 3-1
+'''
+ form_data = {
+ 'data': import_data,
+ 'format': 'yaml'
+ }
+
+ response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")
+
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_import_nolist(self):
+ # Add all required permissions to the test user
+ self.add_permissions(
+ 'dcim.view_devicetype',
+ 'dcim.add_devicetype',
+ 'dcim.add_consoleporttemplate',
+ 'dcim.add_consoleserverporttemplate',
+ 'dcim.add_powerporttemplate',
+ 'dcim.add_poweroutlettemplate',
+ 'dcim.add_interfacetemplate',
+ 'dcim.add_frontporttemplate',
+ 'dcim.add_rearporttemplate',
+ 'dcim.add_modulebaytemplate',
+ 'dcim.add_devicebaytemplate',
+ 'dcim.add_inventoryitemtemplate',
+ )
+
+ for value in ('', 'null', '3', '"My console port"', '{name: "My other console port"}'):
+ with self.subTest(value=value):
+ import_data = f'''
+manufacturer: Manufacturer 1
+model: TEST-3000
+slug: test-3000
+u_height: 1
+console-ports: {value}
+'''
+ form_data = {
+ 'data': import_data,
+ 'format': 'yaml'
+ }
+
+ response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, "Record 1 console-ports: Must be a list.")
+
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+ def test_import_nodict(self):
+ # Add all required permissions to the test user
+ self.add_permissions(
+ 'dcim.view_devicetype',
+ 'dcim.add_devicetype',
+ 'dcim.add_consoleporttemplate',
+ 'dcim.add_consoleserverporttemplate',
+ 'dcim.add_powerporttemplate',
+ 'dcim.add_poweroutlettemplate',
+ 'dcim.add_interfacetemplate',
+ 'dcim.add_frontporttemplate',
+ 'dcim.add_rearporttemplate',
+ 'dcim.add_modulebaytemplate',
+ 'dcim.add_devicebaytemplate',
+ 'dcim.add_inventoryitemtemplate',
+ )
+
+ for value in ('', 'null', '3', '"My console port"', '["My other console port"]'):
+ with self.subTest(value=value):
+ import_data = f'''
+manufacturer: Manufacturer 1
+model: TEST-4000
+slug: test-4000
+u_height: 1
+console-ports:
+ - {value}
+'''
+ form_data = {
+ 'data': import_data,
+ 'format': 'yaml'
+ }
+
+ response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, "Record 1 console-ports[1]: Must be a dictionary.")
+
def test_export_objects(self):
url = reverse('dcim:devicetype_list')
self.add_permissions('dcim.view_devicetype')
@@ -1078,14 +1212,14 @@ class ModuleTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'dcim.add_modulebaytemplate',
)
+ def verify_module_type_profile(scenario_name):
+ # TODO: remove extra regression asserts once parent test supports testing all import fields
+ fan_module_type = ModuleType.objects.get(part_number='generic-fan')
+ fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan')
+ assert fan_module_type.profile == fan_module_type_profile
+
# run base test
- super().test_bulk_import_objects_with_permission()
-
- # TODO: remove extra regression asserts once parent test supports testing all import fields
- fan_module_type = ModuleType.objects.get(part_number='generic-fan')
- fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan')
-
- assert fan_module_type.profile == fan_module_type_profile
+ super().test_bulk_import_objects_with_permission(post_import_callback=verify_module_type_profile)
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
def test_bulk_import_objects_with_constrained_permission(self):
@@ -1190,17 +1324,16 @@ class ModuleTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
RearPortTemplate.objects.bulk_create(rear_ports)
front_ports = (
- FrontPortTemplate(
- module_type=moduletype, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1
- ),
- FrontPortTemplate(
- module_type=moduletype, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1
- ),
- FrontPortTemplate(
- module_type=moduletype, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1
- ),
+ FrontPortTemplate(module_type=moduletype, name='Front Port 1'),
+ FrontPortTemplate(module_type=moduletype, name='Front Port 2'),
+ FrontPortTemplate(module_type=moduletype, name='Front Port 3'),
)
FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(module_type=moduletype, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(module_type=moduletype, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortTemplateMapping(module_type=moduletype, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
url = reverse('dcim:moduletype_frontports', kwargs={'pk': moduletype.pk})
self.assertHttpStatus(self.client.get(url), 200)
@@ -1268,12 +1401,16 @@ rear-ports:
front-ports:
- name: Front Port 1
type: 8p8c
- rear_port: Rear Port 1
- name: Front Port 2
type: 8p8c
- rear_port: Rear Port 2
- name: Front Port 3
type: 8p8c
+port-mappings:
+ - front_port: Front Port 1
+ rear_port: Rear Port 1
+ - front_port: Front Port 2
+ rear_port: Rear Port 2
+ - front_port: Front Port 3
rear_port: Rear Port 3
module-bays:
- name: Module Bay 1
@@ -1351,8 +1488,12 @@ module-bays:
self.assertEqual(module_type.frontporttemplates.count(), 3)
fp1 = FrontPortTemplate.objects.first()
self.assertEqual(fp1.name, 'Front Port 1')
- self.assertEqual(fp1.rear_port, rp1)
- self.assertEqual(fp1.rear_port_position, 1)
+
+ self.assertEqual(module_type.port_mappings.count(), 3)
+ mapping1 = PortTemplateMapping.objects.first()
+ self.assertEqual(mapping1.module_type, module_type)
+ self.assertEqual(mapping1.front_port, fp1)
+ self.assertEqual(mapping1.rear_port, rp1)
self.assertEqual(module_type.modulebaytemplates.count(), 3)
mb1 = ModuleBayTemplate.objects.first()
@@ -1644,7 +1785,7 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
- rearports = (
+ rear_ports = (
RearPortTemplate(device_type=devicetype, name='Rear Port Template 1'),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 2'),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 3'),
@@ -1652,35 +1793,33 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
RearPortTemplate(device_type=devicetype, name='Rear Port Template 5'),
RearPortTemplate(device_type=devicetype, name='Rear Port Template 6'),
)
- RearPortTemplate.objects.bulk_create(rearports)
-
- FrontPortTemplate.objects.bulk_create(
- (
- FrontPortTemplate(
- device_type=devicetype, name='Front Port Template 1', rear_port=rearports[0], rear_port_position=1
- ),
- FrontPortTemplate(
- device_type=devicetype, name='Front Port Template 2', rear_port=rearports[1], rear_port_position=1
- ),
- FrontPortTemplate(
- device_type=devicetype, name='Front Port Template 3', rear_port=rearports[2], rear_port_position=1
- ),
- )
+ RearPortTemplate.objects.bulk_create(rear_ports)
+ front_ports = (
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 1'),
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 2'),
+ FrontPortTemplate(device_type=devicetype, name='Front Port Template 3'),
)
+ FrontPortTemplate.objects.bulk_create(front_ports)
+ PortTemplateMapping.objects.bulk_create([
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortTemplateMapping(device_type=devicetype, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
cls.form_data = {
'device_type': devicetype.pk,
'name': 'Front Port X',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rearports[3].pk,
- 'rear_port_position': 1,
+ 'positions': 1,
+ 'rear_ports': [f'{rear_ports[3].pk}:1'],
}
cls.bulk_create_data = {
'device_type': devicetype.pk,
'name': 'Front Port [4-6]',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
+ 'positions': 1,
+ 'rear_ports': [f'{rp.pk}:1' for rp in rear_ports[3:6]],
}
cls.bulk_edit_data = {
@@ -2150,11 +2289,16 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
RearPort.objects.bulk_create(rear_ports)
front_ports = (
- FrontPort(device=device, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1),
- FrontPort(device=device, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1),
- FrontPort(device=device, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1),
+ FrontPort(device=device, name='Front Port Template 1'),
+ FrontPort(device=device, name='Front Port Template 2'),
+ FrontPort(device=device, name='Front Port Template 3'),
)
FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=device, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=device, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortMapping(device=device, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
url = reverse('dcim:device_frontports', kwargs={'pk': device.pk})
self.assertHttpStatus(self.client.get(url), 200)
@@ -2196,6 +2340,28 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
self.assertHttpStatus(self.client.get(url), 200)
+ def test_device_renderconfig(self):
+ configtemplate = ConfigTemplate.objects.create(
+ name='Test Config Template',
+ template_code='Config for device {{ device.name }}'
+ )
+ device = Device.objects.first()
+ device.config_template = configtemplate
+ device.save()
+ url = reverse('dcim:device_render-config', kwargs={'pk': device.pk})
+
+ # User with only view permission should NOT be able to render config
+ self.add_permissions('dcim.view_device')
+ self.assertHttpStatus(self.client.get(url), 403)
+
+ # With render_config permission added should be able to render config
+ self.add_permissions('dcim.render_config_device')
+ self.assertHttpStatus(self.client.get(url), 200)
+
+ # With view permission removed should NOT be able to render config
+ self.remove_permissions('dcim.view_device')
+ self.assertHttpStatus(self.client.get(url), 403)
+
class ModuleTestCase(
# Module does not support bulk renaming (no name field) or
@@ -2833,10 +2999,19 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
}
cls.csv_data = (
- "device,name,type,vrf.pk,poe_mode,poe_type",
- f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
- f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
- f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af",
+ "device,name,type,vrf.pk,poe_mode,poe_type,mode,untagged_vlan,tagged_vlans",
+ (
+ f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
+ f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
+ ),
+ (
+ f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
+ f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
+ ),
+ (
+ f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
+ f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
+ ),
)
cls.csv_update_data = (
@@ -2884,6 +3059,43 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
self.client.post(self._get_url('bulk_delete'), data)
self.assertEqual(device.interfaces.count(), 4) # Child & parent were both deleted
+ def test_rename_select_all_spans_pages(self):
+ """
+ Tests the bulk rename functionality for interfaces spanning multiple pages in the UI.
+ """
+ device_name = 'DeviceRename'
+ device = create_test_device(device_name)
+ # Create > default page size (25) so selection spans multiple pages
+ for i in range(37):
+ Interface.objects.create(device=device, name=f'eth{i}')
+
+ self.add_permissions('dcim.change_interface')
+
+ # Filter to this device's interfaces to simulate a real list filter
+ get_qs = {'device_id': Device.objects.get(name=device_name).pk}
+ post_url = f'{self._get_url("bulk_rename")}?device_id={get_qs["device_id"]}'
+
+ # Preview step: ensure 37 selected (not just one page)
+ data = {'_preview': '1', '_all': '1', 'find': 'eth', 'replace': 'xe'}
+ response = self.client.post(post_url, data=data)
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['selected_objects']), 37)
+
+ # Extract pk[] just like the browser would submit on Apply
+ # (either from the form's initial, or from selected_objects)
+ pk_list = response.context['form'].initial.get('pk')
+ if not pk_list:
+ pk_list = [obj.pk for obj in response.context['selected_objects']]
+ pk_list = [str(pk) for pk in pk_list]
+
+ # Apply step: include pk[] in the POST
+ apply_data = {'_apply': '1', '_all': '1', 'find': 'eth', 'replace': 'xe', 'pk': pk_list}
+ response = self.client.post(post_url, data=apply_data)
+
+ # On success the view redirects back to the return URL
+ self.assertHttpStatus(response, 302)
+ self.assertEqual(Interface.objects.filter(device=device, name__startswith='xe').count(), 37)
+
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
model = FrontPort
@@ -2893,7 +3105,7 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
def setUpTestData(cls):
device = create_test_device('Device 1')
- rearports = (
+ rear_ports = (
RearPort(device=device, name='Rear Port 1'),
RearPort(device=device, name='Rear Port 2'),
RearPort(device=device, name='Rear Port 3'),
@@ -2901,14 +3113,19 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
RearPort(device=device, name='Rear Port 5'),
RearPort(device=device, name='Rear Port 6'),
)
- RearPort.objects.bulk_create(rearports)
+ RearPort.objects.bulk_create(rear_ports)
front_ports = (
- FrontPort(device=device, name='Front Port 1', rear_port=rearports[0]),
- FrontPort(device=device, name='Front Port 2', rear_port=rearports[1]),
- FrontPort(device=device, name='Front Port 3', rear_port=rearports[2]),
+ FrontPort(device=device, name='Front Port 1'),
+ FrontPort(device=device, name='Front Port 2'),
+ FrontPort(device=device, name='Front Port 3'),
)
FrontPort.objects.bulk_create(front_ports)
+ PortMapping.objects.bulk_create([
+ PortMapping(device=device, front_port=front_ports[0], rear_port=rear_ports[0]),
+ PortMapping(device=device, front_port=front_ports[1], rear_port=rear_ports[1]),
+ PortMapping(device=device, front_port=front_ports[2], rear_port=rear_ports[2]),
+ ])
tags = create_tags('Alpha', 'Bravo', 'Charlie')
@@ -2916,8 +3133,8 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
'device': device.pk,
'name': 'Front Port X',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': rearports[3].pk,
- 'rear_port_position': 1,
+ 'positions': 1,
+ 'rear_ports': [f'{rear_ports[3].pk}:1'],
'description': 'New description',
'tags': [t.pk for t in tags],
}
@@ -2926,7 +3143,8 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
'device': device.pk,
'name': 'Front Port [4-6]',
'type': PortTypeChoices.TYPE_8P8C,
- 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
+ 'positions': 1,
+ 'rear_ports': [f'{rp.pk}:1' for rp in rear_ports[3:6]],
'description': 'New description',
'tags': [t.pk for t in tags],
}
@@ -2937,10 +3155,10 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
}
cls.csv_data = (
- "device,name,type,rear_port,rear_port_position",
- "Device 1,Front Port 4,8p8c,Rear Port 4,1",
- "Device 1,Front Port 5,8p8c,Rear Port 5,1",
- "Device 1,Front Port 6,8p8c,Rear Port 6,1",
+ "device,name,type,positions",
+ "Device 1,Front Port 4,8p8c,1",
+ "Device 1,Front Port 5,8p8c,1",
+ "Device 1,Front Port 6,8p8c,1",
)
cls.csv_update_data = (
@@ -3290,8 +3508,10 @@ class CableTestCase(
Device(name='Device 1', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 2', site=sites[0], device_type=devicetype, role=role),
Device(name='Device 3', site=sites[0], device_type=devicetype, role=role),
+ Device(name='Device 4', site=sites[0], device_type=devicetype, role=role),
# Create 'Device 1' assigned to 'Site 2' (allowed since the site is different)
Device(name='Device 1', site=sites[1], device_type=devicetype, role=role),
+ Device(name='Device 5', site=sites[1], device_type=devicetype, role=role),
)
Device.objects.bulk_create(devices)
@@ -3300,22 +3520,36 @@ class CableTestCase(
vc.save()
interfaces = (
+ # Device 1, Site 1
Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ # Device 2, Site 1
Interface(device=devices[1], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[1], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ # Device 3, Site 1
Interface(device=devices[2], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[2], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ # Device 3, Site 1
Interface(device=devices[3], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[3], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[3], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ # Device 1, Site 2
+ Interface(device=devices[4], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+
+ # Device 1, Site 2
+ Interface(device=devices[5], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[5], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[5], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+
Interface(device=devices[1], name='Device 2 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[2], name='Device 3 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
- Interface(device=devices[3], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
- Interface(device=devices[3], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
)
Interface.objects.bulk_create(interfaces)
@@ -3342,16 +3576,29 @@ class CableTestCase(
'tags': [t.pk for t in tags],
}
- # Ensure that CSV bulk import supports assigning terminations from parent devices that share
- # the same device name, provided those devices belong to different sites.
- cls.csv_data = (
- "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
- "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
- "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
- "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
- "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
- "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
- )
+ cls.csv_data = {
+ 'default': (
+ "side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
+ "Device 4,dcim.interface,Interface 1,Device 5,dcim.interface,Interface 1",
+ "Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2",
+ "Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3",
+
+ # The following is no longer possible in this scenario, because there are multiple
+ # devices named "Device 1" across multiple sites. See the "site-filtering" scenario
+ # below for how to specify a site for non-unique device names.
+ # "Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5",
+ ),
+ 'site-filtering': (
+ # Ensure that CSV bulk import supports assigning terminations from parent devices
+ # that share the same device name, provided those devices belong to different sites.
+ "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
+ "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
+ "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
+ "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
+ "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
+ "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
+ )
+ }
cls.csv_update_data = (
"id,label,color",
@@ -3699,3 +3946,29 @@ class MACAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.bulk_edit_data = {
'description': 'New description',
}
+
+ @tag('regression') # Issue #20542
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
+ def test_create_macaddress_via_quickadd(self):
+ """
+ Test creating a MAC address via quick-add modal (e.g., from Interface form).
+ Regression test for issue #20542 where form prefix was missing in POST handler.
+ """
+ obj_perm = ObjectPermission(name='Test permission', actions=['add'])
+ obj_perm.save()
+ obj_perm.users.add(self.user)
+ obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
+
+ # Simulate quick-add form submission with 'quickadd-' prefix
+ formatted_data = post_data(self.form_data)
+ quickadd_data = {f'quickadd-{k}': v for k, v in formatted_data.items()}
+ quickadd_data['_quickadd'] = 'True'
+
+ initial_count = self._get_queryset().count()
+ url = f"{self._get_url('add')}?_quickadd=True&target=id_primary_mac_address"
+ response = self.client.post(url, data=quickadd_data)
+
+ # Should successfully create the MAC address and return the quick_add_created template
+ self.assertHttpStatus(response, 200)
+ self.assertIn(b'quick-add-object', response.content)
+ self.assertEqual(initial_count + 1, self._get_queryset().count())
diff --git a/netbox/dcim/tests/utils.py b/netbox/dcim/tests/utils.py
new file mode 100644
index 000000000..575034201
--- /dev/null
+++ b/netbox/dcim/tests/utils.py
@@ -0,0 +1,88 @@
+from django.test import TestCase
+
+from circuits.models import *
+from dcim.models import *
+from dcim.utils import object_to_path_node
+
+__all__ = (
+ 'CablePathTestCase',
+)
+
+
+class CablePathTestCase(TestCase):
+ """
+ Base class for test cases for cable paths.
+ """
+ @classmethod
+ def setUpTestData(cls):
+ manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device')
+ role = DeviceRole.objects.create(name='Device Role', slug='device-role')
+ provider = Provider.objects.create(name='Provider', slug='provider')
+ circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type')
+
+ # Create reusable test objects
+ cls.site = Site.objects.create(name='Site', slug='site')
+ cls.device = Device.objects.create(site=cls.site, device_type=device_type, role=role, name='Test Device')
+ cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel')
+ cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1')
+
+ def _get_cablepath(self, nodes, **kwargs):
+ """
+ Return a given cable path
+
+ :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
+
+ :return: The matching CablePath (if any)
+ """
+ path = []
+ for step in nodes:
+ if type(step) in (list, tuple):
+ path.append([object_to_path_node(node) for node in step])
+ else:
+ path.append([object_to_path_node(step)])
+ return CablePath.objects.filter(path=path, **kwargs).first()
+
+ def assertPathExists(self, nodes, **kwargs):
+ """
+ Assert that a CablePath from origin to destination with a specific intermediate path exists. Returns the
+ first matching CablePath, if found.
+
+ :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
+ """
+ cablepath = self._get_cablepath(nodes, **kwargs)
+ self.assertIsNotNone(cablepath, msg='CablePath not found')
+
+ return cablepath
+
+ def assertPathDoesNotExist(self, nodes, **kwargs):
+ """
+ Assert that a specific CablePath does *not* exist.
+
+ :param nodes: Iterable of steps, with each step being either a single node or a list of nodes
+ """
+ cablepath = self._get_cablepath(nodes, **kwargs)
+ self.assertIsNone(cablepath, msg='Unexpected CablePath found')
+
+ def assertPathIsSet(self, origin, cablepath, msg=None):
+ """
+ Assert that a specific CablePath instance is set as the path on the origin.
+
+ :param origin: The originating path endpoint
+ :param cablepath: The CablePath instance originating from this endpoint
+ :param msg: Custom failure message (optional)
+ """
+ if msg is None:
+ msg = f"Path #{cablepath.pk} not set on originating endpoint {origin}"
+ self.assertEqual(origin._path_id, cablepath.pk, msg=msg)
+
+ def assertPathIsNotSet(self, origin, msg=None):
+ """
+ Assert that a specific CablePath instance is set as the path on the origin.
+
+ :param origin: The originating path endpoint
+ :param msg: Custom failure message (optional)
+ """
+ if msg is None:
+ msg = f"Path #{origin._path_id} set as origin on {origin}; should be None!"
+ self.assertIsNone(origin._path_id, msg=msg)
diff --git a/netbox/dcim/ui/__init__.py b/netbox/dcim/ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py
new file mode 100644
index 000000000..87ceb9c4a
--- /dev/null
+++ b/netbox/dcim/ui/panels.py
@@ -0,0 +1,189 @@
+from django.utils.translation import gettext_lazy as _
+
+from netbox.ui import attrs, panels
+
+
+class SitePanel(panels.ObjectAttributesPanel):
+ region = attrs.NestedObjectAttr('region', linkify=True)
+ group = attrs.NestedObjectAttr('group', linkify=True)
+ name = attrs.TextAttr('name')
+ status = attrs.ChoiceAttr('status')
+ tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+ facility = attrs.TextAttr('facility')
+ description = attrs.TextAttr('description')
+ timezone = attrs.TimezoneAttr('time_zone')
+ physical_address = attrs.AddressAttr('physical_address', map_url=True)
+ shipping_address = attrs.AddressAttr('shipping_address', map_url=True)
+ gps_coordinates = attrs.GPSCoordinatesAttr()
+
+
+class LocationPanel(panels.NestedGroupObjectPanel):
+ site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
+ status = attrs.ChoiceAttr('status')
+ tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+ facility = attrs.TextAttr('facility')
+
+
+class RackDimensionsPanel(panels.ObjectAttributesPanel):
+ form_factor = attrs.ChoiceAttr('form_factor')
+ width = attrs.ChoiceAttr('width')
+ height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
+ outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
+ outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
+ outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
+ mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm')
+
+
+class RackNumberingPanel(panels.ObjectAttributesPanel):
+ starting_unit = attrs.TextAttr('starting_unit')
+ desc_units = attrs.BooleanAttr('desc_units', label=_('Descending units'))
+
+
+class RackPanel(panels.ObjectAttributesPanel):
+ region = attrs.NestedObjectAttr('site.region', linkify=True)
+ site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
+ location = attrs.NestedObjectAttr('location', linkify=True)
+ name = attrs.TextAttr('name')
+ facility = attrs.TextAttr('facility', label=_('Facility ID'))
+ tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+ status = attrs.ChoiceAttr('status')
+ rack_type = attrs.RelatedObjectAttr('rack_type', linkify=True, grouped_by='manufacturer')
+ role = attrs.RelatedObjectAttr('role', linkify=True)
+ description = attrs.TextAttr('description')
+ serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
+ asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
+ airflow = attrs.ChoiceAttr('airflow')
+ space_utilization = attrs.UtilizationAttr('get_utilization')
+ power_utilization = attrs.UtilizationAttr('get_power_utilization')
+
+
+class RackWeightPanel(panels.ObjectAttributesPanel):
+ weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
+ max_weight = attrs.NumericAttr('max_weight', unit_accessor='get_weight_unit_display', label=_('Maximum weight'))
+ total_weight = attrs.TemplatedAttr('total_weight', template_name='dcim/rack/attrs/total_weight.html')
+
+
+class RackRolePanel(panels.OrganizationalObjectPanel):
+ color = attrs.ColorAttr('color')
+
+
+class RackReservationPanel(panels.ObjectAttributesPanel):
+ units = attrs.TextAttr('unit_list')
+ status = attrs.ChoiceAttr('status')
+ tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+ user = attrs.RelatedObjectAttr('user')
+ description = attrs.TextAttr('description')
+
+
+class RackTypePanel(panels.ObjectAttributesPanel):
+ manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
+ model = attrs.TextAttr('model')
+ description = attrs.TextAttr('description')
+
+
+class DevicePanel(panels.ObjectAttributesPanel):
+ region = attrs.NestedObjectAttr('site.region', linkify=True)
+ site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
+ location = attrs.NestedObjectAttr('location', linkify=True)
+ rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
+ virtual_chassis = attrs.RelatedObjectAttr('virtual_chassis', linkify=True)
+ parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html')
+ gps_coordinates = attrs.GPSCoordinatesAttr()
+ tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
+ device_type = attrs.RelatedObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
+ description = attrs.TextAttr('description')
+ airflow = attrs.ChoiceAttr('airflow')
+ serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
+ asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
+ config_template = attrs.RelatedObjectAttr('config_template', linkify=True)
+
+
+class DeviceManagementPanel(panels.ObjectAttributesPanel):
+ title = _('Management')
+
+ status = attrs.ChoiceAttr('status')
+ role = attrs.NestedObjectAttr('role', linkify=True, max_depth=3)
+ platform = attrs.NestedObjectAttr('platform', linkify=True, max_depth=3)
+ primary_ip4 = attrs.TemplatedAttr(
+ 'primary_ip4',
+ label=_('Primary IPv4'),
+ template_name='dcim/device/attrs/ipaddress.html',
+ )
+ primary_ip6 = attrs.TemplatedAttr(
+ 'primary_ip6',
+ label=_('Primary IPv6'),
+ template_name='dcim/device/attrs/ipaddress.html',
+ )
+ oob_ip = attrs.TemplatedAttr(
+ 'oob_ip',
+ label=_('Out-of-band IP'),
+ template_name='dcim/device/attrs/ipaddress.html',
+ )
+ cluster = attrs.RelatedObjectAttr('cluster', linkify=True)
+
+
+class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
+ title = _('Dimensions')
+
+ height = attrs.TextAttr('device_type.u_height', format_string='{}U')
+ total_weight = attrs.TemplatedAttr('total_weight', template_name='dcim/device/attrs/total_weight.html')
+
+
+class DeviceTypePanel(panels.ObjectAttributesPanel):
+ manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
+ model = attrs.TextAttr('model')
+ part_number = attrs.TextAttr('part_number')
+ default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True)
+ description = attrs.TextAttr('description')
+ height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
+ exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
+ full_depth = attrs.BooleanAttr('is_full_depth')
+ weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display')
+ subdevice_role = attrs.ChoiceAttr('subdevice_role', label=_('Parent/child'))
+ airflow = attrs.ChoiceAttr('airflow')
+ front_image = attrs.ImageAttr('front_image')
+ rear_image = attrs.ImageAttr('rear_image')
+
+
+class ModuleTypeProfilePanel(panels.ObjectAttributesPanel):
+ name = attrs.TextAttr('name')
+ description = attrs.TextAttr('description')
+
+
+class VirtualChassisMembersPanel(panels.ObjectPanel):
+ """
+ A panel which lists all members of a virtual chassis.
+ """
+ template_name = 'dcim/panels/virtual_chassis_members.html'
+ title = _('Virtual Chassis Members')
+
+ def get_context(self, context):
+ return {
+ **super().get_context(context),
+ 'vc_members': context.get('vc_members'),
+ }
+
+ def render(self, context):
+ if not context.get('vc_members'):
+ return ''
+ return super().render(context)
+
+
+class PowerUtilizationPanel(panels.ObjectPanel):
+ """
+ A panel which displays the power utilization statistics for a device.
+ """
+ template_name = 'dcim/panels/power_utilization.html'
+ title = _('Power Utilization')
+
+ def get_context(self, context):
+ return {
+ **super().get_context(context),
+ 'vc_members': context.get('vc_members'),
+ }
+
+ def render(self, context):
+ obj = context['object']
+ if not obj.powerports.exists() or not obj.poweroutlets.exists():
+ return ''
+ return super().render(context)
diff --git a/netbox/dcim/utils.py b/netbox/dcim/utils.py
index a03790ea2..4b9d0fb5c 100644
--- a/netbox/dcim/utils.py
+++ b/netbox/dcim/utils.py
@@ -1,3 +1,5 @@
+from collections import defaultdict
+
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.db import router, transaction
@@ -31,17 +33,22 @@ def path_node_to_object(repr):
return ct.model_class().objects.filter(pk=object_id).first()
-def create_cablepath(terminations):
+def create_cablepaths(objects):
"""
Create CablePaths for all paths originating from the specified set of nodes.
- :param terminations: Iterable of CableTermination objects
+ :param objects: Iterable of cabled objects (e.g. Interfaces)
"""
from dcim.models import CablePath
- cp = CablePath.from_origin(terminations)
- if cp:
- cp.save()
+ # Arrange objects by cable connector. All objects with a null connector are grouped together.
+ origins = defaultdict(list)
+ for obj in objects:
+ origins[obj.cable_connector].append(obj)
+
+ for connector, objects in origins.items():
+ if cp := CablePath.from_origin(objects):
+ cp.save()
def rebuild_paths(terminations):
@@ -56,7 +63,7 @@ def rebuild_paths(terminations):
with transaction.atomic(using=router.db_for_write(CablePath)):
for cp in cable_paths:
cp.delete()
- create_cablepath(cp.origins)
+ create_cablepaths(cp.origins)
def update_interface_bridges(device, interface_templates, module=None):
@@ -76,3 +83,36 @@ def update_interface_bridges(device, interface_templates, module=None):
)
interface.full_clean()
interface.save()
+
+
+def create_port_mappings(device, device_type, module=None):
+ """
+ Replicate all front/rear port mappings from a DeviceType to the given device.
+ """
+ from dcim.models import FrontPort, PortMapping, RearPort
+
+ templates = device_type.port_mappings.prefetch_related('front_port', 'rear_port')
+
+ # Cache front & rear ports for efficient lookups by name
+ front_ports = {
+ fp.name: fp for fp in FrontPort.objects.filter(device=device)
+ }
+ rear_ports = {
+ rp.name: rp for rp in RearPort.objects.filter(device=device)
+ }
+
+ # Replicate PortMappings
+ mappings = []
+ for template in templates:
+ front_port = front_ports.get(template.front_port.resolve_name(module=module))
+ rear_port = rear_ports.get(template.rear_port.resolve_name(module=module))
+ mappings.append(
+ PortMapping(
+ device_id=front_port.device_id,
+ front_port=front_port,
+ front_port_position=template.front_port_position,
+ rear_port=rear_port,
+ rear_port_position=template.rear_port_position,
+ )
+ )
+ PortMapping.objects.bulk_create(mappings)
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 97ca99874..ba9365c83 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -1,3 +1,4 @@
+from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.paginator import EmptyPage, PageNotAnInteger
@@ -12,10 +13,17 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from circuits.models import Circuit, CircuitTermination
+from dcim.ui import panels
+from extras.ui.panels import CustomFieldsPanel, ImageAttachmentsPanel, TagsPanel
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.object_actions import *
+from netbox.ui import actions, layout
+from netbox.ui.panels import (
+ CommentsPanel, JSONPanel, NestedGroupObjectPanel, ObjectsTablePanel, OrganizationalObjectPanel, RelatedObjectsPanel,
+ TemplatePanel,
+)
from netbox.views import generic
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count
@@ -34,6 +42,7 @@ from wireless.models import WirelessLAN
from . import filtersets, forms, tables
from .choices import DeviceFaceChoices, InterfaceModeChoices
from .models import *
+from .models.device_components import PortMapping
from .object_actions import BulkAddComponents, BulkDisconnect
CABLE_TERMINATION_TYPES = {
@@ -221,6 +230,27 @@ class RegionListView(generic.ObjectListView):
@register_model_view(Region)
class RegionView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Region.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ NestedGroupObjectPanel(),
+ TagsPanel(),
+ CustomFieldsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ model='dcim.Region',
+ title=_('Child Regions'),
+ filters={'parent_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
+ ],
+ ),
+ ]
+ )
def get_extra_context(self, request, instance):
regions = instance.get_descendants(include_self=True)
@@ -295,6 +325,7 @@ class RegionBulkEditView(generic.BulkEditView):
@register_model_view(Region, 'bulk_rename', path='rename', detail=False)
class RegionBulkRenameView(generic.BulkRenameView):
queryset = Region.objects.all()
+ filterset = filtersets.RegionFilterSet
@register_model_view(Region, 'bulk_delete', path='delete', detail=False)
@@ -331,6 +362,27 @@ class SiteGroupListView(generic.ObjectListView):
@register_model_view(SiteGroup)
class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = SiteGroup.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ NestedGroupObjectPanel(),
+ TagsPanel(),
+ CustomFieldsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ model='dcim.SiteGroup',
+ title=_('Child Groups'),
+ filters={'parent_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
+ ],
+ ),
+ ]
+ )
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
@@ -426,6 +478,7 @@ class SiteGroupBulkEditView(generic.BulkEditView):
@register_model_view(SiteGroup, 'bulk_rename', path='rename', detail=False)
class SiteGroupBulkRenameView(generic.BulkRenameView):
queryset = SiteGroup.objects.all()
+ filterset = filtersets.SiteGroupFilterSet
@register_model_view(SiteGroup, 'bulk_delete', path='delete', detail=False)
@@ -459,6 +512,39 @@ class SiteListView(generic.ObjectListView):
@register_model_view(Site)
class SiteView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Site.objects.prefetch_related('tenant__group')
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.SitePanel(),
+ CustomFieldsPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ ImageAttachmentsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ model='dcim.Location',
+ filters={'site_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject('dcim.Location', url_params={'site': lambda ctx: ctx['object'].pk}),
+ ],
+ ),
+ ObjectsTablePanel(
+ model='dcim.Device',
+ title=_('Non-Racked Devices'),
+ filters={
+ 'site_id': lambda ctx: ctx['object'].pk,
+ 'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
+ 'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
+ },
+ actions=[
+ actions.AddObject('dcim.Device', url_params={'site': lambda ctx: ctx['object'].pk}),
+ ],
+ ),
+ ]
+ )
def get_extra_context(self, request, instance):
return {
@@ -516,6 +602,7 @@ class SiteBulkEditView(generic.BulkEditView):
@register_model_view(Site, 'bulk_rename', path='rename', detail=False)
class SiteBulkRenameView(generic.BulkRenameView):
queryset = Site.objects.all()
+ filterset = filtersets.SiteFilterSet
@register_model_view(Site, 'bulk_delete', path='delete', detail=False)
@@ -558,6 +645,52 @@ class LocationListView(generic.ObjectListView):
@register_model_view(Location)
class LocationView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Location.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.LocationPanel(),
+ TagsPanel(),
+ CustomFieldsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ ImageAttachmentsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ model='dcim.Location',
+ title=_('Child Locations'),
+ filters={'parent_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject(
+ 'dcim.Location',
+ url_params={
+ 'site': lambda ctx: ctx['object'].site_id,
+ 'parent': lambda ctx: ctx['object'].pk,
+ }
+ ),
+ ],
+ ),
+ ObjectsTablePanel(
+ model='dcim.Device',
+ title=_('Non-Racked Devices'),
+ filters={
+ 'location_id': lambda ctx: ctx['object'].pk,
+ 'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
+ 'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
+ },
+ actions=[
+ actions.AddObject(
+ 'dcim.Device',
+ url_params={
+ 'site': lambda ctx: ctx['object'].site_id,
+ 'parent': lambda ctx: ctx['object'].pk,
+ }
+ ),
+ ],
+ ),
+ ]
+ )
def get_extra_context(self, request, instance):
locations = instance.get_descendants(include_self=True)
@@ -625,6 +758,7 @@ class LocationBulkEditView(generic.BulkEditView):
@register_model_view(Location, 'bulk_rename', path='rename', detail=False)
class LocationBulkRenameView(generic.BulkRenameView):
queryset = Location.objects.all()
+ filterset = filtersets.LocationFilterSet
@register_model_view(Location, 'bulk_delete', path='delete', detail=False)
@@ -657,6 +791,17 @@ class RackRoleListView(generic.ObjectListView):
@register_model_view(RackRole)
class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
queryset = RackRole.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.RackRolePanel(),
+ TagsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ CustomFieldsPanel(),
+ CommentsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
return {
@@ -695,6 +840,7 @@ class RackRoleBulkEditView(generic.BulkEditView):
@register_model_view(RackRole, 'bulk_rename', path='rename', detail=False)
class RackRoleBulkRenameView(generic.BulkRenameView):
queryset = RackRole.objects.all()
+ filterset = filtersets.RackRoleFilterSet
@register_model_view(RackRole, 'bulk_delete', path='delete', detail=False)
@@ -712,9 +858,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
@register_model_view(RackType, 'list', path='', detail=False)
class RackTypeListView(generic.ObjectListView):
- queryset = RackType.objects.annotate(
- instance_count=count_related(Rack, 'rack_type')
- )
+ queryset = RackType.objects.all()
filterset = filtersets.RackTypeFilterSet
filterset_form = forms.RackTypeFilterForm
table = tables.RackTypeTable
@@ -722,7 +866,22 @@ class RackTypeListView(generic.ObjectListView):
@register_model_view(RackType)
class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
+ template_name = 'generic/object.html'
queryset = RackType.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.RackTypePanel(),
+ panels.RackDimensionsPanel(title=_('Dimensions')),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ panels.RackNumberingPanel(title=_('Numbering')),
+ panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']),
+ CustomFieldsPanel(),
+ RelatedObjectsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
return {
@@ -760,6 +919,7 @@ class RackTypeBulkEditView(generic.BulkEditView):
class RackTypeBulkRenameView(generic.BulkRenameView):
queryset = RackType.objects.all()
field_name = 'model'
+ filterset = filtersets.RackTypeFilterSet
@register_model_view(RackType, 'bulk_delete', path='delete', detail=False)
@@ -839,6 +999,22 @@ class RackElevationListView(generic.ObjectListView):
@register_model_view(Rack)
class RackView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.RackPanel(),
+ panels.RackDimensionsPanel(title=_('Dimensions')),
+ panels.RackNumberingPanel(title=_('Numbering')),
+ panels.RackWeightPanel(title=_('Weight')),
+ CustomFieldsPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ImageAttachmentsPanel(),
+ ],
+ right_panels=[
+ TemplatePanel('dcim/panels/rack_elevations.html'),
+ RelatedObjectsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
@@ -944,6 +1120,7 @@ class RackBulkEditView(generic.BulkEditView):
@register_model_view(Rack, 'bulk_rename', path='rename', detail=False)
class RackBulkRenameView(generic.BulkRenameView):
queryset = Rack.objects.all()
+ filterset = filtersets.RackFilterSet
@register_model_view(Rack, 'bulk_delete', path='delete', detail=False)
@@ -969,6 +1146,19 @@ class RackReservationListView(generic.ObjectListView):
@register_model_view(RackReservation)
class RackReservationView(generic.ObjectView):
queryset = RackReservation.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.RackPanel(accessor='object.rack', only=['region', 'site', 'location', 'name']),
+ panels.RackReservationPanel(title=_('Reservation')),
+ CustomFieldsPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ TemplatePanel(template_name='dcim/panels/rack_reservation_elevations.html'),
+ RelatedObjectsPanel(),
+ ],
+ )
@register_model_view(RackReservation, 'add', detail=False)
@@ -1042,6 +1232,10 @@ class ManufacturerListView(generic.ObjectListView):
@register_model_view(Manufacturer)
class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Manufacturer.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[OrganizationalObjectPanel(), TagsPanel()],
+ right_panels=[RelatedObjectsPanel(), CustomFieldsPanel(), CommentsPanel()],
+ )
def get_extra_context(self, request, instance):
return {
@@ -1083,6 +1277,7 @@ class ManufacturerBulkEditView(generic.BulkEditView):
@register_model_view(Manufacturer, 'bulk_rename', path='rename', detail=False)
class ManufacturerBulkRenameView(generic.BulkRenameView):
queryset = Manufacturer.objects.all()
+ filterset = filtersets.ManufacturerFilterSet
@register_model_view(Manufacturer, 'bulk_delete', path='delete', detail=False)
@@ -1103,9 +1298,7 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
@register_model_view(DeviceType, 'list', path='', detail=False)
class DeviceTypeListView(generic.ObjectListView):
- queryset = DeviceType.objects.annotate(
- instance_count=count_related(Device, 'device_type')
- )
+ queryset = DeviceType.objects.all()
filterset = filtersets.DeviceTypeFilterSet
filterset_form = forms.DeviceTypeFilterForm
table = tables.DeviceTypeTable
@@ -1114,6 +1307,18 @@ class DeviceTypeListView(generic.ObjectListView):
@register_model_view(DeviceType)
class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
queryset = DeviceType.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.DeviceTypePanel(),
+ TagsPanel(),
+ ],
+ right_panels=[
+ RelatedObjectsPanel(),
+ CustomFieldsPanel(),
+ CommentsPanel(),
+ ImageAttachmentsPanel(),
+ ],
+ )
def get_extra_context(self, request, instance):
return {
@@ -1312,6 +1517,7 @@ class DeviceTypeImportView(generic.BulkImportView):
'interfaces': forms.InterfaceTemplateImportForm,
'rear-ports': forms.RearPortTemplateImportForm,
'front-ports': forms.FrontPortTemplateImportForm,
+ 'port-mappings': forms.PortTemplateMappingImportForm,
'module-bays': forms.ModuleBayTemplateImportForm,
'device-bays': forms.DeviceBayTemplateImportForm,
'inventory-items': forms.InventoryItemTemplateImportForm,
@@ -1324,9 +1530,7 @@ class DeviceTypeImportView(generic.BulkImportView):
@register_model_view(DeviceType, 'bulk_edit', path='edit', detail=False)
class DeviceTypeBulkEditView(generic.BulkEditView):
- queryset = DeviceType.objects.annotate(
- instance_count=count_related(Device, 'device_type')
- )
+ queryset = DeviceType.objects.all()
filterset = filtersets.DeviceTypeFilterSet
table = tables.DeviceTypeTable
form = forms.DeviceTypeBulkEditForm
@@ -1336,13 +1540,12 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
class DeviceTypeBulkRenameView(generic.BulkRenameView):
queryset = DeviceType.objects.all()
field_name = 'model'
+ filterset = filtersets.DeviceTypeFilterSet
@register_model_view(DeviceType, 'bulk_delete', path='delete', detail=False)
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
- queryset = DeviceType.objects.annotate(
- instance_count=count_related(Device, 'device_type')
- )
+ queryset = DeviceType.objects.all()
filterset = filtersets.DeviceTypeFilterSet
table = tables.DeviceTypeTable
@@ -1363,7 +1566,36 @@ class ModuleTypeProfileListView(generic.ObjectListView):
@register_model_view(ModuleTypeProfile)
class ModuleTypeProfileView(GetRelatedModelsMixin, generic.ObjectView):
+ template_name = 'generic/object.html'
queryset = ModuleTypeProfile.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.ModuleTypeProfilePanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ],
+ right_panels=[
+ JSONPanel(field_name='schema', title=_('Schema')),
+ CustomFieldsPanel(),
+ ],
+ bottom_panels=[
+ ObjectsTablePanel(
+ model='dcim.ModuleType',
+ title=_('Module Types'),
+ filters={
+ 'profile_id': lambda ctx: ctx['object'].pk,
+ },
+ actions=[
+ actions.AddObject(
+ 'dcim.ModuleType',
+ url_params={
+ 'profile': lambda ctx: ctx['object'].pk,
+ }
+ ),
+ ],
+ ),
+ ]
+ )
@register_model_view(ModuleTypeProfile, 'add', detail=False)
@@ -1397,6 +1629,7 @@ class ModuleTypeProfileBulkEditView(generic.BulkEditView):
@register_model_view(ModuleTypeProfile, 'bulk_rename', path='rename', detail=False)
class ModuleTypeProfileBulkRenameView(generic.BulkRenameView):
queryset = ModuleTypeProfile.objects.all()
+ filterset = filtersets.ModuleTypeProfileFilterSet
@register_model_view(ModuleTypeProfile, 'bulk_delete', path='delete', detail=False)
@@ -1414,9 +1647,7 @@ class ModuleTypeProfileBulkDeleteView(generic.BulkDeleteView):
@register_model_view(ModuleType, 'list', path='', detail=False)
class ModuleTypeListView(generic.ObjectListView):
- queryset = ModuleType.objects.annotate(
- instance_count=count_related(Module, 'module_type')
- )
+ queryset = ModuleType.objects.all()
filterset = filtersets.ModuleTypeFilterSet
filterset_form = forms.ModuleTypeFilterForm
table = tables.ModuleTypeTable
@@ -1591,6 +1822,7 @@ class ModuleTypeImportView(generic.BulkImportView):
'interfaces': forms.InterfaceTemplateImportForm,
'rear-ports': forms.RearPortTemplateImportForm,
'front-ports': forms.FrontPortTemplateImportForm,
+ 'port-mappings': forms.PortTemplateMappingImportForm,
'module-bays': forms.ModuleBayTemplateImportForm,
}
@@ -1612,6 +1844,7 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
@register_model_view(ModuleType, 'bulk_rename', path='rename', detail=False)
class ModuleTypeBulkRenameView(generic.BulkRenameView):
queryset = ModuleType.objects.all()
+ filterset = filtersets.ModuleTypeFilterSet
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
@@ -2100,6 +2333,7 @@ class DeviceRoleBulkEditView(generic.BulkEditView):
@register_model_view(DeviceRole, 'bulk_rename', path='rename', detail=False)
class DeviceRoleBulkRenameView(generic.BulkRenameView):
queryset = DeviceRole.objects.all()
+ filterset = filtersets.DeviceRoleFilterSet
@register_model_view(DeviceRole, 'bulk_delete', path='delete', detail=False)
@@ -2175,6 +2409,7 @@ class PlatformBulkEditView(generic.BulkEditView):
@register_model_view(Platform, 'bulk_rename', path='rename', detail=False)
class PlatformBulkRenameView(generic.BulkRenameView):
queryset = Platform.objects.all()
+ filterset = filtersets.PlatformFilterSet
@register_model_view(Platform, 'bulk_delete', path='delete', detail=False)
@@ -2200,6 +2435,43 @@ class DeviceListView(generic.ObjectListView):
@register_model_view(Device)
class DeviceView(generic.ObjectView):
queryset = Device.objects.all()
+ layout = layout.SimpleLayout(
+ left_panels=[
+ panels.DevicePanel(),
+ panels.VirtualChassisMembersPanel(),
+ CustomFieldsPanel(),
+ TagsPanel(),
+ CommentsPanel(),
+ ObjectsTablePanel(
+ model='dcim.VirtualDeviceContext',
+ filters={'device_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject('dcim.VirtualDeviceContext', url_params={'device': lambda ctx: ctx['object'].pk}),
+ ],
+ ),
+ ],
+ right_panels=[
+ panels.DeviceManagementPanel(),
+ panels.PowerUtilizationPanel(),
+ ObjectsTablePanel(
+ model='ipam.Service',
+ title=_('Application Services'),
+ filters={'device_id': lambda ctx: ctx['object'].pk},
+ actions=[
+ actions.AddObject(
+ 'ipam.Service',
+ url_params={
+ 'parent_object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
+ 'parent': lambda ctx: ctx['object'].pk
+ }
+ ),
+ ],
+ ),
+ ImageAttachmentsPanel(),
+ panels.DeviceDimensionsPanel(),
+ TemplatePanel('dcim/panels/device_rack_elevations.html'),
+ ],
+ )
def get_extra_context(self, request, instance):
# VirtualChassis members
@@ -2212,7 +2484,7 @@ class DeviceView(generic.ObjectView):
return {
'vc_members': vc_members,
- 'svg_extra': f'highlight=id:{instance.pk}'
+ 'svg_extra': f'highlight=id:{instance.pk}',
}
@@ -2410,6 +2682,7 @@ class DeviceConfigContextView(ObjectConfigContextView):
class DeviceRenderConfigView(ObjectRenderConfigView):
queryset = Device.objects.all()
base_template = 'dcim/device/base.html'
+ additional_permissions = ['dcim.render_config_device']
tab = ViewTab(
label=_('Render Config'),
weight=2100,
@@ -2582,6 +2855,7 @@ class ConsolePortBulkEditView(generic.BulkEditView):
@register_model_view(ConsolePort, 'bulk_rename', path='rename', detail=False)
class ConsolePortBulkRenameView(generic.BulkRenameView):
queryset = ConsolePort.objects.all()
+ filterset = filtersets.ConsolePortFilterSet
@register_model_view(ConsolePort, 'bulk_disconnect', path='disconnect', detail=False)
@@ -2652,6 +2926,7 @@ class ConsoleServerPortBulkEditView(generic.BulkEditView):
@register_model_view(ConsoleServerPort, 'bulk_rename', path='rename', detail=False)
class ConsoleServerPortBulkRenameView(generic.BulkRenameView):
queryset = ConsoleServerPort.objects.all()
+ filterset = filtersets.ConsoleServerPortFilterSet
@register_model_view(ConsoleServerPort, 'bulk_disconnect', path='disconnect', detail=False)
@@ -2722,6 +2997,7 @@ class PowerPortBulkEditView(generic.BulkEditView):
@register_model_view(PowerPort, 'bulk_rename', path='rename', detail=False)
class PowerPortBulkRenameView(generic.BulkRenameView):
queryset = PowerPort.objects.all()
+ filterset = filtersets.PowerPortFilterSet
@register_model_view(PowerPort, 'bulk_disconnect', path='disconnect', detail=False)
@@ -2792,6 +3068,7 @@ class PowerOutletBulkEditView(generic.BulkEditView):
@register_model_view(PowerOutlet, 'bulk_rename', path='rename', detail=False)
class PowerOutletBulkRenameView(generic.BulkRenameView):
queryset = PowerOutlet.objects.all()
+ filterset = filtersets.PowerOutletFilterSet
@register_model_view(PowerOutlet, 'bulk_disconnect', path='disconnect', detail=False)
@@ -2934,6 +3211,7 @@ class InterfaceBulkEditView(generic.BulkEditView):
@register_model_view(Interface, 'bulk_rename', path='rename', detail=False)
class InterfaceBulkRenameView(generic.BulkRenameView):
queryset = Interface.objects.all()
+ filterset = filtersets.InterfaceFilterSet
@register_model_view(Interface, 'bulk_disconnect', path='disconnect', detail=False)
@@ -2969,6 +3247,11 @@ class FrontPortListView(generic.ObjectListView):
class FrontPortView(generic.ObjectView):
queryset = FrontPort.objects.all()
+ def get_extra_context(self, request, instance):
+ return {
+ 'rear_port_mappings': PortMapping.objects.filter(front_port=instance).prefetch_related('rear_port'),
+ }
+
@register_model_view(FrontPort, 'add', detail=False)
class FrontPortCreateView(generic.ComponentCreateView):
@@ -3005,6 +3288,7 @@ class FrontPortBulkEditView(generic.BulkEditView):
@register_model_view(FrontPort, 'bulk_rename', path='rename', detail=False)
class FrontPortBulkRenameView(generic.BulkRenameView):
queryset = FrontPort.objects.all()
+ filterset = filtersets.FrontPortFilterSet
@register_model_view(FrontPort, 'bulk_disconnect', path='disconnect', detail=False)
@@ -3039,6 +3323,11 @@ class RearPortListView(generic.ObjectListView):
class RearPortView(generic.ObjectView):
queryset = RearPort.objects.all()
+ def get_extra_context(self, request, instance):
+ return {
+ 'front_port_mappings': PortMapping.objects.filter(rear_port=instance).prefetch_related('front_port'),
+ }
+
@register_model_view(RearPort, 'add', detail=False)
class RearPortCreateView(generic.ComponentCreateView):
@@ -3080,6 +3369,7 @@ class RearPortBulkRenameView(generic.BulkRenameView):
@register_model_view(RearPort, 'bulk_disconnect', path='disconnect', detail=False)
class RearPortBulkDisconnectView(BulkDisconnectView):
queryset = RearPort.objects.all()
+ filterset = filtersets.RearPortFilterSet
@register_model_view(RearPort, 'bulk_delete', path='delete', detail=False)
@@ -3145,6 +3435,7 @@ class ModuleBayBulkEditView(generic.BulkEditView):
@register_model_view(ModuleBay, 'bulk_rename', path='rename', detail=False)
class ModuleBayBulkRenameView(generic.BulkRenameView):
queryset = ModuleBay.objects.all()
+ filterset = filtersets.ModuleBayFilterSet
@register_model_view(ModuleBay, 'bulk_delete', path='delete', detail=False)
@@ -3287,6 +3578,7 @@ class DeviceBayBulkEditView(generic.BulkEditView):
@register_model_view(DeviceBay, 'bulk_rename', path='rename', detail=False)
class DeviceBayBulkRenameView(generic.BulkRenameView):
queryset = DeviceBay.objects.all()
+ filterset = filtersets.DeviceBayFilterSet
@register_model_view(DeviceBay, 'bulk_delete', path='delete', detail=False)
@@ -3348,6 +3640,7 @@ class InventoryItemBulkEditView(generic.BulkEditView):
@register_model_view(InventoryItem, 'bulk_rename', path='rename', detail=False)
class InventoryItemBulkRenameView(generic.BulkRenameView):
queryset = InventoryItem.objects.all()
+ filterset = filtersets.InventoryItemFilterSet
@register_model_view(InventoryItem, 'bulk_delete', path='delete', detail=False)
@@ -3431,6 +3724,7 @@ class InventoryItemRoleBulkEditView(generic.BulkEditView):
@register_model_view(InventoryItemRole, 'bulk_rename', path='rename', detail=False)
class InventoryItemRoleBulkRenameView(generic.BulkRenameView):
queryset = InventoryItemRole.objects.all()
+ filterset = filtersets.InventoryItemRoleFilterSet
@register_model_view(InventoryItemRole, 'bulk_delete', path='delete', detail=False)
@@ -3634,6 +3928,7 @@ class CableBulkEditView(generic.BulkEditView):
class CableBulkRenameView(generic.BulkRenameView):
queryset = Cable.objects.all()
field_name = 'label'
+ filterset = filtersets.CableFilterSet
@register_model_view(Cable, 'bulk_delete', path='delete', detail=False)
@@ -3754,6 +4049,7 @@ class VirtualChassisEditView(ObjectPermissionRequiredMixin, GetReturnURLMixin, V
def post(self, request, pk):
virtual_chassis = get_object_or_404(self.queryset, pk=pk)
+ virtual_chassis.snapshot()
VCMemberFormSet = modelformset_factory(
model=Device,
form=forms.DeviceVCMembershipForm,
@@ -3806,9 +4102,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
return 'dcim.change_virtualchassis'
def get(self, request, pk):
-
virtual_chassis = get_object_or_404(self.queryset, pk=pk)
-
initial_data = {k: request.GET[k] for k in request.GET}
member_select_form = forms.VCMemberSelectForm(initial=initial_data)
membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
@@ -3821,20 +4115,20 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
})
def post(self, request, pk):
-
virtual_chassis = get_object_or_404(self.queryset, pk=pk)
-
member_select_form = forms.VCMemberSelectForm(request.POST)
if member_select_form.is_valid():
-
device = member_select_form.cleaned_data['device']
+ device.snapshot()
device.virtual_chassis = virtual_chassis
- data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']}
+ data = {
+ 'vc_position': request.POST['vc_position'],
+ 'vc_priority': request.POST['vc_priority'],
+ }
membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
if membership_form.is_valid():
-
membership_form.save()
messages.success(request, mark_safe(
_('Added member {device}').format(
@@ -3844,11 +4138,9 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
if '_addanother' in request.POST and safe_for_redirect(request.get_full_path()):
return redirect(request.get_full_path())
-
return redirect(self.get_return_url(request, device))
else:
-
membership_form = forms.DeviceVCMembershipForm(data=request.POST)
return render(request, 'dcim/virtualchassis_add_member.html', {
@@ -3866,7 +4158,6 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
return 'dcim.change_device'
def get(self, request, pk):
-
device = get_object_or_404(self.queryset, pk=pk, virtual_chassis__isnull=False)
form = ConfirmationForm(initial=request.GET)
@@ -3877,7 +4168,6 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
})
def post(self, request, pk):
-
device = get_object_or_404(self.queryset, pk=pk, virtual_chassis__isnull=False)
form = ConfirmationForm(request.POST)
@@ -3891,13 +4181,11 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
return redirect(device.get_absolute_url())
if form.is_valid():
-
- devices = Device.objects.filter(pk=device.pk)
- for device in devices:
- device.virtual_chassis = None
- device.vc_position = None
- device.vc_priority = None
- device.save()
+ device.snapshot()
+ device.virtual_chassis = None
+ device.vc_position = None
+ device.vc_priority = None
+ device.save()
msg = _('Removed {device} from virtual chassis {chassis}').format(
device=device,
@@ -3931,6 +4219,7 @@ class VirtualChassisBulkEditView(generic.BulkEditView):
@register_model_view(VirtualChassis, 'bulk_rename', path='rename', detail=False)
class VirtualChassisBulkRenameView(generic.BulkRenameView):
queryset = VirtualChassis.objects.all()
+ filterset = filtersets.VirtualChassisFilterSet
@register_model_view(VirtualChassis, 'bulk_delete', path='delete', detail=False)
@@ -3993,6 +4282,7 @@ class PowerPanelBulkEditView(generic.BulkEditView):
@register_model_view(PowerPanel, 'bulk_rename', path='rename', detail=False)
class PowerPanelBulkRenameView(generic.BulkRenameView):
queryset = PowerPanel.objects.all()
+ filterset = filtersets.PowerPanelFilterSet
@register_model_view(PowerPanel, 'bulk_delete', path='delete', detail=False)
@@ -4050,6 +4340,7 @@ class PowerFeedBulkEditView(generic.BulkEditView):
@register_model_view(PowerFeed, 'bulk_rename', path='rename', detail=False)
class PowerFeedBulkRenameView(generic.BulkRenameView):
queryset = PowerFeed.objects.all()
+ filterset = filtersets.PowerFeedFilterSet
@register_model_view(PowerFeed, 'bulk_disconnect', path='disconnect', detail=False)
@@ -4128,6 +4419,7 @@ class VirtualDeviceContextBulkEditView(generic.BulkEditView):
@register_model_view(VirtualDeviceContext, 'bulk_rename', path='rename', detail=False)
class VirtualDeviceContextBulkRenameView(generic.BulkRenameView):
queryset = VirtualDeviceContext.objects.all()
+ filterset = filtersets.VirtualDeviceContextFilterSet
@register_model_view(VirtualDeviceContext, 'bulk_delete', path='delete', detail=False)
diff --git a/netbox/extras/api/mixins.py b/netbox/extras/api/mixins.py
index aafdf32d4..ac4617bb8 100644
--- a/netbox/extras/api/mixins.py
+++ b/netbox/extras/api/mixins.py
@@ -4,6 +4,7 @@ from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST
+from netbox.api.authentication import TokenWritePermission
from netbox.api.renderers import TextRenderer
from .serializers import ConfigTemplateSerializer
@@ -64,12 +65,24 @@ class RenderConfigMixin(ConfigTemplateRenderMixin):
"""
Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned.
"""
+
+ def get_permissions(self):
+ # For render_config action, check only token write ability (not model permissions)
+ if self.action == 'render_config':
+ return [TokenWritePermission()]
+ return super().get_permissions()
+
@action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
def render_config(self, request, pk):
"""
Resolve and render the preferred ConfigTemplate for this Device.
"""
+ # Override restrict() on the default queryset to enforce the render_config & view actions
+ self.queryset = self.queryset.model.objects.restrict(request.user, 'render_config').restrict(
+ request.user, 'view'
+ )
instance = self.get_object()
+
object_type = instance._meta.model_name
configtemplate = instance.get_config_template()
if not configtemplate:
diff --git a/netbox/extras/api/serializers_/attachments.py b/netbox/extras/api/serializers_/attachments.py
index 6507a12be..613825203 100644
--- a/netbox/extras/api/serializers_/attachments.py
+++ b/netbox/extras/api/serializers_/attachments.py
@@ -1,12 +1,11 @@
from django.core.exceptions import ObjectDoesNotExist
-from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.models import ObjectType
from extras.models import ImageAttachment
from netbox.api.fields import ContentTypeField
+from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import ValidatedModelSerializer
-from utilities.api import get_serializer_for_model
__all__ = (
'ImageAttachmentSerializer',
@@ -17,7 +16,7 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
object_type = ContentTypeField(
queryset=ObjectType.objects.all()
)
- parent = serializers.SerializerMethodField(read_only=True)
+ parent = GFKSerializerField(read_only=True)
image_width = serializers.IntegerField(read_only=True)
image_height = serializers.IntegerField(read_only=True)
@@ -43,9 +42,3 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
super().validate(data)
return data
-
- @extend_schema_field(serializers.JSONField(allow_null=True))
- def get_parent(self, obj):
- serializer = get_serializer_for_model(obj.parent)
- context = {'request': self.context['request']}
- return serializer(obj.parent, nested=True, context=context).data
diff --git a/netbox/extras/api/serializers_/bookmarks.py b/netbox/extras/api/serializers_/bookmarks.py
index a404d83c3..a1c56f5f0 100644
--- a/netbox/extras/api/serializers_/bookmarks.py
+++ b/netbox/extras/api/serializers_/bookmarks.py
@@ -1,12 +1,9 @@
-from drf_spectacular.utils import extend_schema_field
-from rest_framework import serializers
-
from core.models import ObjectType
from extras.models import Bookmark
from netbox.api.fields import ContentTypeField
+from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import ValidatedModelSerializer
from users.api.serializers_.users import UserSerializer
-from utilities.api import get_serializer_for_model
__all__ = (
'BookmarkSerializer',
@@ -17,7 +14,7 @@ class BookmarkSerializer(ValidatedModelSerializer):
object_type = ContentTypeField(
queryset=ObjectType.objects.with_feature('bookmarks'),
)
- object = serializers.SerializerMethodField(read_only=True)
+ object = GFKSerializerField(read_only=True)
user = UserSerializer(nested=True)
class Meta:
@@ -26,9 +23,3 @@ class BookmarkSerializer(ValidatedModelSerializer):
'id', 'url', 'display', 'object_type', 'object_id', 'object', 'user', 'created',
]
brief_fields = ('id', 'url', 'display', 'object_id', 'object_type')
-
- @extend_schema_field(serializers.JSONField(allow_null=True))
- def get_object(self, instance):
- serializer = get_serializer_for_model(instance.object)
- context = {'request': self.context['request']}
- return serializer(instance.object, nested=True, context=context).data
diff --git a/netbox/extras/api/serializers_/configcontexts.py b/netbox/extras/api/serializers_/configcontexts.py
index ff85f0fc6..631dc461b 100644
--- a/netbox/extras/api/serializers_/configcontexts.py
+++ b/netbox/extras/api/serializers_/configcontexts.py
@@ -8,7 +8,8 @@ from dcim.api.serializers_.sites import LocationSerializer, RegionSerializer, Si
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.models import ConfigContext, ConfigContextProfile, Tag
from netbox.api.fields import SerializedPKRelatedField
-from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
+from netbox.api.serializers import ChangeLogMessageSerializer, PrimaryModelSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
from tenancy.api.serializers_.tenants import TenantSerializer, TenantGroupSerializer
from tenancy.models import Tenant, TenantGroup
from virtualization.api.serializers_.clusters import ClusterSerializer, ClusterGroupSerializer, ClusterTypeSerializer
@@ -20,13 +21,7 @@ __all__ = (
)
-class ConfigContextProfileSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
- tags = serializers.SlugRelatedField(
- queryset=Tag.objects.all(),
- slug_field='slug',
- required=False,
- many=True
- )
+class ConfigContextProfileSerializer(PrimaryModelSerializer):
data_source = DataSourceSerializer(
nested=True,
required=False
@@ -39,13 +34,13 @@ class ConfigContextProfileSerializer(ChangeLogMessageSerializer, ValidatedModelS
class Meta:
model = ConfigContextProfile
fields = [
- 'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'tags', 'comments', 'data_source',
- 'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
+ 'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'tags', 'owner', 'comments',
+ 'data_source', 'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
-class ConfigContextSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
profile = ConfigContextProfileSerializer(
nested=True,
required=False,
@@ -156,7 +151,7 @@ class ConfigContextSerializer(ChangeLogMessageSerializer, ValidatedModelSerializ
fields = [
'id', 'url', 'display_url', 'display', 'name', 'weight', 'profile', 'description', 'is_active', 'regions',
'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
- 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data_source', 'data_path', 'data_file',
- 'data_synced', 'data', 'created', 'last_updated',
+ 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'owner', 'tags', 'data_source', 'data_path',
+ 'data_file', 'data_synced', 'data', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
diff --git a/netbox/extras/api/serializers_/configtemplates.py b/netbox/extras/api/serializers_/configtemplates.py
index 244308535..ac9f40738 100644
--- a/netbox/extras/api/serializers_/configtemplates.py
+++ b/netbox/extras/api/serializers_/configtemplates.py
@@ -2,13 +2,19 @@ from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
from extras.models import ConfigTemplate
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
from netbox.api.serializers.features import TaggableModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'ConfigTemplateSerializer',
)
-class ConfigTemplateSerializer(ChangeLogMessageSerializer, TaggableModelSerializer, ValidatedModelSerializer):
+class ConfigTemplateSerializer(
+ OwnerMixin,
+ ChangeLogMessageSerializer,
+ TaggableModelSerializer,
+ ValidatedModelSerializer
+):
data_source = DataSourceSerializer(
nested=True,
required=False
@@ -23,6 +29,6 @@ class ConfigTemplateSerializer(ChangeLogMessageSerializer, TaggableModelSerializ
fields = [
'id', 'url', 'display_url', 'display', 'name', 'description', 'environment_params', 'template_code',
'mime_type', 'file_name', 'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file',
- 'data_synced', 'tags', 'created', 'last_updated',
+ 'auto_sync_enabled', 'data_synced', 'owner', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
diff --git a/netbox/extras/api/serializers_/customfields.py b/netbox/extras/api/serializers_/customfields.py
index f50f7a829..b12979439 100644
--- a/netbox/extras/api/serializers_/customfields.py
+++ b/netbox/extras/api/serializers_/customfields.py
@@ -8,6 +8,7 @@ from extras.choices import *
from extras.models import CustomField, CustomFieldChoiceSet
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'CustomFieldChoiceSetSerializer',
@@ -15,7 +16,7 @@ __all__ = (
)
-class CustomFieldChoiceSetSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class CustomFieldChoiceSetSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
base_choices = ChoiceField(
choices=CustomFieldChoiceSetBaseChoices,
required=False
@@ -26,17 +27,18 @@ class CustomFieldChoiceSetSerializer(ChangeLogMessageSerializer, ValidatedModelS
max_length=2
)
)
+ choices_count = serializers.IntegerField(read_only=True)
class Meta:
model = CustomFieldChoiceSet
fields = [
'id', 'url', 'display_url', 'display', 'name', 'description', 'base_choices', 'extra_choices',
- 'order_alphabetically', 'choices_count', 'created', 'last_updated',
+ 'order_alphabetically', 'choices_count', 'owner', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description', 'choices_count')
-class CustomFieldSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class CustomFieldSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.with_feature('custom_fields'),
many=True
@@ -63,8 +65,8 @@ class CustomFieldSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer
'id', 'url', 'display_url', 'display', 'object_types', 'type', 'related_object_type', 'data_type',
'name', 'label', 'group_name', 'description', 'required', 'unique', 'search_weight', 'filter_logic',
'ui_visible', 'ui_editable', 'is_cloneable', 'default', 'related_object_filter', 'weight',
- 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'comments', 'created',
- 'last_updated',
+ 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'owner', 'comments',
+ 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
diff --git a/netbox/extras/api/serializers_/customlinks.py b/netbox/extras/api/serializers_/customlinks.py
index 951c3aded..cca38f89f 100644
--- a/netbox/extras/api/serializers_/customlinks.py
+++ b/netbox/extras/api/serializers_/customlinks.py
@@ -2,13 +2,14 @@ from core.models import ObjectType
from extras.models import CustomLink
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'CustomLinkSerializer',
)
-class CustomLinkSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class CustomLinkSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.with_feature('custom_links'),
many=True
@@ -18,6 +19,6 @@ class CustomLinkSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer)
model = CustomLink
fields = [
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'enabled', 'link_text', 'link_url',
- 'weight', 'group_name', 'button_class', 'new_window', 'created', 'last_updated',
+ 'weight', 'group_name', 'button_class', 'new_window', 'owner', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name')
diff --git a/netbox/extras/api/serializers_/events.py b/netbox/extras/api/serializers_/events.py
index 926259cf3..0d72874e7 100644
--- a/netbox/extras/api/serializers_/events.py
+++ b/netbox/extras/api/serializers_/events.py
@@ -1,14 +1,10 @@
-from drf_spectacular.types import OpenApiTypes
-from drf_spectacular.utils import extend_schema_field
-from rest_framework import serializers
-
from core.models import ObjectType
from extras.choices import *
from extras.models import EventRule, Webhook
from netbox.api.fields import ChoiceField, ContentTypeField
+from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import NetBoxModelSerializer
-from utilities.api import get_serializer_for_model
-from .scripts import ScriptSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'EventRuleSerializer',
@@ -20,7 +16,7 @@ __all__ = (
# Event Rules
#
-class EventRuleSerializer(NetBoxModelSerializer):
+class EventRuleSerializer(OwnerMixin, NetBoxModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.with_feature('event_rules'),
many=True
@@ -29,40 +25,29 @@ class EventRuleSerializer(NetBoxModelSerializer):
action_object_type = ContentTypeField(
queryset=ObjectType.objects.with_feature('event_rules'),
)
- action_object = serializers.SerializerMethodField(read_only=True)
+ action_object = GFKSerializerField(read_only=True)
class Meta:
model = EventRule
fields = [
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'enabled', 'event_types', 'conditions',
'action_type', 'action_object_type', 'action_object_id', 'action_object', 'description', 'custom_fields',
- 'tags', 'created', 'last_updated',
+ 'owner', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
- @extend_schema_field(OpenApiTypes.OBJECT)
- def get_action_object(self, instance):
- context = {'request': self.context['request']}
- # We need to manually instantiate the serializer for scripts
- if instance.action_type == EventRuleActionChoices.SCRIPT:
- script = instance.action_object
- return ScriptSerializer(script, nested=True, context=context).data
- else:
- serializer = get_serializer_for_model(instance.action_object_type.model_class())
- return serializer(instance.action_object, nested=True, context=context).data
-
#
# Webhooks
#
-class WebhookSerializer(NetBoxModelSerializer):
+class WebhookSerializer(OwnerMixin, NetBoxModelSerializer):
class Meta:
model = Webhook
fields = [
'id', 'url', 'display_url', 'display', 'name', 'description', 'payload_url', 'http_method',
'http_content_type', 'additional_headers', 'body_template', 'secret', 'ssl_verification', 'ca_file_path',
- 'custom_fields', 'tags', 'created', 'last_updated',
+ 'custom_fields', 'owner', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
diff --git a/netbox/extras/api/serializers_/exporttemplates.py b/netbox/extras/api/serializers_/exporttemplates.py
index 0d3eed442..8c4e453d6 100644
--- a/netbox/extras/api/serializers_/exporttemplates.py
+++ b/netbox/extras/api/serializers_/exporttemplates.py
@@ -3,13 +3,14 @@ from core.models import ObjectType
from extras.models import ExportTemplate
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'ExportTemplateSerializer',
)
-class ExportTemplateSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class ExportTemplateSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.with_feature('export_templates'),
many=True
@@ -28,6 +29,6 @@ class ExportTemplateSerializer(ChangeLogMessageSerializer, ValidatedModelSeriali
fields = [
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'description', 'environment_params',
'template_code', 'mime_type', 'file_name', 'file_extension', 'as_attachment', 'data_source',
- 'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
+ 'data_path', 'data_file', 'data_synced', 'owner', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
diff --git a/netbox/extras/api/serializers_/journaling.py b/netbox/extras/api/serializers_/journaling.py
index cba56fc32..03ec34451 100644
--- a/netbox/extras/api/serializers_/journaling.py
+++ b/netbox/extras/api/serializers_/journaling.py
@@ -1,14 +1,13 @@
from django.core.exceptions import ObjectDoesNotExist
-from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.models import ObjectType
from extras.choices import *
from extras.models import JournalEntry
from netbox.api.fields import ChoiceField, ContentTypeField
+from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import NetBoxModelSerializer
from users.models import User
-from utilities.api import get_serializer_for_model
__all__ = (
'JournalEntrySerializer',
@@ -19,7 +18,7 @@ class JournalEntrySerializer(NetBoxModelSerializer):
assigned_object_type = ContentTypeField(
queryset=ObjectType.objects.all()
)
- assigned_object = serializers.SerializerMethodField(read_only=True)
+ assigned_object = GFKSerializerField(read_only=True)
created_by = serializers.PrimaryKeyRelatedField(
allow_null=True,
queryset=User.objects.all(),
@@ -51,9 +50,3 @@ class JournalEntrySerializer(NetBoxModelSerializer):
)
return super().validate(data)
-
- @extend_schema_field(serializers.JSONField(allow_null=True))
- def get_assigned_object(self, instance):
- serializer = get_serializer_for_model(instance.assigned_object_type.model_class())
- context = {'request': self.context['request']}
- return serializer(instance.assigned_object, nested=True, context=context).data
diff --git a/netbox/extras/api/serializers_/notifications.py b/netbox/extras/api/serializers_/notifications.py
index 9f0c7cff3..4c9d08169 100644
--- a/netbox/extras/api/serializers_/notifications.py
+++ b/netbox/extras/api/serializers_/notifications.py
@@ -1,13 +1,10 @@
-from drf_spectacular.utils import extend_schema_field
-from rest_framework import serializers
-
from core.models import ObjectType
from extras.models import Notification, NotificationGroup, Subscription
from netbox.api.fields import ContentTypeField, SerializedPKRelatedField
+from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
from users.api.serializers_.users import GroupSerializer, UserSerializer
from users.models import Group, User
-from utilities.api import get_serializer_for_model
__all__ = (
'NotificationSerializer',
@@ -20,7 +17,7 @@ class NotificationSerializer(ValidatedModelSerializer):
object_type = ContentTypeField(
queryset=ObjectType.objects.with_feature('notifications'),
)
- object = serializers.SerializerMethodField(read_only=True)
+ object = GFKSerializerField(read_only=True)
user = UserSerializer(nested=True)
class Meta:
@@ -30,12 +27,6 @@ class NotificationSerializer(ValidatedModelSerializer):
]
brief_fields = ('id', 'url', 'display', 'object_type', 'object_id', 'user', 'read', 'event_type')
- @extend_schema_field(serializers.JSONField(allow_null=True))
- def get_object(self, instance):
- serializer = get_serializer_for_model(instance.object)
- context = {'request': self.context['request']}
- return serializer(instance.object, nested=True, context=context).data
-
class NotificationGroupSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
groups = SerializedPKRelatedField(
@@ -65,7 +56,7 @@ class SubscriptionSerializer(ValidatedModelSerializer):
object_type = ContentTypeField(
queryset=ObjectType.objects.with_feature('notifications'),
)
- object = serializers.SerializerMethodField(read_only=True)
+ object = GFKSerializerField(read_only=True)
user = UserSerializer(nested=True)
class Meta:
@@ -74,9 +65,3 @@ class SubscriptionSerializer(ValidatedModelSerializer):
'id', 'url', 'display', 'object_type', 'object_id', 'object', 'user', 'created',
]
brief_fields = ('id', 'url', 'display', 'object_type', 'object_id', 'user')
-
- @extend_schema_field(serializers.JSONField(allow_null=True))
- def get_object(self, instance):
- serializer = get_serializer_for_model(instance.object)
- context = {'request': self.context['request']}
- return serializer(instance.object, nested=True, context=context).data
diff --git a/netbox/extras/api/serializers_/savedfilters.py b/netbox/extras/api/serializers_/savedfilters.py
index e7128389c..830453e6f 100644
--- a/netbox/extras/api/serializers_/savedfilters.py
+++ b/netbox/extras/api/serializers_/savedfilters.py
@@ -2,13 +2,14 @@ from core.models import ObjectType
from extras.models import SavedFilter
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
__all__ = (
'SavedFilterSerializer',
)
-class SavedFilterSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class SavedFilterSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.all(),
many=True
@@ -18,6 +19,6 @@ class SavedFilterSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer
model = SavedFilter
fields = [
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'slug', 'description', 'user', 'weight',
- 'enabled', 'shared', 'parameters', 'created', 'last_updated',
+ 'enabled', 'shared', 'parameters', 'owner', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')
diff --git a/netbox/extras/api/serializers_/scripts.py b/netbox/extras/api/serializers_/scripts.py
index aa0268ecf..9e41e64d1 100644
--- a/netbox/extras/api/serializers_/scripts.py
+++ b/netbox/extras/api/serializers_/scripts.py
@@ -5,6 +5,7 @@ from rest_framework import serializers
from core.api.serializers_.jobs import JobSerializer
from extras.models import Script
from netbox.api.serializers import ValidatedModelSerializer
+from utilities.datetime import local_now
__all__ = (
'ScriptDetailSerializer',
@@ -66,11 +67,31 @@ class ScriptInputSerializer(serializers.Serializer):
interval = serializers.IntegerField(required=False, allow_null=True)
def validate_schedule_at(self, value):
- if value and not self.context['script'].python_class.scheduling_enabled:
- raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
+ """
+ Validates the specified schedule time for a script execution.
+ """
+ if value:
+ if not self.context['script'].python_class.scheduling_enabled:
+ raise serializers.ValidationError(_('Scheduling is not enabled for this script.'))
+ if value < local_now():
+ raise serializers.ValidationError(_('Scheduled time must be in the future.'))
return value
def validate_interval(self, value):
+ """
+ Validates the provided interval based on the script's scheduling configuration.
+ """
if value and not self.context['script'].python_class.scheduling_enabled:
- raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
+ raise serializers.ValidationError(_('Scheduling is not enabled for this script.'))
return value
+
+ def validate(self, data):
+ """
+ Validates the given data and ensures the necessary fields are populated.
+ """
+ # Set the schedule_at time to now if only an interval is provided
+ # while handling the case where schedule_at is null.
+ if data.get('interval') and not data.get('schedule_at'):
+ data['schedule_at'] = local_now()
+
+ return super().validate(data)
diff --git a/netbox/extras/api/serializers_/tags.py b/netbox/extras/api/serializers_/tags.py
index 7567a4543..75ca4e9d2 100644
--- a/netbox/extras/api/serializers_/tags.py
+++ b/netbox/extras/api/serializers_/tags.py
@@ -6,6 +6,7 @@ from extras.models import Tag, TaggedItem
from netbox.api.exceptions import SerializerNotFound
from netbox.api.fields import ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import BaseModelSerializer, ChangeLogMessageSerializer, ValidatedModelSerializer
+from users.api.serializers_.mixins import OwnerMixin
from utilities.api import get_serializer_for_model
__all__ = (
@@ -14,7 +15,7 @@ __all__ = (
)
-class TagSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
+class TagSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
object_types = ContentTypeField(
queryset=ObjectType.objects.with_feature('tags'),
many=True,
diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py
index f333d5dbf..a9f6763cd 100644
--- a/netbox/extras/api/views.py
+++ b/netbox/extras/api/views.py
@@ -16,7 +16,7 @@ from rq import Worker
from extras import filtersets
from extras.jobs import ScriptJob
from extras.models import *
-from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
+from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired, TokenWritePermission
from netbox.api.features import SyncedDataMixin
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.renderers import TextRenderer
@@ -238,13 +238,22 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
serializer_class = serializers.ConfigTemplateSerializer
filterset_class = filtersets.ConfigTemplateFilterSet
+ def get_permissions(self):
+ # For render action, check only token write ability (not model permissions)
+ if self.action == 'render':
+ return [TokenWritePermission()]
+ return super().get_permissions()
+
@action(detail=True, methods=['post'], renderer_classes=[JSONRenderer, TextRenderer])
def render(self, request, pk):
"""
Render a ConfigTemplate using the context data provided (if any). If the client requests "text/plain" data,
return the raw rendered content, rather than serialized JSON.
"""
+ # Override restrict() on the default queryset to enforce the render & view actions
+ self.queryset = self.queryset.model.objects.restrict(request.user, 'render').restrict(request.user, 'view')
configtemplate = self.get_object()
+
context = request.data
return self.render_configtemplate(request, configtemplate, context)
@@ -267,6 +276,14 @@ class ScriptViewSet(ModelViewSet):
_ignore_model_permissions = True
lookup_value_regex = '[^/]+' # Allow dots
+ def initial(self, request, *args, **kwargs):
+ super().initial(request, *args, **kwargs)
+
+ # Restrict the view's QuerySet to allow only the permitted objects
+ if request.user.is_authenticated:
+ action = 'run' if request.method == 'POST' else 'view'
+ self.queryset = self.queryset.restrict(request.user, action)
+
def _get_script(self, pk):
# If pk is numeric, retrieve script by ID
if pk.isnumeric():
@@ -290,10 +307,12 @@ class ScriptViewSet(ModelViewSet):
"""
Run a Script identified by its numeric PK or module & name and return the pending Job as the result
"""
- if not request.user.has_perm('extras.run_script'):
- raise PermissionDenied("This user does not have permission to run scripts.")
script = self._get_script(pk)
+
+ if not request.user.has_perm('extras.run_script', obj=script):
+ raise PermissionDenied("This user does not have permission to run this script.")
+
input_serializer = serializers.ScriptInputSerializer(
data=request.data,
context={'script': script}
diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py
index f88490ad2..935e48051 100644
--- a/netbox/extras/dashboard/widgets.py
+++ b/netbox/extras/dashboard/widgets.py
@@ -209,7 +209,10 @@ class ObjectCountsWidget(DashboardWidget):
url = get_action_url(model, action='list')
except NoReverseMatch:
url = None
- qs = model.objects.restrict(request.user, 'view')
+ try:
+ qs = model.objects.restrict(request.user, 'view')
+ except AttributeError:
+ qs = model.objects.all()
# Apply any specified filters
if url and (filters := self.config.get('filters')):
params = dict_to_querydict(filters)
diff --git a/netbox/extras/events.py b/netbox/extras/events.py
index 9dac4ce45..e2912758a 100644
--- a/netbox/extras/events.py
+++ b/netbox/extras/events.py
@@ -134,11 +134,18 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
# Enqueue a Job to record the script's execution
from extras.jobs import ScriptJob
+ params = {
+ "instance": event_rule.action_object,
+ "name": script.name,
+ "user": user,
+ "data": event_data
+ }
+ if snapshots:
+ params["snapshots"] = snapshots
+ if request:
+ params["request"] = copy_safe_request(request)
ScriptJob.enqueue(
- instance=event_rule.action_object,
- name=script.name,
- user=user,
- data=event_data
+ **params
)
# Notification groups
diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py
index f34b21370..cdc0c75c5 100644
--- a/netbox/extras/filtersets.py
+++ b/netbox/extras/filtersets.py
@@ -5,12 +5,14 @@ from django.utils.translation import gettext as _
from core.models import DataSource, ObjectType
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
-from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
+from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet, PrimaryModelFilterSet
from tenancy.models import Tenant, TenantGroup
+from users.filterset_mixins import OwnerFilterMixin
from users.models import Group, User
from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
)
+from utilities.filtersets import register_filterset
from virtualization.models import Cluster, ClusterGroup, ClusterType
from .choices import *
from .filters import TagFilter, TagIDFilter
@@ -39,6 +41,7 @@ __all__ = (
)
+@register_filterset
class ScriptFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',
@@ -61,7 +64,8 @@ class ScriptFilterSet(BaseFilterSet):
)
-class WebhookFilterSet(NetBoxModelFilterSet):
+@register_filterset
+class WebhookFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -90,7 +94,8 @@ class WebhookFilterSet(NetBoxModelFilterSet):
)
-class EventRuleFilterSet(NetBoxModelFilterSet):
+@register_filterset
+class EventRuleFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -130,7 +135,8 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
return queryset.filter(event_types__overlap=value)
-class CustomFieldFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class CustomFieldFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -179,7 +185,8 @@ class CustomFieldFilterSet(ChangeLoggedModelFilterSet):
)
-class CustomFieldChoiceSetFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class CustomFieldChoiceSetFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -207,7 +214,8 @@ class CustomFieldChoiceSetFilterSet(ChangeLoggedModelFilterSet):
return queryset.filter(extra_choices__overlap=value)
-class CustomLinkFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class CustomLinkFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -237,7 +245,8 @@ class CustomLinkFilterSet(ChangeLoggedModelFilterSet):
)
-class ExportTemplateFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class ExportTemplateFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -275,7 +284,8 @@ class ExportTemplateFilterSet(ChangeLoggedModelFilterSet):
)
-class SavedFilterFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class SavedFilterFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -327,6 +337,7 @@ class SavedFilterFilterSet(ChangeLoggedModelFilterSet):
return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user)))
+@register_filterset
class TableConfigFilterSet(ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
@@ -380,6 +391,7 @@ class TableConfigFilterSet(ChangeLoggedModelFilterSet):
return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user)))
+@register_filterset
class BookmarkFilterSet(BaseFilterSet):
created = django_filters.DateTimeFilter()
object_type_id = MultiValueNumberFilter()
@@ -400,6 +412,7 @@ class BookmarkFilterSet(BaseFilterSet):
fields = ('id', 'object_id')
+@register_filterset
class NotificationGroupFilterSet(ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
@@ -443,6 +456,7 @@ class NotificationGroupFilterSet(ChangeLoggedModelFilterSet):
)
+@register_filterset
class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
@@ -464,6 +478,7 @@ class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
)
+@register_filterset
class JournalEntryFilterSet(NetBoxModelFilterSet):
created = django_filters.DateTimeFromToRangeFilter()
assigned_object_type = ContentTypeFilter()
@@ -494,7 +509,8 @@ class JournalEntryFilterSet(NetBoxModelFilterSet):
return queryset.filter(comments__icontains=value)
-class TagFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class TagFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -554,6 +570,7 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
)
+@register_filterset
class TaggedItemFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',
@@ -589,7 +606,8 @@ class TaggedItemFilterSet(BaseFilterSet):
)
-class ConfigContextProfileFilterSet(NetBoxModelFilterSet):
+@register_filterset
+class ConfigContextProfileFilterSet(PrimaryModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -619,7 +637,8 @@ class ConfigContextProfileFilterSet(NetBoxModelFilterSet):
)
-class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class ConfigContextFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
@@ -788,7 +807,8 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
)
-class ConfigTemplateFilterSet(ChangeLoggedModelFilterSet):
+@register_filterset
+class ConfigTemplateFilterSet(OwnerFilterMixin, ChangeLoggedModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py
index c0a210e42..6805898fc 100644
--- a/netbox/extras/forms/bulk_edit.py
+++ b/netbox/extras/forms/bulk_edit.py
@@ -4,8 +4,8 @@ from django.utils.translation import gettext_lazy as _
from extras.choices import *
from extras.models import *
from netbox.events import get_event_type_choices
-from netbox.forms import NetBoxModelBulkEditForm
-from netbox.forms.mixins import ChangelogMessageMixin
+from netbox.forms import NetBoxModelBulkEditForm, PrimaryModelBulkEditForm
+from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
from utilities.forms import BulkEditForm, add_blank_choice
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField
from utilities.forms.rendering import FieldSet
@@ -30,7 +30,7 @@ __all__ = (
)
-class CustomFieldBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class CustomFieldBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=CustomField.objects.all(),
widget=forms.MultipleHiddenInput
@@ -76,11 +76,11 @@ class CustomFieldBulkEditForm(ChangelogMessageMixin, BulkEditForm):
required=False,
widget=BulkEditNullBooleanSelect()
)
- validation_minimum = forms.IntegerField(
+ validation_minimum = forms.DecimalField(
label=_('Minimum value'),
required=False,
)
- validation_maximum = forms.IntegerField(
+ validation_maximum = forms.DecimalField(
label=_('Maximum value'),
required=False,
)
@@ -98,7 +98,7 @@ class CustomFieldBulkEditForm(ChangelogMessageMixin, BulkEditForm):
nullable_fields = ('group_name', 'description', 'choice_set')
-class CustomFieldChoiceSetBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class CustomFieldChoiceSetBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=CustomFieldChoiceSet.objects.all(),
widget=forms.MultipleHiddenInput
@@ -118,7 +118,7 @@ class CustomFieldChoiceSetBulkEditForm(ChangelogMessageMixin, BulkEditForm):
nullable_fields = ('base_choices', 'description')
-class CustomLinkBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class CustomLinkBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=CustomLink.objects.all(),
widget=forms.MultipleHiddenInput
@@ -144,7 +144,7 @@ class CustomLinkBulkEditForm(ChangelogMessageMixin, BulkEditForm):
)
-class ExportTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class ExportTemplateBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ExportTemplate.objects.all(),
widget=forms.MultipleHiddenInput
@@ -177,7 +177,7 @@ class ExportTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension')
-class SavedFilterBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class SavedFilterBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SavedFilter.objects.all(),
widget=forms.MultipleHiddenInput
@@ -233,7 +233,7 @@ class TableConfigBulkEditForm(BulkEditForm):
nullable_fields = ('description',)
-class WebhookBulkEditForm(NetBoxModelBulkEditForm):
+class WebhookBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
model = Webhook
pk = forms.ModelMultipleChoiceField(
@@ -271,7 +271,7 @@ class WebhookBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('secret', 'ca_file_path')
-class EventRuleBulkEditForm(NetBoxModelBulkEditForm):
+class EventRuleBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
model = EventRule
pk = forms.ModelMultipleChoiceField(
@@ -297,7 +297,7 @@ class EventRuleBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('description', 'conditions')
-class TagBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class TagBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(),
widget=forms.MultipleHiddenInput
@@ -319,17 +319,11 @@ class TagBulkEditForm(ChangelogMessageMixin, BulkEditForm):
nullable_fields = ('description',)
-class ConfigContextProfileBulkEditForm(NetBoxModelBulkEditForm):
+class ConfigContextProfileBulkEditForm(PrimaryModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ConfigContextProfile.objects.all(),
widget=forms.MultipleHiddenInput
)
- description = forms.CharField(
- label=_('Description'),
- required=False,
- max_length=100
- )
- comments = CommentField()
model = ConfigContextProfile
fieldsets = (
@@ -338,7 +332,7 @@ class ConfigContextProfileBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('description',)
-class ConfigContextBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class ConfigContextBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ConfigContext.objects.all(),
widget=forms.MultipleHiddenInput
@@ -369,7 +363,7 @@ class ConfigContextBulkEditForm(ChangelogMessageMixin, BulkEditForm):
nullable_fields = ('profile', 'description')
-class ConfigTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
+class ConfigTemplateBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ConfigTemplate.objects.all(),
widget=forms.MultipleHiddenInput
@@ -398,8 +392,12 @@ class ConfigTemplateBulkEditForm(ChangelogMessageMixin, BulkEditForm):
required=False,
widget=BulkEditNullBooleanSelect()
)
-
- nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension')
+ auto_sync_enabled = forms.NullBooleanField(
+ label=_('Auto sync enabled'),
+ required=False,
+ widget=BulkEditNullBooleanSelect()
+ )
+ nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension', 'auto_sync_enabled',)
class ImageAttachmentBulkEditForm(ChangelogMessageMixin, BulkEditForm):
diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py
index 4f7c85e85..afe0de5d1 100644
--- a/netbox/extras/forms/bulk_import.py
+++ b/netbox/extras/forms/bulk_import.py
@@ -5,11 +5,11 @@ from django.contrib.postgres.forms import SimpleArrayField
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
-from core.models import ObjectType
+from core.models import DataFile, DataSource, ObjectType
from extras.choices import *
from extras.models import *
from netbox.events import get_event_type_choices
-from netbox.forms import NetBoxModelImportForm
+from netbox.forms import NetBoxModelImportForm, OwnerCSVMixin, PrimaryModelImportForm
from users.models import Group, User
from utilities.forms import CSVModelForm
from utilities.forms.fields import (
@@ -33,7 +33,7 @@ __all__ = (
)
-class CustomFieldImportForm(CSVModelForm):
+class CustomFieldImportForm(OwnerCSVMixin, CSVModelForm):
object_types = CSVMultipleContentTypeField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('custom_fields'),
@@ -75,11 +75,11 @@ class CustomFieldImportForm(CSVModelForm):
fields = (
'name', 'label', 'group_name', 'type', 'object_types', 'related_object_type', 'required', 'unique',
'description', 'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
- 'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable', 'comments',
+ 'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable', 'owner', 'comments',
)
-class CustomFieldChoiceSetImportForm(CSVModelForm):
+class CustomFieldChoiceSetImportForm(OwnerCSVMixin, CSVModelForm):
base_choices = CSVChoiceField(
choices=CustomFieldChoiceSetBaseChoices,
required=False,
@@ -97,7 +97,7 @@ class CustomFieldChoiceSetImportForm(CSVModelForm):
class Meta:
model = CustomFieldChoiceSet
fields = (
- 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically',
+ 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically', 'owner',
)
def clean_extra_choices(self):
@@ -114,7 +114,7 @@ class CustomFieldChoiceSetImportForm(CSVModelForm):
return data
-class CustomLinkImportForm(CSVModelForm):
+class CustomLinkImportForm(OwnerCSVMixin, CSVModelForm):
object_types = CSVMultipleContentTypeField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('custom_links'),
@@ -131,11 +131,11 @@ class CustomLinkImportForm(CSVModelForm):
model = CustomLink
fields = (
'name', 'object_types', 'enabled', 'weight', 'group_name', 'button_class', 'new_window', 'link_text',
- 'link_url',
+ 'link_url', 'owner',
)
-class ExportTemplateImportForm(CSVModelForm):
+class ExportTemplateImportForm(OwnerCSVMixin, CSVModelForm):
object_types = CSVMultipleContentTypeField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('export_templates'),
@@ -146,30 +146,57 @@ class ExportTemplateImportForm(CSVModelForm):
model = ExportTemplate
fields = (
'name', 'object_types', 'description', 'environment_params', 'mime_type', 'file_name', 'file_extension',
- 'as_attachment', 'template_code',
+ 'as_attachment', 'template_code', 'owner',
)
-class ConfigContextProfileImportForm(NetBoxModelImportForm):
+class ConfigContextProfileImportForm(PrimaryModelImportForm):
class Meta:
model = ConfigContextProfile
fields = [
- 'name', 'description', 'schema', 'comments', 'tags',
+ 'name', 'description', 'schema', 'owner', 'comments', 'tags',
]
-class ConfigTemplateImportForm(CSVModelForm):
+class ConfigTemplateImportForm(OwnerCSVMixin, CSVModelForm):
+ data_source = CSVModelChoiceField(
+ label=_('Data source'),
+ queryset=DataSource.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text=_('Data source which provides the data file')
+ )
+ data_file = CSVModelChoiceField(
+ label=_('Data file'),
+ queryset=DataFile.objects.all(),
+ required=False,
+ to_field_name='path',
+ help_text=_('Data file containing the template code')
+ )
+ auto_sync_enabled = forms.BooleanField(
+ required=False,
+ label=_('Auto sync enabled'),
+ help_text=_("Enable automatic synchronization of template content when the data file is updated")
+ )
class Meta:
model = ConfigTemplate
fields = (
- 'name', 'description', 'template_code', 'environment_params', 'mime_type', 'file_name', 'file_extension',
- 'as_attachment', 'tags',
+ 'name', 'description', 'template_code', 'data_source', 'data_file', 'auto_sync_enabled',
+ 'environment_params', 'mime_type', 'file_name', 'file_extension', 'as_attachment', 'owner', 'tags',
)
+ def clean(self):
+ super().clean()
-class SavedFilterImportForm(CSVModelForm):
+ # Make sure template_code is None when it's not included in the uploaded data
+ if not self.data.get('template_code') and not self.data.get('data_file'):
+ raise forms.ValidationError(_("Must specify either local content or a data file"))
+ return self.cleaned_data['template_code']
+
+
+class SavedFilterImportForm(OwnerCSVMixin, CSVModelForm):
object_types = CSVMultipleContentTypeField(
label=_('Object types'),
queryset=ObjectType.objects.all(),
@@ -179,21 +206,21 @@ class SavedFilterImportForm(CSVModelForm):
class Meta:
model = SavedFilter
fields = (
- 'name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared', 'parameters',
+ 'name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared', 'parameters', 'owner',
)
-class WebhookImportForm(NetBoxModelImportForm):
+class WebhookImportForm(OwnerCSVMixin, NetBoxModelImportForm):
class Meta:
model = Webhook
fields = (
'name', 'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template',
- 'secret', 'ssl_verification', 'ca_file_path', 'description', 'tags'
+ 'secret', 'ssl_verification', 'ca_file_path', 'description', 'owner', 'tags'
)
-class EventRuleImportForm(NetBoxModelImportForm):
+class EventRuleImportForm(OwnerCSVMixin, NetBoxModelImportForm):
object_types = CSVMultipleContentTypeField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('event_rules'),
@@ -214,7 +241,7 @@ class EventRuleImportForm(NetBoxModelImportForm):
model = EventRule
fields = (
'name', 'description', 'enabled', 'conditions', 'object_types', 'event_types', 'action_type',
- 'comments', 'tags'
+ 'owner', 'comments', 'tags'
)
def clean(self):
@@ -242,7 +269,7 @@ class EventRuleImportForm(NetBoxModelImportForm):
self.instance.action_object_type = ObjectType.objects.get_for_model(script, for_concrete_model=False)
-class TagImportForm(CSVModelForm):
+class TagImportForm(OwnerCSVMixin, CSVModelForm):
slug = SlugField()
weight = forms.IntegerField(
label=_('Weight'),
@@ -258,7 +285,7 @@ class TagImportForm(CSVModelForm):
class Meta:
model = Tag
fields = (
- 'name', 'slug', 'color', 'weight', 'description', 'object_types',
+ 'name', 'slug', 'color', 'weight', 'description', 'object_types', 'owner',
)
@@ -272,6 +299,10 @@ class JournalEntryImportForm(NetBoxModelImportForm):
choices=JournalEntryKindChoices,
help_text=_('The classification of entry')
)
+ comments = forms.CharField(
+ label=_('Comments'),
+ required=True
+ )
class Meta:
model = JournalEntry
diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py
index 675315bed..0a3036597 100644
--- a/netbox/extras/forms/filtersets.py
+++ b/netbox/extras/forms/filtersets.py
@@ -6,13 +6,14 @@ from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site
from extras.choices import *
from extras.models import *
from netbox.events import get_event_type_choices
-from netbox.forms.base import NetBoxModelFilterSetForm
+from netbox.forms import NetBoxModelFilterSetForm, PrimaryModelFilterSetForm
from netbox.forms.mixins import SavedFiltersMixin
from tenancy.models import Tenant, TenantGroup
-from users.models import Group, User
+from users.models import Group, Owner, User
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
from utilities.forms.fields import (
- ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
+ ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
+ TagFilterField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import DateTimePicker
@@ -42,17 +43,20 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
model = CustomField
fieldsets = (
FieldSet('q', 'filter_id'),
- FieldSet(
- 'type', 'related_object_type_id', 'group_name', 'weight', 'required', 'unique', 'choice_set_id',
- name=_('Attributes')
- ),
+ FieldSet('object_type_id', 'type', 'group_name', 'weight', 'required', 'unique', name=_('Attributes')),
+ FieldSet('choice_set_id', 'related_object_type_id', name=_('Type Options')),
FieldSet('ui_visible', 'ui_editable', 'is_cloneable', name=_('Behavior')),
FieldSet('validation_minimum', 'validation_maximum', 'validation_regex', name=_('Validation')),
)
- related_object_type_id = ContentTypeMultipleChoiceField(
+ object_type_id = ContentTypeMultipleChoiceField(
queryset=ObjectType.objects.with_feature('custom_fields'),
required=False,
- label=_('Related object type')
+ label=_('Object types'),
+ )
+ related_object_type_id = ContentTypeMultipleChoiceField(
+ queryset=ObjectType.objects.public(),
+ required=False,
+ label=_('Related object type'),
)
type = forms.MultipleChoiceField(
choices=CustomFieldTypeChoices,
@@ -103,11 +107,11 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
- validation_minimum = forms.IntegerField(
+ validation_minimum = forms.DecimalField(
label=_('Minimum value'),
required=False
)
- validation_maximum = forms.IntegerField(
+ validation_maximum = forms.DecimalField(
label=_('Maximum value'),
required=False
)
@@ -115,6 +119,11 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
label=_('Validation regex'),
required=False
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
@@ -130,18 +139,23 @@ class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
choice = forms.CharField(
required=False
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
model = CustomLink
fieldsets = (
FieldSet('q', 'filter_id'),
- FieldSet('object_type', 'enabled', 'new_window', 'weight', name=_('Attributes')),
+ FieldSet('object_type_id', 'enabled', 'new_window', 'weight', name=_('Attributes')),
)
- object_type = ContentTypeMultipleChoiceField(
+ object_type_id = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('custom_links'),
- required=False
+ required=False,
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
@@ -161,6 +175,11 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
label=_('Weight'),
required=False
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
@@ -207,6 +226,11 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
@@ -230,12 +254,12 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
model = SavedFilter
fieldsets = (
FieldSet('q', 'filter_id'),
- FieldSet('object_type', 'enabled', 'shared', 'weight', name=_('Attributes')),
+ FieldSet('object_type_id', 'enabled', 'shared', 'weight', name=_('Attributes')),
)
- object_type = ContentTypeMultipleChoiceField(
+ object_type_id = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.public(),
- required=False
+ required=False,
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
@@ -255,9 +279,15 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
label=_('Weight'),
required=False
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class TableConfigFilterForm(SavedFiltersMixin, FilterForm):
+ model = TableConfig
fieldsets = (
FieldSet('q', 'filter_id'),
FieldSet('object_type_id', 'enabled', 'shared', 'weight', name=_('Attributes')),
@@ -290,7 +320,7 @@ class TableConfigFilterForm(SavedFiltersMixin, FilterForm):
class WebhookFilterForm(NetBoxModelFilterSetForm):
model = Webhook
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('payload_url', 'http_method', 'http_content_type', name=_('Attributes')),
)
http_content_type = forms.CharField(
@@ -306,15 +336,18 @@ class WebhookFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('HTTP method')
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
tag = TagFilterField(model)
class EventRuleFilterForm(NetBoxModelFilterSetForm):
model = EventRule
- tag = TagFilterField(model)
-
fieldsets = (
- FieldSet('q', 'filter_id', 'tag'),
+ FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('object_type_id', 'event_type', 'action_type', 'enabled', name=_('Attributes')),
)
object_type_id = ContentTypeMultipleChoiceField(
@@ -339,6 +372,12 @@ class EventRuleFilterForm(NetBoxModelFilterSetForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
+ tag = TagFilterField(model)
class TagFilterForm(SavedFiltersMixin, FilterForm):
@@ -353,9 +392,14 @@ class TagFilterForm(SavedFiltersMixin, FilterForm):
required=False,
label=_('Allowed object type')
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
-class ConfigContextProfileFilterForm(SavedFiltersMixin, FilterForm):
+class ConfigContextProfileFilterForm(PrimaryModelFilterSetForm):
model = ConfigContextProfile
fieldsets = (
FieldSet('q', 'filter_id'),
@@ -470,13 +514,18 @@ class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
required=False,
label=_('Tags')
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
model = ConfigTemplate
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
- FieldSet('data_source_id', 'data_file_id', name=_('Data')),
+ FieldSet('data_source_id', 'data_file_id', 'auto_sync_enabled', name=_('Data')),
FieldSet('mime_type', 'file_name', 'file_extension', 'as_attachment', name=_('Rendering'))
)
data_source_id = DynamicModelMultipleChoiceField(
@@ -492,6 +541,13 @@ class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
'source_id': '$data_source_id'
}
)
+ auto_sync_enabled = forms.NullBooleanField(
+ label=_('Auto sync enabled'),
+ required=False,
+ widget=forms.Select(
+ choices=BOOLEAN_WITH_BLANK_CHOICES
+ )
+ )
tag = TagFilterField(ConfigTemplate)
mime_type = forms.CharField(
required=False,
@@ -512,6 +568,11 @@ class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
+ owner_id = DynamicModelChoiceField(
+ queryset=Owner.objects.all(),
+ required=False,
+ label=_('Owner'),
+ )
class LocalConfigContextFilterForm(forms.Form):
diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py
index 37ee10604..ba745fa94 100644
--- a/netbox/extras/forms/model_forms.py
+++ b/netbox/extras/forms/model_forms.py
@@ -12,8 +12,8 @@ from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site
from extras.choices import *
from extras.models import *
from netbox.events import get_event_type_choices
-from netbox.forms import NetBoxModelForm
-from netbox.forms.mixins import ChangelogMessageMixin
+from netbox.forms import NetBoxModelForm, PrimaryModelForm
+from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
from tenancy.models import Tenant, TenantGroup
from users.models import Group, User
from utilities.forms import get_field_value
@@ -47,7 +47,7 @@ __all__ = (
)
-class CustomFieldForm(ChangelogMessageMixin, forms.ModelForm):
+class CustomFieldForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('custom_fields'),
@@ -166,7 +166,7 @@ class CustomFieldForm(ChangelogMessageMixin, forms.ModelForm):
del self.fields['choice_set']
-class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm):
+class CustomFieldChoiceSetForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
# TODO: The extra_choices field definition diverge from the CustomFieldChoiceSet model
extra_choices = forms.CharField(
widget=ChoicesWidget(),
@@ -179,7 +179,7 @@ class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm):
class Meta:
model = CustomFieldChoiceSet
- fields = ('name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically')
+ fields = ('name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically', 'owner')
def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
@@ -219,7 +219,7 @@ class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm):
return data
-class CustomLinkForm(ChangelogMessageMixin, forms.ModelForm):
+class CustomLinkForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('custom_links')
@@ -251,7 +251,7 @@ class CustomLinkForm(ChangelogMessageMixin, forms.ModelForm):
}
-class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm):
+class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm):
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('export_templates')
@@ -293,7 +293,7 @@ class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm
return self.cleaned_data
-class SavedFilterForm(ChangelogMessageMixin, forms.ModelForm):
+class SavedFilterForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
slug = SlugField()
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
@@ -427,7 +427,7 @@ class SubscriptionForm(forms.ModelForm):
fields = ('object_type', 'object_id')
-class WebhookForm(NetBoxModelForm):
+class WebhookForm(OwnerMixin, NetBoxModelForm):
fieldsets = (
FieldSet('name', 'description', 'tags', name=_('Webhook')),
@@ -447,7 +447,7 @@ class WebhookForm(NetBoxModelForm):
}
-class EventRuleForm(NetBoxModelForm):
+class EventRuleForm(OwnerMixin, NetBoxModelForm):
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ObjectType.objects.with_feature('event_rules'),
@@ -480,7 +480,7 @@ class EventRuleForm(NetBoxModelForm):
model = EventRule
fields = (
'object_types', 'name', 'description', 'enabled', 'event_types', 'conditions', 'action_type',
- 'action_object_type', 'action_object_id', 'action_data', 'comments', 'tags'
+ 'action_object_type', 'action_object_id', 'action_data', 'owner', 'comments', 'tags'
)
widgets = {
'conditions': forms.Textarea(attrs={'class': 'font-monospace'}),
@@ -563,7 +563,7 @@ class EventRuleForm(NetBoxModelForm):
return self.cleaned_data
-class TagForm(ChangelogMessageMixin, forms.ModelForm):
+class TagForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
slug = SlugField()
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
@@ -582,11 +582,11 @@ class TagForm(ChangelogMessageMixin, forms.ModelForm):
class Meta:
model = Tag
fields = [
- 'name', 'slug', 'color', 'weight', 'description', 'object_types',
+ 'name', 'slug', 'color', 'weight', 'description', 'object_types', 'owner',
]
-class ConfigContextProfileForm(SyncedDataMixin, NetBoxModelForm):
+class ConfigContextProfileForm(SyncedDataMixin, PrimaryModelForm):
schema = JSONField(
label=_('Schema'),
required=False,
@@ -606,11 +606,12 @@ class ConfigContextProfileForm(SyncedDataMixin, NetBoxModelForm):
class Meta:
model = ConfigContextProfile
fields = (
- 'name', 'description', 'schema', 'data_source', 'data_file', 'auto_sync_enabled', 'comments', 'tags',
+ 'name', 'description', 'schema', 'data_source', 'data_file', 'auto_sync_enabled', 'owner', 'comments',
+ 'tags',
)
-class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm):
+class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm):
profile = DynamicModelChoiceField(
label=_('Profile'),
queryset=ConfigContextProfile.objects.all(),
@@ -701,7 +702,7 @@ class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm)
fields = (
'name', 'weight', 'profile', 'description', 'data', 'is_active', 'regions', 'site_groups', 'sites',
'locations', 'roles', 'device_types', 'platforms', 'cluster_types', 'cluster_groups', 'clusters',
- 'tenant_groups', 'tenants', 'tags', 'data_source', 'data_file', 'auto_sync_enabled',
+ 'tenant_groups', 'tenants', 'owner', 'tags', 'data_source', 'data_file', 'auto_sync_enabled',
)
def __init__(self, *args, initial=None, **kwargs):
@@ -727,7 +728,7 @@ class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm)
return self.cleaned_data
-class ConfigTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm):
+class ConfigTemplateForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm):
tags = DynamicModelMultipleChoiceField(
label=_('Tags'),
queryset=Tag.objects.all(),
@@ -793,7 +794,7 @@ class JournalEntryForm(NetBoxModelForm):
label=_('Kind'),
choices=JournalEntryKindChoices
)
- comments = CommentField()
+ comments = CommentField(required=True)
class Meta:
model = JournalEntry
diff --git a/netbox/extras/graphql/filter_mixins.py b/netbox/extras/graphql/filter_mixins.py
index 7e9a970f2..4b5636c0e 100644
--- a/netbox/extras/graphql/filter_mixins.py
+++ b/netbox/extras/graphql/filter_mixins.py
@@ -3,9 +3,6 @@ from typing import Annotated, TYPE_CHECKING
import strawberry
import strawberry_django
-from strawberry_django import FilterLookup
-
-from core.graphql.filter_mixins import BaseFilterMixin
if TYPE_CHECKING:
from netbox.graphql.filter_lookups import JSONFilter
@@ -16,37 +13,30 @@ __all__ = (
'JournalEntriesFilterMixin',
'TagsFilterMixin',
'ConfigContextFilterMixin',
- 'TagBaseFilterMixin',
)
@dataclass
-class CustomFieldsFilterMixin(BaseFilterMixin):
+class CustomFieldsFilterMixin:
custom_field_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@dataclass
-class JournalEntriesFilterMixin(BaseFilterMixin):
+class JournalEntriesFilterMixin:
journal_entries: Annotated['JournalEntryFilter', strawberry.lazy('extras.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@dataclass
-class TagsFilterMixin(BaseFilterMixin):
+class TagsFilterMixin:
tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field()
@dataclass
-class ConfigContextFilterMixin(BaseFilterMixin):
+class ConfigContextFilterMixin:
local_context_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
-
-
-@dataclass
-class TagBaseFilterMixin(BaseFilterMixin):
- name: FilterLookup[str] | None = strawberry_django.filter_field()
- slug: FilterLookup[str] | None = strawberry_django.filter_field()
diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py
index dda9d947b..12b773f03 100644
--- a/netbox/extras/graphql/filters.py
+++ b/netbox/extras/graphql/filters.py
@@ -3,12 +3,12 @@ from typing import Annotated, TYPE_CHECKING
import strawberry
import strawberry_django
from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
-from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin
from extras import models
-from extras.graphql.filter_mixins import TagBaseFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin
-from netbox.graphql.filter_mixins import PrimaryModelFilterMixin, SyncedDataFilterMixin
+from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin
+from netbox.graphql.filter_mixins import SyncedDataFilterMixin
+from netbox.graphql.filters import ChangeLoggedModelFilter, PrimaryModelFilter
if TYPE_CHECKING:
from core.graphql.filters import ContentTypeFilter
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
)
from tenancy.graphql.filters import TenantFilter, TenantGroupFilter
from netbox.graphql.enums import ColorEnum
- from netbox.graphql.filter_lookups import IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter
+ from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter
from users.graphql.filters import GroupFilter, UserFilter
from virtualization.graphql.filters import ClusterFilter, ClusterGroupFilter, ClusterTypeFilter
from .enums import *
@@ -42,13 +42,13 @@ __all__ = (
@strawberry_django.filter_type(models.ConfigContext, lookups=True)
-class ConfigContextFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin):
- name: FilterLookup[str] = strawberry_django.filter_field()
+class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
+ name: FilterLookup[str] | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- description: FilterLookup[str] = strawberry_django.filter_field()
- is_active: FilterLookup[bool] = strawberry_django.filter_field()
+ description: FilterLookup[str] | None = strawberry_django.filter_field()
+ is_active: FilterLookup[bool] | None = strawberry_django.filter_field()
regions: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -99,14 +99,14 @@ class ConfigContextFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Chan
@strawberry_django.filter_type(models.ConfigContextProfile, lookups=True)
-class ConfigContextProfileFilter(SyncedDataFilterMixin, PrimaryModelFilterMixin):
+class ConfigContextProfileFilter(SyncedDataFilterMixin, PrimaryModelFilter):
name: FilterLookup[str] = strawberry_django.filter_field()
description: FilterLookup[str] = strawberry_django.filter_field()
tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ConfigTemplate, lookups=True)
-class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin):
+class ConfigTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
template_code: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -120,8 +120,8 @@ class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Cha
@strawberry_django.filter_type(models.CustomField, lookups=True)
-class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
- type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+class CustomFieldFilter(ChangeLoggedModelFilter):
+ type: BaseFilterLookup[Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
object_types: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -139,7 +139,9 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ filter_logic: (
+ BaseFilterLookup[Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
default: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -151,10 +153,10 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- validation_minimum: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+ validation_minimum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- validation_maximum: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
+ validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
validation_regex: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -162,10 +164,14 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
strawberry_django.filter_field()
)
choice_set_id: ID | None = strawberry_django.filter_field()
- ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ ui_visible: (
+ BaseFilterLookup[Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
- ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ ui_editable: (
+ BaseFilterLookup[Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -173,10 +179,12 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
@strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True)
-class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
- base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ base_choices: (
+ BaseFilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -186,7 +194,7 @@ class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin
@strawberry_django.filter_type(models.CustomLink, lookups=True)
-class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class CustomLinkFilter(ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
link_text: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -195,14 +203,16 @@ class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
strawberry_django.filter_field()
)
group_name: FilterLookup[str] | None = strawberry_django.filter_field()
- button_class: Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ button_class: (
+ BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
new_window: FilterLookup[bool] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ExportTemplate, lookups=True)
-class ExportTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin):
+class ExportTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
template_code: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -216,7 +226,7 @@ class ExportTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Cha
@strawberry_django.filter_type(models.ImageAttachment, lookups=True)
-class ImageAttachmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class ImageAttachmentFilter(ChangeLoggedModelFilter):
object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -231,7 +241,7 @@ class ImageAttachmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
@strawberry_django.filter_type(models.JournalEntry, lookups=True)
-class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin):
+class JournalEntryFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -240,14 +250,14 @@ class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, Tag
created_by: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
comments: FilterLookup[str] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.NotificationGroup, lookups=True)
-class NotificationGroupFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class NotificationGroupFilter(ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
@@ -255,7 +265,7 @@ class NotificationGroupFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
@strawberry_django.filter_type(models.SavedFilter, lookups=True)
-class SavedFilterFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class SavedFilterFilter(ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
slug: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -272,7 +282,7 @@ class SavedFilterFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
@strawberry_django.filter_type(models.TableConfig, lookups=True)
-class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
+class TableConfigFilter(ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
@@ -285,17 +295,23 @@ class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
@strawberry_django.filter_type(models.Tag, lookups=True)
-class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin):
- color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+class TagFilter(ChangeLoggedModelFilter):
+ name: FilterLookup[str] | None = strawberry_django.filter_field()
+ slug: FilterLookup[str] | None = strawberry_django.filter_field()
+ color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+ strawberry_django.filter_field()
+ )
description: FilterLookup[str] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Webhook, lookups=True)
-class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin):
+class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
payload_url: FilterLookup[str] | None = strawberry_django.filter_field()
- http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ http_method: (
+ BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None
+ ) = (
strawberry_django.filter_field()
)
http_content_type: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -310,7 +326,7 @@ class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilt
@strawberry_django.filter_type(models.EventRule, lookups=True)
-class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin):
+class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
name: FilterLookup[str] | None = strawberry_django.filter_field()
description: FilterLookup[str] | None = strawberry_django.filter_field()
event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -320,7 +336,7 @@ class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFi
conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+ action_type: BaseFilterLookup[Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
action_object_type: FilterLookup[str] | None = strawberry_django.filter_field()
diff --git a/netbox/extras/graphql/mixins.py b/netbox/extras/graphql/mixins.py
index 542bbcc85..881a53aa3 100644
--- a/netbox/extras/graphql/mixins.py
+++ b/netbox/extras/graphql/mixins.py
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Annotated, List
import strawberry
import strawberry_django
+from strawberry.types import Info
__all__ = (
'ConfigContextMixin',
@@ -37,7 +38,7 @@ class CustomFieldsMixin:
class ImageAttachmentsMixin:
@strawberry_django.field
- def image_attachments(self, info) -> List[Annotated["ImageAttachmentType", strawberry.lazy('.types')]]:
+ def image_attachments(self, info: Info) -> List[Annotated['ImageAttachmentType', strawberry.lazy('.types')]]:
return self.images.restrict(info.context.request.user, 'view')
@@ -45,17 +46,17 @@ class ImageAttachmentsMixin:
class JournalEntriesMixin:
@strawberry_django.field
- def journal_entries(self, info) -> List[Annotated["JournalEntryType", strawberry.lazy('.types')]]:
+ def journal_entries(self, info: Info) -> List[Annotated['JournalEntryType', strawberry.lazy('.types')]]:
return self.journal_entries.all()
@strawberry.type
class TagsMixin:
- tags: List[Annotated["TagType", strawberry.lazy('.types')]]
+ tags: List[Annotated['TagType', strawberry.lazy('.types')]]
@strawberry.type
class ContactsMixin:
- contacts: List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]
+ contacts: List[Annotated['ContactAssignmentType', strawberry.lazy('tenancy.graphql.types')]]
diff --git a/netbox/extras/graphql/types.py b/netbox/extras/graphql/types.py
index 97637684e..8230edea8 100644
--- a/netbox/extras/graphql/types.py
+++ b/netbox/extras/graphql/types.py
@@ -6,7 +6,8 @@ import strawberry_django
from core.graphql.mixins import SyncedDataMixin
from extras import models
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
-from netbox.graphql.types import BaseObjectType, ContentTypeType, NetBoxObjectType, ObjectType, OrganizationalObjectType
+from netbox.graphql.types import BaseObjectType, ContentTypeType, ObjectType, PrimaryObjectType
+from users.graphql.mixins import OwnerMixin
from .filters import *
if TYPE_CHECKING:
@@ -51,7 +52,7 @@ __all__ = (
filters=ConfigContextProfileFilter,
pagination=True
)
-class ConfigContextProfileType(SyncedDataMixin, NetBoxObjectType):
+class ConfigContextProfileType(SyncedDataMixin, PrimaryObjectType):
pass
@@ -61,7 +62,7 @@ class ConfigContextProfileType(SyncedDataMixin, NetBoxObjectType):
filters=ConfigContextFilter,
pagination=True
)
-class ConfigContextType(SyncedDataMixin, ObjectType):
+class ConfigContextType(SyncedDataMixin, OwnerMixin, ObjectType):
profile: ConfigContextProfileType | None
roles: List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]
device_types: List[Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]]
@@ -84,7 +85,7 @@ class ConfigContextType(SyncedDataMixin, ObjectType):
filters=ConfigTemplateFilter,
pagination=True
)
-class ConfigTemplateType(SyncedDataMixin, TagsMixin, ObjectType):
+class ConfigTemplateType(SyncedDataMixin, OwnerMixin, TagsMixin, ObjectType):
virtualmachines: List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
platforms: List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]
@@ -97,7 +98,7 @@ class ConfigTemplateType(SyncedDataMixin, TagsMixin, ObjectType):
filters=CustomFieldFilter,
pagination=True
)
-class CustomFieldType(ObjectType):
+class CustomFieldType(OwnerMixin, ObjectType):
related_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
choice_set: Annotated["CustomFieldChoiceSetType", strawberry.lazy('extras.graphql.types')] | None
@@ -108,7 +109,7 @@ class CustomFieldType(ObjectType):
filters=CustomFieldChoiceSetFilter,
pagination=True
)
-class CustomFieldChoiceSetType(ObjectType):
+class CustomFieldChoiceSetType(OwnerMixin, ObjectType):
choices_for: List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]
extra_choices: List[List[str]] | None
@@ -120,7 +121,7 @@ class CustomFieldChoiceSetType(ObjectType):
filters=CustomLinkFilter,
pagination=True
)
-class CustomLinkType(ObjectType):
+class CustomLinkType(OwnerMixin, ObjectType):
pass
@@ -130,7 +131,7 @@ class CustomLinkType(ObjectType):
filters=ExportTemplateFilter,
pagination=True
)
-class ExportTemplateType(SyncedDataMixin, ObjectType):
+class ExportTemplateType(SyncedDataMixin, OwnerMixin, ObjectType):
pass
@@ -180,7 +181,7 @@ class NotificationGroupType(ObjectType):
filters=SavedFilterFilter,
pagination=True
)
-class SavedFilterType(ObjectType):
+class SavedFilterType(OwnerMixin, ObjectType):
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
@@ -209,7 +210,7 @@ class TableConfigType(ObjectType):
filters=TagFilter,
pagination=True
)
-class TagType(ObjectType):
+class TagType(OwnerMixin, ObjectType):
color: str
object_types: List[ContentTypeType]
@@ -221,7 +222,7 @@ class TagType(ObjectType):
filters=WebhookFilter,
pagination=True
)
-class WebhookType(OrganizationalObjectType):
+class WebhookType(OwnerMixin, CustomFieldsMixin, TagsMixin, ObjectType):
pass
@@ -231,5 +232,5 @@ class WebhookType(OrganizationalObjectType):
filters=EventRuleFilter,
pagination=True
)
-class EventRuleType(OrganizationalObjectType):
+class EventRuleType(OwnerMixin, CustomFieldsMixin, TagsMixin, ObjectType):
action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
diff --git a/netbox/extras/jobs.py b/netbox/extras/jobs.py
index 8a039c7c8..5b57cbce4 100644
--- a/netbox/extras/jobs.py
+++ b/netbox/extras/jobs.py
@@ -2,11 +2,14 @@ import logging
import traceback
from contextlib import ExitStack
-from django.db import transaction
+from django.db import router, transaction
+from django.db import DEFAULT_DB_ALIAS
from django.utils.translation import gettext as _
from core.signals import clear_events
+from dcim.models import Device
from extras.models import Script as ScriptModel
+from netbox.context_managers import event_tracking
from netbox.jobs import JobRunner
from netbox.registry import registry
from utilities.exceptions import AbortScript, AbortTransaction
@@ -42,10 +45,21 @@ class ScriptJob(JobRunner):
# A script can modify multiple models so need to do an atomic lock on
# both the default database (for non ChangeLogged models) and potentially
# any other database (for ChangeLogged models)
- with transaction.atomic():
- script.output = script.run(data, commit)
- if not commit:
- raise AbortTransaction()
+ changeloged_db = router.db_for_write(Device)
+ with transaction.atomic(using=DEFAULT_DB_ALIAS):
+ # If branch database is different from default, wrap in a second atomic transaction
+ # Note: Don't add any extra code between the two atomic transactions,
+ # otherwise the changes might get committed to the default database
+ # if there are any raised exceptions.
+ if changeloged_db != DEFAULT_DB_ALIAS:
+ with transaction.atomic(using=changeloged_db):
+ script.output = script.run(data, commit)
+ if not commit:
+ raise AbortTransaction()
+ else:
+ script.output = script.run(data, commit)
+ if not commit:
+ raise AbortTransaction()
except AbortTransaction:
script.log_info(message=_("Database changes have been reverted automatically."))
if script.failed:
@@ -106,16 +120,16 @@ class ScriptJob(JobRunner):
# Add the current request as a property of the script
script.request = request
- self.logger.debug(f"Request ID: {request.id}")
+ self.logger.debug(f"Request ID: {request.id if request else None}")
- # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
- # change logging, event rules, etc.
if commit:
self.logger.info("Executing script (commit enabled)")
- with ExitStack() as stack:
- for request_processor in registry['request_processors']:
- stack.enter_context(request_processor(request))
- self.run_script(script, request, data, commit)
else:
self.logger.warning("Executing script (commit disabled)")
+
+ with ExitStack() as stack:
+ for request_processor in registry['request_processors']:
+ if not commit and request_processor is event_tracking:
+ continue
+ stack.enter_context(request_processor(request))
self.run_script(script, request, data, commit)
diff --git a/netbox/extras/lookups.py b/netbox/extras/lookups.py
index 33296340e..678239080 100644
--- a/netbox/extras/lookups.py
+++ b/netbox/extras/lookups.py
@@ -1,9 +1,39 @@
+from django.contrib.postgres.fields import ArrayField
+from django.contrib.postgres.fields.ranges import RangeField
from django.db.models import CharField, JSONField, Lookup
from django.db.models.fields.json import KeyTextTransform
from .fields import CachedValueField
+class RangeContains(Lookup):
+ """
+ Filter ArrayField(RangeField) columns where ANY element-range contains the scalar RHS.
+
+ Usage (ORM):
+ Model.objects.filter({regex}").format(
+ regex=escape(self.validation_regex)
+ ))
+ )
+ ]
# JSON
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
- field = JSONField(required=required, initial=json.dumps(initial) if initial else None)
+ field = JSONField(required=required, initial=json.dumps(initial) if initial is not None else None)
# Object
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
@@ -680,6 +694,13 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
if self.validation_regex and not re.match(self.validation_regex, value):
raise ValidationError(_("Value must match regex '{regex}'").format(regex=self.validation_regex))
+ # Validate URL field
+ elif self.type == CustomFieldTypeChoices.TYPE_URL:
+ if type(value) is not str:
+ raise ValidationError(_("Value must be a string."))
+ if self.validation_regex and not re.match(self.validation_regex, value):
+ raise ValidationError(_("Value must match regex '{regex}'").format(regex=self.validation_regex))
+
# Validate integer
elif self.type == CustomFieldTypeChoices.TYPE_INTEGER:
if type(value) is not int:
@@ -769,7 +790,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
raise ValidationError(_("Required field cannot be empty."))
-class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
+class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedModel):
"""
Represents a set of choices available for choice and multi-choice custom fields.
"""
diff --git a/netbox/extras/models/mixins.py b/netbox/extras/models/mixins.py
index d04220982..14540ffcf 100644
--- a/netbox/extras/models/mixins.py
+++ b/netbox/extras/models/mixins.py
@@ -30,8 +30,7 @@ class CustomStoragesLoader(importlib.abc.Loader):
return None # Use default module creation
def exec_module(self, module):
- storage = storages.create_storage(storages.backends["scripts"])
- with storage.open(self.filename, 'rb') as f:
+ with storages["scripts"].open(self.filename, 'rb') as f:
code = f.read()
exec(code, module.__dict__)
diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py
index be4c44d63..52ced1835 100644
--- a/netbox/extras/models/models.py
+++ b/netbox/extras/models/models.py
@@ -1,6 +1,6 @@
import json
-import os
import urllib.parse
+from pathlib import Path
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
@@ -25,6 +25,7 @@ from netbox.models import ChangeLoggedModel
from netbox.models.features import (
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, has_feature
)
+from netbox.models.mixins import OwnerMixin
from utilities.html import clean_html
from utilities.jinja2 import render_jinja2
from utilities.querydict import dict_to_querydict
@@ -44,7 +45,7 @@ __all__ = (
)
-class EventRule(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
+class EventRule(CustomFieldsMixin, ExportTemplatesMixin, OwnerMixin, TagsMixin, ChangeLoggedModel):
"""
An EventRule defines an action to be taken automatically in response to a specific set of events, such as when a
specific type of object is created, modified, or deleted. The action to be taken might entail transmitting a
@@ -155,7 +156,7 @@ class EventRule(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLogged
return False
-class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
+class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, OwnerMixin, ChangeLoggedModel):
"""
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
@@ -294,7 +295,7 @@ class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedMo
return render_jinja2(self.payload_url, context)
-class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
+class CustomLink(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedModel):
"""
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
code to be rendered with an object as context.
@@ -394,7 +395,14 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
}
-class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, RenderTemplateMixin):
+class ExportTemplate(
+ SyncedDataMixin,
+ CloningMixin,
+ ExportTemplatesMixin,
+ OwnerMixin,
+ ChangeLoggedModel,
+ RenderTemplateMixin,
+):
object_types = models.ManyToManyField(
to='contenttypes.ContentType',
related_name='export_templates',
@@ -456,7 +464,7 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
return _context
-class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
+class SavedFilter(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedModel):
"""
A set of predefined keyword parameters that can be reused to filter for specific objects.
"""
@@ -728,7 +736,9 @@ class ImageAttachment(ChangeLoggedModel):
@property
def filename(self):
- return os.path.basename(self.image.name).split('_', 2)[2]
+ base_name = Path(self.image.name).name
+ prefix = f"{self.object_type.model}_{self.object_id}_"
+ return base_name.removeprefix(prefix)
@property
def html_tag(self):
diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py
index 91732f8f0..944492d76 100644
--- a/netbox/extras/models/scripts.py
+++ b/netbox/extras/models/scripts.py
@@ -126,7 +126,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
ordered.extend(script_objects.values())
return ordered
- @property
+ @cached_property
def module_scripts(self):
def _get_name(cls):
diff --git a/netbox/extras/models/tags.py b/netbox/extras/models/tags.py
index 0df76d7b3..dc98ae65b 100644
--- a/netbox/extras/models/tags.py
+++ b/netbox/extras/models/tags.py
@@ -8,6 +8,7 @@ from taggit.models import TagBase, GenericTaggedItemBase
from netbox.choices import ColorChoices
from netbox.models import ChangeLoggedModel
from netbox.models.features import CloningMixin, ExportTemplatesMixin
+from netbox.models.mixins import OwnerMixin
from utilities.fields import ColorField
from utilities.querysets import RestrictedQuerySet
@@ -21,7 +22,7 @@ __all__ = (
# Tags
#
-class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
+class Tag(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedModel, TagBase):
id = models.BigAutoField(
primary_key=True
)
diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py
index 8d6628a83..315ec86fb 100644
--- a/netbox/extras/querysets.py
+++ b/netbox/extras/querysets.py
@@ -22,9 +22,10 @@ class ConfigContextQuerySet(RestrictedQuerySet):
aggregate_data: If True, use the JSONBAgg aggregate function to return only the list of JSON data objects
"""
- # Device type and location assignment is relevant only for Devices
+ # Device type and location assignment are relevant only for Devices
device_type = getattr(obj, 'device_type', None)
location = getattr(obj, 'location', None)
+ locations = location.get_ancestors(include_self=True) if location else []
# Get assigned cluster, group, and type (if any)
cluster = getattr(obj, 'cluster', None)
@@ -45,14 +46,18 @@ class ConfigContextQuerySet(RestrictedQuerySet):
# Match against the directly assigned role as well as any parent roles.
device_roles = obj.role.get_ancestors(include_self=True) if obj.role else []
+ # Match against the directly assigned platform as well as any parent platforms.
+ platform = getattr(obj, 'platform', None)
+ platforms = platform.get_ancestors(include_self=True) if platform else []
+
queryset = self.filter(
Q(regions__in=regions) | Q(regions=None),
Q(site_groups__in=sitegroups) | Q(site_groups=None),
Q(sites=obj.site) | Q(sites=None),
- Q(locations=location) | Q(locations=None),
+ Q(locations__in=locations) | Q(locations=None),
Q(device_types=device_type) | Q(device_types=None),
Q(roles__in=device_roles) | Q(roles=None),
- Q(platforms=obj.platform) | Q(platforms=None),
+ Q(platforms__in=platforms) | Q(platforms=None),
Q(cluster_types=cluster_type) | Q(cluster_types=None),
Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
Q(clusters=cluster) | Q(clusters=None),
@@ -89,10 +94,10 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
ConfigContext.objects.filter(
self._get_config_context_filters()
).annotate(
- _data=EmptyGroupByJSONBAgg('data', ordering=['weight', 'name'])
+ _data=EmptyGroupByJSONBAgg('data', order_by=['weight', 'name'])
).values("_data").order_by()
)
- ).distinct()
+ )
def _get_config_context_filters(self):
# Construct the set of Q objects for the specific object types
@@ -102,7 +107,6 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
"content_type__model": self.model._meta.model_name
}
base_query = Q(
- Q(platforms=OuterRef('platform')) | Q(platforms=None),
Q(cluster_types=OuterRef('cluster__type')) | Q(cluster_types=None),
Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None),
Q(clusters=OuterRef('cluster')) | Q(clusters=None),
@@ -116,7 +120,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
).values_list(
'tag_id',
flat=True
- )
+ ).distinct()
)
) | Q(tags=None),
is_active=True,
@@ -124,7 +128,15 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
# Apply Location & DeviceType filters only for VirtualMachines
if self.model._meta.model_name == 'device':
- base_query.add((Q(locations=OuterRef('location')) | Q(locations=None)), Q.AND)
+ base_query.add(
+ (Q(
+ locations__tree_id=OuterRef('location__tree_id'),
+ locations__level__lte=OuterRef('location__level'),
+ locations__lft__lte=OuterRef('location__lft'),
+ locations__rght__gte=OuterRef('location__rght'),
+ ) | Q(locations=None)),
+ Q.AND
+ )
base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND)
elif self.model._meta.model_name == 'virtualmachine':
base_query.add(Q(locations=None), Q.AND)
@@ -158,6 +170,15 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
) | Q(roles=None)),
Q.AND
)
+ base_query.add(
+ (Q(
+ platforms__tree_id=OuterRef('platform__tree_id'),
+ platforms__level__lte=OuterRef('platform__level'),
+ platforms__lft__lte=OuterRef('platform__lft'),
+ platforms__rght__gte=OuterRef('platform__rght'),
+ ) | Q(platforms=None)),
+ Q.AND
+ )
return base_query
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index a14eba556..ad9e5bcc4 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -1,12 +1,9 @@
import inspect
-import json
import logging
import os
import re
-import yaml
from django import forms
-from django.conf import settings
from django.core.files.storage import storages
from django.core.validators import RegexValidator
from django.utils import timezone
@@ -329,6 +326,9 @@ class BaseScript:
# Declare the placeholder for the current request
self.request = None
+ # Initiate the storage backend (local, S3, etc) as a class attr
+ self.storage = storages.create_storage(storages.backends["scripts"])
+
# Compile test methods and initialize results skeleton
for method in dir(self):
if method.startswith('test_') and callable(getattr(self, method)):
@@ -394,8 +394,7 @@ class BaseScript:
return inspect.getfile(self.__class__)
def findsource(self, object):
- storage = storages.create_storage(storages.backends["scripts"])
- with storage.open(os.path.basename(self.filename), 'r') as f:
+ with self.storage.open(os.path.basename(self.filename), 'r') as f:
data = f.read()
# Break the source code into lines
@@ -488,7 +487,7 @@ class BaseScript:
if self.fieldsets:
fieldsets.extend(self.fieldsets)
else:
- fields = list(name for name, _ in self._get_vars().items())
+ fields = list(name for name, __ in self._get_vars().items())
fieldsets.append((_('Script Data'), fields))
# Append the default fieldset if defined in the Meta class
@@ -580,40 +579,6 @@ class BaseScript:
self._log(message, obj, level=LogLevelChoices.LOG_FAILURE)
self.failed = True
- #
- # Convenience functions
- #
-
- def load_yaml(self, filename):
- """
- Return data from a YAML file
- """
- # TODO: DEPRECATED: Remove this method in v4.5
- self._log(
- _("load_yaml is deprecated and will be removed in v4.5"),
- level=LogLevelChoices.LOG_WARNING
- )
- file_path = os.path.join(settings.SCRIPTS_ROOT, filename)
- with open(file_path, 'r') as datafile:
- data = yaml.load(datafile, Loader=yaml.SafeLoader)
-
- return data
-
- def load_json(self, filename):
- """
- Return data from a JSON file
- """
- # TODO: DEPRECATED: Remove this method in v4.5
- self._log(
- _("load_json is deprecated and will be removed in v4.5"),
- level=LogLevelChoices.LOG_WARNING
- )
- file_path = os.path.join(settings.SCRIPTS_ROOT, filename)
- with open(file_path, 'r') as datafile:
- data = json.load(datafile)
-
- return data
-
#
# Legacy Report functionality
#
diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py
index 5c1a63d26..09fc5cc3c 100644
--- a/netbox/extras/tables/tables.py
+++ b/netbox/extras/tables/tables.py
@@ -10,7 +10,7 @@ from core.tables import JobTable
from core.models import Job
from netbox.constants import EMPTY_TABLE_TEXT
from netbox.events import get_event_text
-from netbox.tables import BaseTable, NetBoxTable, columns
+from netbox.tables import BaseTable, NetBoxTable, PrimaryModelTable, columns
from .columns import NotificationActionsColumn
__all__ = (
@@ -109,6 +109,10 @@ class CustomFieldTable(NetBoxTable):
validation_regex = tables.Column(
verbose_name=_('Validation Regex'),
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
class Meta(NetBoxTable.Meta):
model = CustomField
@@ -146,6 +150,10 @@ class CustomFieldChoiceSetTable(NetBoxTable):
verbose_name=_('Order Alphabetically'),
false_mark=None
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
class Meta(NetBoxTable.Meta):
model = CustomFieldChoiceSet
@@ -171,6 +179,10 @@ class CustomLinkTable(NetBoxTable):
verbose_name=_('New Window'),
false_mark=None
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
class Meta(NetBoxTable.Meta):
model = CustomLink
@@ -214,6 +226,10 @@ class ExportTemplateTable(NetBoxTable):
orderable=False,
verbose_name=_('Synced')
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
class Meta(NetBoxTable.Meta):
model = ExportTemplate
@@ -294,6 +310,10 @@ class SavedFilterTable(NetBoxTable):
verbose_name=_('Shared'),
false_mark=None
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
def value_parameters(self, value):
return json.dumps(value)
@@ -450,6 +470,10 @@ class WebhookTable(NetBoxTable):
ssl_validation = columns.BooleanColumn(
verbose_name=_('SSL Validation')
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
tags = columns.TagColumn(
url_name='extras:webhook_list'
)
@@ -488,6 +512,10 @@ class EventRuleTable(NetBoxTable):
func=get_event_text,
orderable=False
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
tags = columns.TagColumn(
url_name='extras:webhook_list'
)
@@ -514,6 +542,10 @@ class TagTable(NetBoxTable):
object_types = columns.ContentTypesColumn(
verbose_name=_('Object Types'),
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
class Meta(NetBoxTable.Meta):
model = Tag
@@ -547,7 +579,7 @@ class TaggedItemTable(NetBoxTable):
fields = ('id', 'content_type', 'content_object')
-class ConfigContextProfileTable(NetBoxTable):
+class ConfigContextProfileTable(PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -568,7 +600,7 @@ class ConfigContextProfileTable(NetBoxTable):
url_name='extras:configcontextprofile_list'
)
- class Meta(NetBoxTable.Meta):
+ class Meta(PrimaryModelTable.Meta):
model = ConfigContextProfile
fields = (
'pk', 'id', 'name', 'description', 'comments', 'data_source', 'data_file', 'is_synced', 'tags', 'created',
@@ -601,6 +633,10 @@ class ConfigContextTable(NetBoxTable):
orderable=False,
verbose_name=_('Synced')
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
tags = columns.TagColumn(
url_name='extras:configcontext_list'
)
@@ -632,6 +668,10 @@ class ConfigTemplateTable(NetBoxTable):
orderable=False,
verbose_name=_('Synced')
)
+ auto_sync_enabled = columns.BooleanColumn(
+ verbose_name=_('Auto Sync Enabled'),
+ orderable=False,
+ )
mime_type = tables.Column(
verbose_name=_('MIME Type')
)
@@ -645,6 +685,10 @@ class ConfigTemplateTable(NetBoxTable):
verbose_name=_('As Attachment'),
false_mark=None
)
+ owner = tables.Column(
+ linkify=True,
+ verbose_name=_('Owner')
+ )
tags = columns.TagColumn(
url_name='extras:configtemplate_list'
)
@@ -725,8 +769,9 @@ class ScriptResultsTable(BaseTable):
index = tables.Column(
verbose_name=_('Line')
)
- time = tables.Column(
- verbose_name=_('Time')
+ time = columns.DateTimeColumn(
+ verbose_name=_('Time'),
+ timespec='seconds'
)
status = tables.TemplateColumn(
template_code="""{% load log_levels %}{% log_level record.status %}""",
diff --git a/netbox/extras/templatetags/dashboard.py b/netbox/extras/templatetags/dashboard.py
index 4ac31abcf..67fa4024c 100644
--- a/netbox/extras/templatetags/dashboard.py
+++ b/netbox/extras/templatetags/dashboard.py
@@ -1,4 +1,6 @@
from django import template
+from django.utils.safestring import mark_safe
+from django.utils.translation import gettext as _
register = template.Library()
@@ -8,4 +10,16 @@ register = template.Library()
def render_widget(context, widget):
request = context['request']
- return widget.render(request)
+ try:
+ return widget.render(request)
+ except Exception as e:
+ message1 = _('An error was encountered when attempting to render this widget:')
+ message2 = _('Please try reconfiguring the widget, or remove it from your dashboard.')
+ return mark_safe(f"""
+ + + {message1} +
+{e}
+{message2}
+ """) diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index d635916e4..6e6ba66b6 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -3,6 +3,7 @@ import datetime from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.utils.timezone import make_aware, now +from rest_framework import status from core.choices import ManagedFileRootPathChoices from core.events import * @@ -11,7 +12,8 @@ from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Loca from extras.choices import * from extras.models import * from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar -from users.models import Group, User +from users.constants import TOKEN_PREFIX +from users.models import Group, Token, User from utilities.testing import APITestCase, APIViewTestCases @@ -854,20 +856,61 @@ class ConfigTemplateTest(APIViewTestCases.APIViewTestCase): ) ConfigTemplate.objects.bulk_create(config_templates) + def test_render(self): + configtemplate = ConfigTemplate.objects.first() + + self.add_permissions('extras.render_configtemplate', 'extras.view_configtemplate') + url = reverse('extras-api:configtemplate-render', kwargs={'pk': configtemplate.pk}) + response = self.client.post(url, {'foo': 'bar'}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(response.data['content'], 'Foo: bar') + + def test_render_without_permission(self): + configtemplate = ConfigTemplate.objects.first() + + # No permissions added - user has no render permission + url = reverse('extras-api:configtemplate-render', kwargs={'pk': configtemplate.pk}) + response = self.client.post(url, {'foo': 'bar'}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND) + + def test_render_token_write_enabled(self): + configtemplate = ConfigTemplate.objects.first() + + self.add_permissions('extras.render_configtemplate', 'extras.view_configtemplate') + url = reverse('extras-api:configtemplate-render', kwargs={'pk': configtemplate.pk}) + + # Request without token auth should fail with PermissionDenied + response = self.client.post(url, {'foo': 'bar'}, format='json') + self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN) + + # Create token with write_enabled=False + token = Token.objects.create(version=2, user=self.user, write_enabled=False) + token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}' + + # Request with write-disabled token should fail + response = self.client.post(url, {'foo': 'bar'}, format='json', HTTP_AUTHORIZATION=token_header) + self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN) + + # Enable write and retry + token.write_enabled = True + token.save() + response = self.client.post(url, {'foo': 'bar'}, format='json', HTTP_AUTHORIZATION=token_header) + self.assertHttpStatus(response, status.HTTP_200_OK) + class ScriptTest(APITestCase): class TestScriptClass(PythonClass): - class Meta: - name = "Test script" + name = 'Test script' + commit = True + scheduling_enabled = True var1 = StringVar() var2 = IntegerVar() var3 = BooleanVar() def run(self, data, commit=True): - self.log_info(data['var1']) self.log_success(data['var2']) self.log_failure(data['var3']) @@ -878,37 +921,104 @@ class ScriptTest(APITestCase): def setUpTestData(cls): module = ScriptModule.objects.create( file_root=ManagedFileRootPathChoices.SCRIPTS, - file_path='/var/tmp/script.py' + file_path='script.py', ) - Script.objects.create( + script = Script.objects.create( module=module, - name="Test script", + name='Test script', is_executable=True, ) + cls.url = reverse('extras-api:script-detail', kwargs={'pk': script.pk}) + @property def python_class(self): return self.TestScriptClass def setUp(self): super().setUp() + self.add_permissions('extras.view_script') # Monkey-patch the Script model to return our TestScriptClass above Script.python_class = self.python_class def test_get_script(self): - module = ScriptModule.objects.get( - file_root=ManagedFileRootPathChoices.SCRIPTS, - file_path='/var/tmp/script.py' - ) - script = module.scripts.all().first() - url = reverse('extras-api:script-detail', kwargs={'pk': script.pk}) - response = self.client.get(url, **self.header) + response = self.client.get(self.url, **self.header) self.assertEqual(response.data['name'], self.TestScriptClass.Meta.name) self.assertEqual(response.data['vars']['var1'], 'StringVar') self.assertEqual(response.data['vars']['var2'], 'IntegerVar') self.assertEqual(response.data['vars']['var3'], 'BooleanVar') + def test_schedule_script_past_time_rejected(self): + """ + Scheduling with past schedule_at should fail. + """ + self.add_permissions('extras.run_script') + + payload = { + 'data': {'var1': 'hello', 'var2': 1, 'var3': False}, + 'commit': True, + 'schedule_at': now() - datetime.timedelta(hours=1), + } + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertIn('schedule_at', response.data) + # Be tolerant of exact wording but ensure we failed on schedule_at being in the past + self.assertIn('future', str(response.data['schedule_at']).lower()) + + def test_schedule_script_interval_only(self): + """ + Interval without schedule_at should auto-set schedule_at now. + """ + self.add_permissions('extras.run_script') + + payload = { + 'data': {'var1': 'hello', 'var2': 1, 'var3': False}, + 'commit': True, + 'interval': 60, + } + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + # The latest job is returned in the script detail serializer under "result" + self.assertIn('result', response.data) + self.assertEqual(response.data['result']['interval'], 60) + # Ensure a start time was autopopulated + self.assertIsNotNone(response.data['result']['scheduled']) + + def test_schedule_script_when_disabled(self): + """ + Scheduling should fail when script.scheduling_enabled=False. + """ + self.add_permissions('extras.run_script') + + # Temporarily disable scheduling on the in-test Python class + original = getattr(self.TestScriptClass.Meta, 'scheduling_enabled', True) + self.TestScriptClass.Meta.scheduling_enabled = False + base = { + 'data': {'var1': 'hello', 'var2': 1, 'var3': False}, + 'commit': True, + } + # Check both schedule_at and interval paths + cases = [ + {**base, 'schedule_at': now() + datetime.timedelta(minutes=5)}, + {**base, 'interval': 60}, + ] + try: + for case in cases: + with self.subTest(case=list(case.keys())): + response = self.client.post(self.url, case, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + # Error should be attached to whichever field we used + key = 'schedule_at' if 'schedule_at' in case else 'interval' + self.assertIn(key, response.data) + self.assertIn('scheduling is not enabled', str(response.data[key]).lower()) + finally: + # Restore the original setting for other tests + self.TestScriptClass.Meta.scheduling_enabled = original + class CreatedUpdatedFilterTest(APITestCase): diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index c3074aa41..502759ab9 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -1,7 +1,9 @@ import datetime +import json from decimal import Decimal from django.core.exceptions import ValidationError +from django.test import tag from django.urls import reverse from rest_framework import status @@ -269,6 +271,60 @@ class CustomFieldTest(TestCase): instance.refresh_from_db() self.assertIsNone(instance.custom_field_data.get(cf.name)) + @tag('regression') + def test_json_field_falsy_defaults(self): + """Test that falsy JSON default values are properly handled""" + falsy_test_cases = [ + ({}, 'empty_dict'), + ([], 'empty_array'), + (0, 'zero'), + (False, 'false_bool'), + ("", 'empty_string'), + ] + + for default, suffix in falsy_test_cases: + with self.subTest(default=default, suffix=suffix): + cf = CustomField.objects.create( + name=f'json_falsy_{suffix}', + type=CustomFieldTypeChoices.TYPE_JSON, + default=default, + required=False + ) + cf.object_types.set([self.object_type]) + + instance = Site.objects.create(name=f'Test Site {suffix}', slug=f'test-site-{suffix}') + + self.assertIsNotNone(instance.custom_field_data) + self.assertIn(cf.name, instance.custom_field_data) + + instance.refresh_from_db() + stored = instance.custom_field_data[cf.name] + self.assertEqual(stored, default) + + @tag('regression') + def test_json_field_falsy_to_form_field(self): + """Test form field generation preserves falsy defaults""" + falsy_test_cases = ( + ({}, json.dumps({}), 'empty_dict'), + ([], json.dumps([]), 'empty_array'), + (0, json.dumps(0), 'zero'), + (False, json.dumps(False), 'false_bool'), + ("", '""', 'empty_string'), + ) + + for default, expected, suffix in falsy_test_cases: + with self.subTest(default=default, expected=expected, suffix=suffix): + cf = CustomField.objects.create( + name=f'json_falsy_{suffix}', + type=CustomFieldTypeChoices.TYPE_JSON, + default=default, + required=False + ) + cf.object_types.set([self.object_type]) + + form_field = cf.to_form_field(set_initial=True) + self.assertEqual(form_field.initial, expected) + def test_select_field(self): CHOICES = ( ('a', 'Option A'), @@ -1244,6 +1300,28 @@ class CustomFieldAPITest(APITestCase): response = self.client.patch(url, data, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) + def test_url_regex_validation(self): + """ + Test that validation_regex is applied to URL custom fields (fixes #20498). + """ + site2 = Site.objects.get(name='Site 2') + url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk}) + self.add_permissions('dcim.change_site') + + cf_url = CustomField.objects.get(name='url_field') + cf_url.validation_regex = r'^https://' # Require HTTPS + cf_url.save() + + # Test invalid URL (http instead of https) + data = {'custom_fields': {'url_field': 'http://example.com'}} + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + + # Test valid URL (https) + data = {'custom_fields': {'url_field': 'https://example.com'}} + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + def test_uniqueness_validation(self): # Create a unique custom field cf_text = CustomField.objects.get(name='text_field') diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index 0c9c25de3..082550346 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -363,7 +363,7 @@ class EventRuleTest(APITestCase): body = json.loads(request.body) self.assertEqual(body['event'], 'created') self.assertEqual(body['timestamp'], job.kwargs['timestamp']) - self.assertEqual(body['model'], 'site') + self.assertEqual(body['object_type'], 'dcim.site') self.assertEqual(body['username'], 'testuser') self.assertEqual(body['request_id'], str(request_id)) self.assertEqual(body['data']['name'], 'Site 1') diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index 341920a81..7b2e58646 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -1,17 +1,95 @@ import tempfile from pathlib import Path +from django.contrib.contenttypes.models import ContentType +from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import ValidationError from django.test import tag, TestCase from core.models import DataSource, ObjectType from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup -from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, Tag +from extras.models import ConfigContext, ConfigContextProfile, ConfigTemplate, ImageAttachment, Tag, TaggedItem from tenancy.models import Tenant, TenantGroup from utilities.exceptions import AbortRequest from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine +class ImageAttachmentTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack') + cls.image_content = b'' + + def _stub_image_attachment(self, object_id, image_filename, name=None): + """ + Creates an instance of ImageAttachment with the provided object_id and image_name. + + This method prepares a stubbed image attachment to test functionalities that + require an ImageAttachment object. + The function initializes the attachment with a specified file name and + pre-defined image content. + """ + ia = ImageAttachment( + object_type=self.ct_rack, + object_id=object_id, + name=name, + image=SimpleUploadedFile( + name=image_filename, + content=self.image_content, + content_type='image/jpeg', + ), + ) + return ia + + def test_filename_strips_expected_prefix(self): + """ + Tests that the filename of the image attachment is stripped of the expected + prefix. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_My_File.png') + self.assertEqual(ia.filename, 'My_File.png') + + def test_filename_legacy_nested_path_returns_basename(self): + """ + Tests if the filename of a legacy-nested path correctly returns only the basename. + """ + # e.g. "image-attachments/rack_12_5/31/23.jpg" -> "23.jpg" + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_5/31/23.jpg') + self.assertEqual(ia.filename, '23.jpg') + + def test_filename_no_prefix_returns_basename(self): + """ + Tests that the filename property correctly returns the basename for an image + attachment that has no leading prefix in its path. + """ + ia = self._stub_image_attachment(42, 'image-attachments/just_name.webp') + self.assertEqual(ia.filename, 'just_name.webp') + + def test_mismatched_prefix_is_not_stripped(self): + """ + Tests that a mismatched prefix in the filename is not stripped. + """ + # Prefix does not match object_id -> leave as-is (basename only) + ia = self._stub_image_attachment(12, 'image-attachments/rack_13_other.png') + self.assertEqual('rack_13_other.png', ia.filename) + + def test_str_uses_name_when_present(self): + """ + Tests that the `str` representation of the object uses the + `name` attribute when provided. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_file.png', name='Human title') + self.assertEqual('Human title', str(ia)) + + def test_str_falls_back_to_filename(self): + """ + Tests that the `str` representation of the object falls back to + the filename if the name attribute is not set. + """ + ia = self._stub_image_attachment(12, 'image-attachments/rack_12_file.png', name='') + self.assertEqual('file.png', str(ia)) + + class TagTest(TestCase): def test_default_ordering_weight_then_name_is_set(self): @@ -445,7 +523,7 @@ class ConfigContextTest(TestCase): vm1 = VirtualMachine.objects.create(name="VM 1", site=site, role=vm_role) vm2 = VirtualMachine.objects.create(name="VM 2", cluster=cluster, role=vm_role) - # Check that their individually-rendered config contexts are identical + # Check that their individually rendered config contexts are identical self.assertEqual( vm1.get_config_context(), vm2.get_config_context() @@ -458,11 +536,39 @@ class ConfigContextTest(TestCase): vms[1].get_config_context() ) + def test_valid_local_context_data(self): + device = Device.objects.first() + device.local_context_data = None + device.clean() + + device.local_context_data = {"foo": "bar"} + device.clean() + + def test_invalid_local_context_data(self): + device = Device.objects.first() + + device.local_context_data = "" + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 0 + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = False + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 'foo' + with self.assertRaises(ValidationError): + device.clean() + + @tag('regression') def test_multiple_tags_return_distinct_objects(self): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. This is combated by appending distinct() to the config context querysets. This test creates a config - context assigned to two tags and ensures objects related by those same two tags result in only a single + context assigned to two tags and ensures objects related to those same two tags result in only a single config context record being returned. See https://github.com/netbox-community/netbox/issues/5314 @@ -495,14 +601,15 @@ class ConfigContextTest(TestCase): self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 1) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) - def test_multiple_tags_return_distinct_objects_with_seperate_config_contexts(self): + @tag('regression') + def test_multiple_tags_return_distinct_objects_with_separate_config_contexts(self): """ Tagged items use a generic relationship, which results in duplicate rows being returned when queried. - This is combatted by by appending distinct() to the config context querysets. This test creates a config - context assigned to two tags and ensures objects related by those same two tags result in only a single + This is combated by appending distinct() to the config context querysets. This test creates a config + context assigned to two tags and ensures objects related to those same two tags result in only a single config context record being returned. - This test case is seperate from the above in that it deals with multiple config context objects in play. + This test case is separate from the above in that it deals with multiple config context objects in play. See https://github.com/netbox-community/netbox/issues/5387 """ @@ -543,32 +650,47 @@ class ConfigContextTest(TestCase): self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 2) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) - def test_valid_local_context_data(self): + @tag('performance', 'regression') + def test_config_context_annotation_query_optimization(self): + """ + Regression test for issue #20327: Ensure config context annotation + doesn't use expensive DISTINCT on main query. + + Verifies that DISTINCT is only used in tag subquery where needed, + not on the main device query which is expensive for large datasets. + """ device = Device.objects.first() - device.local_context_data = None - device.clean() + queryset = Device.objects.filter(pk=device.pk).annotate_config_context_data() - device.local_context_data = {"foo": "bar"} - device.clean() + # Main device query should NOT use DISTINCT + self.assertFalse(queryset.query.distinct) - def test_invalid_local_context_data(self): - device = Device.objects.first() + # Check that tag subqueries DO use DISTINCT by inspecting the annotation + config_annotation = queryset.query.annotations.get('config_context_data') + self.assertIsNotNone(config_annotation) - device.local_context_data = "" - with self.assertRaises(ValidationError): - device.clean() + def find_tag_subqueries(where_node): + """Find subqueries in WHERE clause that relate to tag filtering""" + subqueries = [] - device.local_context_data = 0 - with self.assertRaises(ValidationError): - device.clean() + def traverse(node): + if hasattr(node, 'children'): + for child in node.children: + try: + if child.rhs.query.model is TaggedItem: + subqueries.append(child.rhs.query) + except AttributeError: + traverse(child) + traverse(where_node) + return subqueries - device.local_context_data = False - with self.assertRaises(ValidationError): - device.clean() + # Find subqueries in the WHERE clause that should have DISTINCT + tag_subqueries = find_tag_subqueries(config_annotation.query.where) + distinct_subqueries = [sq for sq in tag_subqueries if sq.distinct] - device.local_context_data = 'foo' - with self.assertRaises(ValidationError): - device.clean() + # Verify we found at least one DISTINCT subquery for tags + self.assertEqual(len(distinct_subqueries), 1) + self.assertTrue(distinct_subqueries[0].distinct) class ConfigTemplateTest(TestCase): diff --git a/netbox/extras/tests/test_scripts.py b/netbox/extras/tests/test_scripts.py index 4f5d0187a..5db15bd41 100644 --- a/netbox/extras/tests/test_scripts.py +++ b/netbox/extras/tests/test_scripts.py @@ -1,5 +1,3 @@ -import logging -import tempfile from datetime import date, datetime, timezone from decimal import Decimal @@ -9,7 +7,6 @@ from netaddr import IPAddress, IPNetwork from dcim.models import DeviceRole from extras.scripts import * -from utilities.testing import disable_logging CHOICES = ( ('ff0000', 'Red'), @@ -35,35 +32,6 @@ JSON_DATA = """ """ -class ScriptTest(TestCase): - - def test_load_yaml(self): - datafile = tempfile.NamedTemporaryFile() - datafile.write(bytes(YAML_DATA, 'UTF-8')) - datafile.seek(0) - - with disable_logging(level=logging.WARNING): - data = Script().load_yaml(datafile.name) - self.assertEqual(data, { - 'Foo': 123, - 'Bar': 456, - 'Baz': ['A', 'B', 'C'], - }) - - def test_load_json(self): - datafile = tempfile.NamedTemporaryFile() - datafile.write(bytes(JSON_DATA, 'UTF-8')) - datafile.seek(0) - - with disable_logging(level=logging.WARNING): - data = Script().load_json(datafile.name) - self.assertEqual(data, { - 'Foo': 123, - 'Bar': 456, - 'Baz': ['A', 'B', 'C'], - }) - - class ScriptVariablesTest(TestCase): def test_stringvar(self): diff --git a/netbox/extras/tests/test_utils.py b/netbox/extras/tests/test_utils.py index b851acab8..ec0102887 100644 --- a/netbox/extras/tests/test_utils.py +++ b/netbox/extras/tests/test_utils.py @@ -1,7 +1,10 @@ +from types import SimpleNamespace + +from django.contrib.contenttypes.models import ContentType from django.test import TestCase from extras.models import ExportTemplate -from extras.utils import filename_from_model +from extras.utils import filename_from_model, image_upload from tenancy.models import ContactGroup, TenantGroup from wireless.models import WirelessLANGroup @@ -17,3 +20,141 @@ class FilenameFromModelTests(TestCase): for model, expected in cases: self.assertEqual(filename_from_model(model), expected) + + +class ImageUploadTests(TestCase): + @classmethod + def setUpTestData(cls): + # We only need a ContentType with model="rack" for the prefix; + # this doesn't require creating a Rack object. + cls.ct_rack = ContentType.objects.get(app_label='dcim', model='rack') + + def _stub_instance(self, object_id=12, name=None): + """ + Creates a minimal stub for use with the `image_upload()` function. + + This method generates an instance of `SimpleNamespace` containing a set + of attributes required to simulate the expected input for the + `image_upload()` method. + It is designed to simplify testing or processing by providing a + lightweight representation of an object. + """ + return SimpleNamespace(object_type=self.ct_rack, object_id=object_id, name=name) + + def _second_segment(self, path: str): + """ + Extracts and returns the portion of the input string after the + first '/' character. + """ + return path.split('/', 1)[1] + + def test_windows_fake_path_and_extension_lowercased(self): + """ + Tests handling of a Windows file path with a fake directory and extension. + """ + inst = self._stub_instance(name=None) + path = image_upload(inst, r'C:\fake_path\MyPhoto.JPG') + # Base directory and single-level path + seg2 = self._second_segment(path) + self.assertTrue(path.startswith('image-attachments/rack_12_')) + self.assertNotIn('/', seg2, 'should not create nested directories') + # Extension from the uploaded file, lowercased + self.assertTrue(seg2.endswith('.jpg')) + + def test_name_with_slashes_is_flattened_no_subdirectories(self): + """ + Tests that a name with slashes is flattened and does not + create subdirectories. + """ + inst = self._stub_instance(name='5/31/23') + path = image_upload(inst, 'image.png') + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_name_with_backslashes_is_flattened_no_subdirectories(self): + """ + Tests that a name including backslashes is correctly flattened + into a single directory name without creating subdirectories. + """ + inst = self._stub_instance(name=r'5\31\23') + path = image_upload(inst, 'image_name.png') + + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_prefix_format_is_as_expected(self): + """ + Tests the output path format generated by the `image_upload` function. + """ + inst = self._stub_instance(object_id=99, name='label') + path = image_upload(inst, 'a.webp') + # The second segment must begin with "rack_99_" + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_99_')) + self.assertTrue(seg2.endswith('.webp')) + + def test_unsupported_file_extension(self): + """ + Test that when the file extension is not allowed, the extension + is omitted. + """ + inst = self._stub_instance(name='test') + path = image_upload(inst, 'document.txt') + + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_test')) + self.assertFalse(seg2.endswith('.txt')) + # When not allowed, no extension should be appended + self.assertNotRegex(seg2, r'\.txt$') + + def test_instance_name_with_whitespace_and_special_chars(self): + """ + Test that an instance name with leading/trailing whitespace and + special characters is sanitized properly. + """ + # Suppose the instance name has surrounding whitespace and + # extra slashes. + inst = self._stub_instance(name=' my/complex\\name ') + path = image_upload(inst, 'irrelevant.png') + + # The output should be flattened and sanitized. + # We expect the name to be transformed into a valid filename without + # path separators. + seg2 = self._second_segment(path) + self.assertNotIn(' ', seg2) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.png')) + + def test_separator_variants_with_subTest(self): + """ + Tests that both forward slash and backslash in file paths are + handled consistently by the `image_upload` function and + processed into a sanitized uniform format. + """ + for name in ['2025/09/12', r'2025\09\12']: + with self.subTest(name=name): + inst = self._stub_instance(name=name) + path = image_upload(inst, 'x.jpeg') + seg2 = self._second_segment(path) + self.assertTrue(seg2.startswith('rack_12_')) + self.assertNotIn('/', seg2) + self.assertNotIn('\\', seg2) + self.assertTrue(seg2.endswith('.jpeg') or seg2.endswith('.jpg')) + + def test_fallback_on_suspicious_file_operation(self): + """ + Test that when default_storage.get_valid_name() raises a + SuspiciousFileOperation, the fallback default is used. + """ + inst = self._stub_instance(name=' ') + path = image_upload(inst, 'sample.png') + # Expect the fallback name 'unnamed' to be used. + self.assertIn('unnamed', path) + self.assertTrue(path.startswith('image-attachments/rack_12_')) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 9da6f047a..91444e2ce 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -1,11 +1,14 @@ from django.contrib.contenttypes.models import ContentType from django.urls import reverse +from django.test import tag +from core.choices import ManagedFileRootPathChoices from core.events import * from core.models import ObjectType from dcim.models import DeviceType, Manufacturer, Site from extras.choices import * from extras.models import * +from extras.scripts import Script as PythonClass, IntegerVar, BooleanVar from users.models import Group, User from utilities.testing import ViewTestCases, TestCase @@ -897,3 +900,70 @@ class ScriptListViewTest(TestCase): response = self.client.get(url, {'embedded': 'true'}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'extras/inc/script_list_content.html') + + +class ScriptValidationErrorTest(TestCase): + user_permissions = ['extras.view_script', 'extras.run_script'] + + class TestScriptMixin: + bar = IntegerVar(min_value=0, max_value=30, default=30) + + class TestScriptClass(TestScriptMixin, PythonClass): + class Meta: + name = 'Test script' + commit_default = False + fieldsets = (("Logging", ("debug_mode",)),) + + debug_mode = BooleanVar(default=False) + + def run(self, data, commit): + return "Complete" + + @classmethod + def setUpTestData(cls): + module = ScriptModule.objects.create(file_root=ManagedFileRootPathChoices.SCRIPTS, file_path='test_script.py') + cls.script = Script.objects.create(module=module, name='Test script', is_executable=True) + + def setUp(self): + super().setUp() + Script.python_class = property(lambda self: ScriptValidationErrorTest.TestScriptClass) + + @tag('regression') + def test_script_validation_error_displays_message(self): + from unittest.mock import patch + + url = reverse('extras:script', kwargs={'pk': self.script.pk}) + + with patch('extras.views.get_workers_for_queue', return_value=['worker']): + response = self.client.post(url, {'debug_mode': 'true', '_commit': 'true'}) + + self.assertEqual(response.status_code, 200) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "bar: This field is required.") + + @tag('regression') + def test_script_validation_error_no_toast_for_fieldset_fields(self): + from unittest.mock import patch, PropertyMock + + class FieldsetScript(PythonClass): + class Meta: + name = 'Fieldset test' + commit_default = False + fieldsets = (("Fields", ("required_field",)),) + + required_field = IntegerVar(min_value=10) + + def run(self, data, commit): + return "Complete" + + url = reverse('extras:script', kwargs={'pk': self.script.pk}) + + with patch.object(Script, 'python_class', new_callable=PropertyMock) as mock_python_class: + mock_python_class.return_value = FieldsetScript + with patch('extras.views.get_workers_for_queue', return_value=['worker']): + response = self.client.post(url, {'required_field': '5', '_commit': 'true'}) + + self.assertEqual(response.status_code, 200) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 0) diff --git a/netbox/extras/ui/panels.py b/netbox/extras/ui/panels.py new file mode 100644 index 000000000..f2f9a5c9a --- /dev/null +++ b/netbox/extras/ui/panels.py @@ -0,0 +1,68 @@ +from django.contrib.contenttypes.models import ContentType +from django.template.loader import render_to_string +from django.utils.translation import gettext_lazy as _ + +from netbox.ui import actions, panels +from utilities.data import resolve_attr_path + +__all__ = ( + 'CustomFieldsPanel', + 'ImageAttachmentsPanel', + 'TagsPanel', +) + + +class CustomFieldsPanel(panels.ObjectPanel): + """ + A panel showing the value of all custom fields defined on an object. + """ + template_name = 'extras/panels/custom_fields.html' + title = _('Custom Fields') + + def get_context(self, context): + obj = resolve_attr_path(context, self.accessor) + return { + **super().get_context(context), + 'custom_fields': obj.get_custom_fields_by_group(), + } + + def render(self, context): + ctx = self.get_context(context) + # Hide the panel if no custom fields exist + if not ctx['custom_fields']: + return '' + return render_to_string(self.template_name, self.get_context(context)) + + +class ImageAttachmentsPanel(panels.ObjectsTablePanel): + """ + A panel showing all images attached to the object. + """ + actions = [ + actions.AddObject( + 'extras.imageattachment', + url_params={ + 'object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk, + 'object_id': lambda ctx: ctx['object'].pk, + 'return_url': lambda ctx: ctx['object'].get_absolute_url(), + }, + label=_('Attach an image'), + ), + ] + + def __init__(self, **kwargs): + super().__init__('extras.imageattachment', **kwargs) + + +class TagsPanel(panels.ObjectPanel): + """ + A panel showing the tags assigned to the object. + """ + template_name = 'extras/panels/tags.html' + title = _('Tags') + + def get_context(self, context): + return { + **super().get_context(context), + 'object': resolve_attr_path(context, self.accessor), + } diff --git a/netbox/extras/utils.py b/netbox/extras/utils.py index c9f554d22..761f4affb 100644 --- a/netbox/extras/utils.py +++ b/netbox/extras/utils.py @@ -1,15 +1,20 @@ import importlib +from pathlib import Path -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation +from django.core.files.storage import default_storage +from django.core.files.utils import validate_file_name from django.db import models from django.db.models import Q from taggit.managers import _TaggableManager from netbox.context import current_request + from .validators import CustomValidator __all__ = ( 'SharedObjectViewMixin', + 'filename_from_model', 'image_upload', 'is_report', 'is_script', @@ -35,13 +40,13 @@ class SharedObjectViewMixin: def filename_from_model(model: models.Model) -> str: - """Standardises how we generate filenames from model class for exports""" + """Standardizes how we generate filenames from model class for exports""" base = model._meta.verbose_name_plural.lower().replace(' ', '_') return f'netbox_{base}' def filename_from_object(context: dict) -> str: - """Standardises how we generate filenames from model class for exports""" + """Standardizes how we generate filenames from model class for exports""" if 'device' in context: base = f"{context['device'].name or 'config'}" elif 'virtualmachine' in context: @@ -64,17 +69,42 @@ def is_taggable(obj): def image_upload(instance, filename): """ Return a path for uploading image attachments. + + - Normalizes browser paths (e.g., C:\\fake_path\\photo.jpg) + - Uses the instance.name if provided (sanitized to a *basename*, no ext) + - Prefixes with a machine-friendly identifier + + Note: Relies on Django's default_storage utility. """ - path = 'image-attachments/' + upload_dir = 'image-attachments' + default_filename = 'unnamed' + allowed_img_extensions = ('bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp') - # Rename the file to the provided name, if any. Attempt to preserve the file extension. - extension = filename.rsplit('.')[-1].lower() - if instance.name and extension in ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp']: - filename = '.'.join([instance.name, extension]) - elif instance.name: - filename = instance.name + # Normalize Windows paths and create a Path object. + normalized_filename = str(filename).replace('\\', '/') + file_path = Path(normalized_filename) - return '{}{}_{}_{}'.format(path, instance.object_type.name, instance.object_id, filename) + # Extract the extension from the uploaded file. + ext = file_path.suffix.lower().lstrip('.') + + # Use the instance-provided name if available; otherwise use the file stem. + # Rely on Django's get_valid_filename to perform sanitization. + stem = (instance.name or file_path.stem).strip() + try: + safe_stem = default_storage.get_valid_name(stem) + except SuspiciousFileOperation: + safe_stem = default_filename + + # Append the uploaded extension only if it's an allowed image type + final_name = f"{safe_stem}.{ext}" if ext in allowed_img_extensions else safe_stem + + # Create a machine-friendly prefix from the instance + prefix = f"{instance.object_type.model}_{instance.object_id}" + name_with_path = f"{upload_dir}/{prefix}_{final_name}" + + # Validate the generated relative path (blocks absolute/traversal) + validate_file_name(name_with_path, allow_relative_path=True) + return name_with_path def is_script(obj): @@ -107,7 +137,7 @@ def run_validators(instance, validators): request = current_request.get() for validator in validators: - # Loading a validator class by dotted path + # Loading a validator class by a dotted path if type(validator) is str: module, cls = validator.rsplit('.', 1) validator = getattr(importlib.import_module(module), cls)() diff --git a/netbox/extras/views.py b/netbox/extras/views.py index c76afbd15..3c1fc395d 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,9 +1,10 @@ +from datetime import datetime from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.core.paginator import EmptyPage from django.db.models import Count, Q -from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponse +from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone @@ -24,7 +25,7 @@ from netbox.object_actions import * from netbox.views import generic from netbox.views.generic.mixins import TableMixin from utilities.forms import ConfirmationForm, get_field_value -from utilities.htmx import htmx_partial +from utilities.htmx import htmx_partial, htmx_maybe_redirect_current_page from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.query import count_related from utilities.querydict import normalize_querydict @@ -100,6 +101,7 @@ class CustomFieldBulkEditView(generic.BulkEditView): @register_model_view(CustomField, 'bulk_rename', path='rename', detail=False) class CustomFieldBulkRenameView(generic.BulkRenameView): queryset = CustomField.objects.all() + filterset = filtersets.CustomFieldFilterSet @register_model_view(CustomField, 'bulk_delete', path='delete', detail=False) @@ -174,6 +176,7 @@ class CustomFieldChoiceSetBulkEditView(generic.BulkEditView): @register_model_view(CustomFieldChoiceSet, 'bulk_rename', path='rename', detail=False) class CustomFieldChoiceSetBulkRenameView(generic.BulkRenameView): queryset = CustomFieldChoiceSet.objects.all() + filterset = filtersets.CustomFieldChoiceSetFilterSet @register_model_view(CustomFieldChoiceSet, 'bulk_delete', path='delete', detail=False) @@ -229,6 +232,7 @@ class CustomLinkBulkEditView(generic.BulkEditView): @register_model_view(CustomLink, 'bulk_rename', path='rename', detail=False) class CustomLinkBulkRenameView(generic.BulkRenameView): queryset = CustomLink.objects.all() + filterset = filtersets.CustomLinkFilterSet @register_model_view(CustomLink, 'bulk_delete', path='delete', detail=False) @@ -285,6 +289,7 @@ class ExportTemplateBulkEditView(generic.BulkEditView): @register_model_view(ExportTemplate, 'bulk_rename', path='rename', detail=False) class ExportTemplateBulkRenameView(generic.BulkRenameView): queryset = ExportTemplate.objects.all() + filterset = filtersets.ExportTemplateFilterSet @register_model_view(ExportTemplate, 'bulk_delete', path='delete', detail=False) @@ -350,6 +355,7 @@ class SavedFilterBulkEditView(SharedObjectViewMixin, generic.BulkEditView): @register_model_view(SavedFilter, 'bulk_rename', path='rename', detail=False) class SavedFilterBulkRenameView(generic.BulkRenameView): queryset = SavedFilter.objects.all() + filterset = filtersets.SavedFilterFilterSet @register_model_view(SavedFilter, 'bulk_delete', path='delete', detail=False) @@ -412,6 +418,7 @@ class TableConfigBulkEditView(SharedObjectViewMixin, generic.BulkEditView): @register_model_view(TableConfig, 'bulk_rename', path='rename', detail=False) class TableConfigBulkRenameView(generic.BulkRenameView): queryset = TableConfig.objects.all() + filterset = filtersets.TableConfigFilterSet @register_model_view(TableConfig, 'bulk_delete', path='delete', detail=False) @@ -498,6 +505,7 @@ class NotificationGroupBulkEditView(generic.BulkEditView): @register_model_view(NotificationGroup, 'bulk_rename', path='rename', detail=False) class NotificationGroupBulkRenameView(generic.BulkRenameView): queryset = NotificationGroup.objects.all() + filterset = filtersets.NotificationGroupFilterSet @register_model_view(NotificationGroup, 'bulk_delete', path='delete', detail=False) @@ -517,8 +525,9 @@ class NotificationsView(LoginRequiredMixin, View): """ def get(self, request): return render(request, 'htmx/notifications.html', { - 'notifications': request.user.notifications.unread(), + 'notifications': request.user.notifications.unread()[:10], 'total_count': request.user.notifications.count(), + 'unread_count': request.user.notifications.unread().count(), }) @@ -527,6 +536,7 @@ class NotificationReadView(LoginRequiredMixin, View): """ Mark the Notification read and redirect the user to its attached object. """ + def get(self, request, pk): # Mark the Notification as read notification = get_object_or_404(request.user.notifications, pk=pk) @@ -540,18 +550,48 @@ class NotificationReadView(LoginRequiredMixin, View): return redirect('account:notifications') +@register_model_view(Notification, name='dismiss_all', path='dismiss-all', detail=False) +class NotificationDismissAllView(LoginRequiredMixin, View): + """ + Convenience view to clear all *unread* notifications for the current user. + """ + + def get(self, request): + request.user.notifications.unread().delete() + if htmx_partial(request): + # If a user is currently on the notification page, redirect there (full repaint) + redirect_resp = htmx_maybe_redirect_current_page(request, 'account:notifications', preserve_query=True) + if redirect_resp: + return redirect_resp + + return render(request, 'htmx/notifications.html', { + 'notifications': request.user.notifications.unread()[:10], + 'total_count': request.user.notifications.count(), + 'unread_count': request.user.notifications.unread().count(), + }) + return redirect('account:notifications') + + @register_model_view(Notification, 'dismiss') class NotificationDismissView(LoginRequiredMixin, View): """ A convenience view which allows deleting notifications with one click. """ + def get(self, request, pk): notification = get_object_or_404(request.user.notifications, pk=pk) notification.delete() if htmx_partial(request): + # If a user is currently on the notification page, redirect there (full repaint) + redirect_resp = htmx_maybe_redirect_current_page(request, 'account:notifications', preserve_query=True) + if redirect_resp: + return redirect_resp + return render(request, 'htmx/notifications.html', { 'notifications': request.user.notifications.unread()[:10], + 'total_count': request.user.notifications.count(), + 'unread_count': request.user.notifications.unread().count(), }) return redirect('account:notifications') @@ -649,6 +689,7 @@ class WebhookBulkEditView(generic.BulkEditView): @register_model_view(Webhook, 'bulk_rename', path='rename', detail=False) class WebhookBulkRenameView(generic.BulkRenameView): queryset = Webhook.objects.all() + filterset = filtersets.WebhookFilterSet @register_model_view(Webhook, 'bulk_delete', path='delete', detail=False) @@ -704,6 +745,7 @@ class EventRuleBulkEditView(generic.BulkEditView): @register_model_view(EventRule, 'bulk_rename', path='rename', detail=False) class EventRuleBulkRenameView(generic.BulkRenameView): queryset = EventRule.objects.all() + filterset = filtersets.EventRuleFilterSet @register_model_view(EventRule, 'bulk_delete', path='delete', detail=False) @@ -840,6 +882,7 @@ class ConfigContextProfileBulkEditView(generic.BulkEditView): @register_model_view(ConfigContextProfile, 'bulk_rename', path='rename', detail=False) class ConfigContextProfileBulkRenameView(generic.BulkRenameView): queryset = ConfigContextProfile.objects.all() + filterset = filtersets.ConfigContextProfileFilterSet @register_model_view(ConfigContextProfile, 'bulk_delete', path='delete', detail=False) @@ -928,6 +971,7 @@ class ConfigContextBulkEditView(generic.BulkEditView): @register_model_view(ConfigContext, 'bulk_rename', path='rename', detail=False) class ConfigContextBulkRenameView(generic.BulkRenameView): queryset = ConfigContext.objects.all() + filterset = filtersets.ConfigContextFilterSet @register_model_view(ConfigContext, 'bulk_delete', path='delete', detail=False) @@ -1019,6 +1063,7 @@ class ConfigTemplateBulkEditView(generic.BulkEditView): @register_model_view(ConfigTemplate, 'bulk_rename', path='rename', detail=False) class ConfigTemplateBulkRenameView(generic.BulkRenameView): queryset = ConfigTemplate.objects.all() + filterset = filtersets.ConfigTemplateFilterSet @register_model_view(ConfigTemplate, 'bulk_delete', path='delete', detail=False) @@ -1142,6 +1187,7 @@ class ImageAttachmentBulkEditView(generic.BulkEditView): @register_model_view(ImageAttachment, 'bulk_rename', path='rename', detail=False) class ImageAttachmentBulkRenameView(generic.BulkRenameView): queryset = ImageAttachment.objects.all() + filterset = filtersets.ImageAttachmentFilterSet @register_model_view(ImageAttachment, 'bulk_delete', path='delete', detail=False) @@ -1484,6 +1530,15 @@ class ScriptView(BaseScriptView): ) return redirect('extras:script_result', job_pk=job.pk) + else: + fieldset_fields = {field for _, fields in script_class.get_fieldsets() for field in fields} + hidden_errors = { + field: errors for field, errors in form.errors.items() + if field not in fieldset_fields + } + if hidden_errors: + error_msg = '; '.join(f"{field}: {', '.join(errors)}" for field, errors in hidden_errors.items()) + messages.error(request, error_msg) return render(request, 'extras/script.html', { 'object': script, @@ -1547,7 +1602,6 @@ class ScriptResultView(TableMixin, generic.ObjectView): except KeyError: log_threshold = LOG_LEVEL_RANK[LogLevelChoices.LOG_INFO] if job.data: - if 'log' in job.data: if 'tests' in job.data: tests = job.data['tests'] @@ -1558,7 +1612,7 @@ class ScriptResultView(TableMixin, generic.ObjectView): index += 1 result = { 'index': index, - 'time': log.get('time'), + 'time': datetime.fromisoformat(log.get('time')), 'status': log.get('status'), 'message': log.get('message'), 'object': log.get('obj'), diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index a68f219bd..1492eb6ea 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -52,7 +52,6 @@ def send_webhook(event_rule, object_type, event_type, data, timestamp, username, 'event': WEBHOOK_EVENT_TYPES.get(event_type, event_type), 'timestamp': timestamp, 'object_type': '.'.join(object_type.natural_key()), - 'model': object_type.model, 'username': username, 'request_id': request.id if request else None, 'data': data, @@ -100,7 +99,7 @@ def send_webhook(event_rule, object_type, event_type, data, timestamp, username, 'data': body.encode('utf8'), } logger.info( - f"Sending {params['method']} request to {params['url']} ({context['model']} {context['event']})" + f"Sending {params['method']} request to {params['url']} ({context['object_type']} {context['event']})" ) logger.debug(params) try: diff --git a/netbox/ipam/api/serializers_/asns.py b/netbox/ipam/api/serializers_/asns.py index 8baa073f5..dd821cb61 100644 --- a/netbox/ipam/api/serializers_/asns.py +++ b/netbox/ipam/api/serializers_/asns.py @@ -1,19 +1,21 @@ from rest_framework import serializers +from dcim.models import Site from ipam.models import ASN, ASNRange, RIR -from netbox.api.fields import RelatedObjectCountField -from netbox.api.serializers import NetBoxModelSerializer +from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField +from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer from tenancy.api.serializers_.tenants import TenantSerializer __all__ = ( 'ASNRangeSerializer', 'ASNSerializer', + 'ASNSiteSerializer', 'AvailableASNSerializer', 'RIRSerializer', ) -class RIRSerializer(NetBoxModelSerializer): +class RIRSerializer(OrganizationalModelSerializer): # Related object counts aggregate_count = RelatedObjectCountField('aggregates') @@ -21,13 +23,13 @@ class RIRSerializer(NetBoxModelSerializer): class Meta: model = RIR fields = [ - 'id', 'url', 'display_url', 'display', 'name', 'slug', 'is_private', 'description', 'tags', - 'custom_fields', 'created', 'last_updated', 'aggregate_count', + 'id', 'url', 'display_url', 'display', 'name', 'slug', 'is_private', 'description', 'owner', 'comments', + 'tags', 'custom_fields', 'created', 'last_updated', 'aggregate_count', ] brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'aggregate_count') -class ASNRangeSerializer(NetBoxModelSerializer): +class ASNRangeSerializer(OrganizationalModelSerializer): rir = RIRSerializer(nested=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) asn_count = serializers.IntegerField(read_only=True) @@ -36,14 +38,32 @@ class ASNRangeSerializer(NetBoxModelSerializer): model = ASNRange fields = [ 'id', 'url', 'display_url', 'display', 'name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', - 'tags', 'custom_fields', 'created', 'last_updated', 'asn_count', + 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'asn_count', ] brief_fields = ('id', 'url', 'display', 'name', 'description') -class ASNSerializer(NetBoxModelSerializer): +class ASNSiteSerializer(PrimaryModelSerializer): + """ + This serializer is meant for inclusion in ASNSerializer and is only used + to avoid a circular import of SiteSerializer. + """ + class Meta: + model = Site + fields = ('id', 'url', 'display', 'name', 'description', 'slug') + brief_fields = ('id', 'url', 'display', 'name', 'description', 'slug') + + +class ASNSerializer(PrimaryModelSerializer): rir = RIRSerializer(nested=True, required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) + sites = SerializedPKRelatedField( + queryset=Site.objects.all(), + serializer=ASNSiteSerializer, + nested=True, + required=False, + many=True + ) # Related object counts site_count = RelatedObjectCountField('sites') @@ -52,8 +72,8 @@ class ASNSerializer(NetBoxModelSerializer): class Meta: model = ASN fields = [ - 'id', 'url', 'display_url', 'display', 'asn', 'rir', 'tenant', 'description', 'comments', 'tags', - 'custom_fields', 'created', 'last_updated', 'site_count', 'provider_count', + 'id', 'url', 'display_url', 'display', 'asn', 'rir', 'tenant', 'description', 'owner', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', 'site_count', 'provider_count', 'sites', ] brief_fields = ('id', 'url', 'display', 'asn', 'description') diff --git a/netbox/ipam/api/serializers_/fhrpgroups.py b/netbox/ipam/api/serializers_/fhrpgroups.py index b5bebbc95..cf8770682 100644 --- a/netbox/ipam/api/serializers_/fhrpgroups.py +++ b/netbox/ipam/api/serializers_/fhrpgroups.py @@ -1,11 +1,8 @@ from django.contrib.contenttypes.models import ContentType -from drf_spectacular.utils import extend_schema_field -from rest_framework import serializers - from ipam.models import FHRPGroup, FHRPGroupAssignment from netbox.api.fields import ContentTypeField -from netbox.api.serializers import NetBoxModelSerializer -from utilities.api import get_serializer_for_model +from netbox.api.gfk_fields import GFKSerializerField +from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer from .ip import IPAddressSerializer __all__ = ( @@ -14,14 +11,14 @@ __all__ = ( ) -class FHRPGroupSerializer(NetBoxModelSerializer): +class FHRPGroupSerializer(PrimaryModelSerializer): ip_addresses = IPAddressSerializer(nested=True, many=True, read_only=True) class Meta: model = FHRPGroup fields = [ 'id', 'name', 'url', 'display_url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', - 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'ip_addresses', + 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'ip_addresses', ] brief_fields = ('id', 'url', 'display', 'protocol', 'group_id', 'description') @@ -31,7 +28,7 @@ class FHRPGroupAssignmentSerializer(NetBoxModelSerializer): interface_type = ContentTypeField( queryset=ContentType.objects.all() ) - interface = serializers.SerializerMethodField(read_only=True) + interface = GFKSerializerField(read_only=True) class Meta: model = FHRPGroupAssignment @@ -40,11 +37,3 @@ class FHRPGroupAssignmentSerializer(NetBoxModelSerializer): 'priority', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'group', 'interface_type', 'interface_id', 'priority') - - @extend_schema_field(serializers.JSONField(allow_null=True)) - def get_interface(self, obj): - if obj.interface is None: - return None - serializer = get_serializer_for_model(obj.interface) - context = {'request': self.context['request']} - return serializer(obj.interface, nested=True, context=context).data diff --git a/netbox/ipam/api/serializers_/ip.py b/netbox/ipam/api/serializers_/ip.py index ea587eb95..b910dc561 100644 --- a/netbox/ipam/api/serializers_/ip.py +++ b/netbox/ipam/api/serializers_/ip.py @@ -1,5 +1,4 @@ from django.contrib.contenttypes.models import ContentType -from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from dcim.constants import LOCATION_SCOPE_TYPES @@ -7,9 +6,9 @@ from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS from ipam.models import Aggregate, IPAddress, IPRange, Prefix from netbox.api.fields import ChoiceField, ContentTypeField -from netbox.api.serializers import NetBoxModelSerializer +from netbox.api.gfk_fields import GFKSerializerField +from netbox.api.serializers import PrimaryModelSerializer from tenancy.api.serializers_.tenants import TenantSerializer -from utilities.api import get_serializer_for_model from .asns import RIRSerializer from .nested import NestedIPAddressSerializer from .roles import RoleSerializer @@ -28,7 +27,7 @@ __all__ = ( ) -class AggregateSerializer(NetBoxModelSerializer): +class AggregateSerializer(PrimaryModelSerializer): family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) rir = RIRSerializer(nested=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) @@ -38,12 +37,12 @@ class AggregateSerializer(NetBoxModelSerializer): model = Aggregate fields = [ 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', - 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'description') -class PrefixSerializer(NetBoxModelSerializer): +class PrefixSerializer(PrimaryModelSerializer): family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) vrf = VRFSerializer(nested=True, required=False, allow_null=True) scope_type = ContentTypeField( @@ -55,7 +54,7 @@ class PrefixSerializer(NetBoxModelSerializer): default=None ) scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) - scope = serializers.SerializerMethodField(read_only=True) + scope = GFKSerializerField(read_only=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) vlan = VLANSerializer(nested=True, required=False, allow_null=True) status = ChoiceField(choices=PrefixStatusChoices, required=False) @@ -69,7 +68,7 @@ class PrefixSerializer(NetBoxModelSerializer): fields = [ 'id', 'url', 'display_url', 'display', 'family', 'aggregate', 'parent', 'prefix', 'vrf', 'scope_type', 'scope_id', 'scope', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description', - 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_children', '_depth', + 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_children', '_depth', ] brief_fields = ('id', 'url', 'display', 'family', 'aggregate', 'parent', 'prefix', 'description', '_depth') @@ -79,14 +78,6 @@ class PrefixSerializer(NetBoxModelSerializer): return fields - @extend_schema_field(serializers.JSONField(allow_null=True)) - def get_scope(self, obj): - if obj.scope_id is None: - return None - serializer = get_serializer_for_model(obj.scope) - context = {'request': self.context['request']} - return serializer(obj.scope, nested=True, context=context).data - class PrefixLengthSerializer(serializers.Serializer): @@ -139,7 +130,8 @@ class AvailablePrefixSerializer(serializers.Serializer): # IP ranges # -class IPRangeSerializer(NetBoxModelSerializer): + +class IPRangeSerializer(PrimaryModelSerializer): prefix = PrefixSerializer(nested=True, required=False, allow_null=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) start_address = IPAddressField() @@ -153,7 +145,7 @@ class IPRangeSerializer(NetBoxModelSerializer): model = IPRange fields = [ 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'start_address', 'end_address', 'size', 'vrf', - 'tenant', 'status', 'role', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'tenant', 'status', 'role', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'mark_populated', 'mark_utilized', ] brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'start_address', 'end_address', 'description') @@ -163,7 +155,7 @@ class IPRangeSerializer(NetBoxModelSerializer): # IP addresses # -class IPAddressSerializer(NetBoxModelSerializer): +class IPAddressSerializer(PrimaryModelSerializer): prefix = PrefixSerializer(nested=True, required=False, allow_null=True) family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) address = IPAddressField() @@ -176,7 +168,7 @@ class IPAddressSerializer(NetBoxModelSerializer): required=False, allow_null=True ) - assigned_object = serializers.SerializerMethodField(read_only=True) + assigned_object = GFKSerializerField(read_only=True) nat_inside = NestedIPAddressSerializer(required=False, allow_null=True) nat_outside = NestedIPAddressSerializer(many=True, read_only=True) @@ -185,18 +177,10 @@ class IPAddressSerializer(NetBoxModelSerializer): fields = [ 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'address', 'vrf', 'tenant', 'status', 'role', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', - 'dns_name', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'dns_name', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'address', 'description') - @extend_schema_field(serializers.JSONField(allow_null=True)) - def get_assigned_object(self, obj): - if obj.assigned_object is None: - return None - serializer = get_serializer_for_model(obj.assigned_object) - context = {'request': self.context['request']} - return serializer(obj.assigned_object, nested=True, context=context).data - class AvailableIPSerializer(serializers.Serializer): """ diff --git a/netbox/ipam/api/serializers_/roles.py b/netbox/ipam/api/serializers_/roles.py index 99fd6f470..80a892659 100644 --- a/netbox/ipam/api/serializers_/roles.py +++ b/netbox/ipam/api/serializers_/roles.py @@ -1,13 +1,13 @@ from ipam.models import Role from netbox.api.fields import RelatedObjectCountField -from netbox.api.serializers import NetBoxModelSerializer +from netbox.api.serializers import OrganizationalModelSerializer __all__ = ( 'RoleSerializer', ) -class RoleSerializer(NetBoxModelSerializer): +class RoleSerializer(OrganizationalModelSerializer): # Related object counts prefix_count = RelatedObjectCountField('prefixes') @@ -16,7 +16,7 @@ class RoleSerializer(NetBoxModelSerializer): class Meta: model = Role fields = [ - 'id', 'url', 'display_url', 'display', 'name', 'slug', 'weight', 'description', 'tags', 'custom_fields', - 'created', 'last_updated', 'prefix_count', 'vlan_count', + 'id', 'url', 'display_url', 'display', 'name', 'slug', 'weight', 'description', 'owner', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', 'prefix_count', 'vlan_count', ] brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'prefix_count', 'vlan_count') diff --git a/netbox/ipam/api/serializers_/services.py b/netbox/ipam/api/serializers_/services.py index c7c1bb136..824fc5738 100644 --- a/netbox/ipam/api/serializers_/services.py +++ b/netbox/ipam/api/serializers_/services.py @@ -1,13 +1,11 @@ from django.contrib.contenttypes.models import ContentType -from drf_spectacular.utils import extend_schema_field -from rest_framework import serializers from ipam.choices import * from ipam.constants import SERVICE_ASSIGNMENT_MODELS from ipam.models import IPAddress, Service, ServiceTemplate from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField -from netbox.api.serializers import NetBoxModelSerializer -from utilities.api import get_serializer_for_model +from netbox.api.gfk_fields import GFKSerializerField +from netbox.api.serializers import PrimaryModelSerializer from .ip import IPAddressSerializer __all__ = ( @@ -16,19 +14,19 @@ __all__ = ( ) -class ServiceTemplateSerializer(NetBoxModelSerializer): +class ServiceTemplateSerializer(PrimaryModelSerializer): protocol = ChoiceField(choices=ServiceProtocolChoices, required=False) class Meta: model = ServiceTemplate fields = [ - 'id', 'url', 'display_url', 'display', 'name', 'protocol', 'ports', 'description', 'comments', 'tags', - 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'display_url', 'display', 'name', 'protocol', 'ports', 'description', 'owner', 'comments', + 'tags', 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'name', 'protocol', 'ports', 'description') -class ServiceSerializer(NetBoxModelSerializer): +class ServiceSerializer(PrimaryModelSerializer): protocol = ChoiceField(choices=ServiceProtocolChoices, required=False) ipaddresses = SerializedPKRelatedField( queryset=IPAddress.objects.all(), @@ -40,21 +38,13 @@ class ServiceSerializer(NetBoxModelSerializer): parent_object_type = ContentTypeField( queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS) ) - parent = serializers.SerializerMethodField(read_only=True) + parent = GFKSerializerField(read_only=True) class Meta: model = Service fields = [ 'id', 'url', 'display_url', 'display', 'parent_object_type', 'parent_object_id', 'parent', 'name', - 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags', 'custom_fields', + 'protocol', 'ports', 'ipaddresses', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'name', 'protocol', 'ports', 'description') - - @extend_schema_field(serializers.JSONField(allow_null=True)) - def get_parent(self, obj): - if obj.parent is None: - return None - serializer = get_serializer_for_model(obj.parent) - context = {'request': self.context['request']} - return serializer(obj.parent, nested=True, context=context).data diff --git a/netbox/ipam/api/serializers_/vlans.py b/netbox/ipam/api/serializers_/vlans.py index 3eada3193..871d6eadb 100644 --- a/netbox/ipam/api/serializers_/vlans.py +++ b/netbox/ipam/api/serializers_/vlans.py @@ -1,5 +1,4 @@ from django.contrib.contenttypes.models import ContentType -from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from dcim.api.serializers_.sites import SiteSerializer @@ -7,9 +6,9 @@ from ipam.choices import * from ipam.constants import VLANGROUP_SCOPE_TYPES from ipam.models import VLAN, VLANGroup, VLANTranslationPolicy, VLANTranslationRule from netbox.api.fields import ChoiceField, ContentTypeField, IntegerRangeSerializer, RelatedObjectCountField -from netbox.api.serializers import NetBoxModelSerializer +from netbox.api.gfk_fields import GFKSerializerField +from netbox.api.serializers import NetBoxModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer from tenancy.api.serializers_.tenants import TenantSerializer -from utilities.api import get_serializer_for_model from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer from .nested import NestedVLANSerializer from .roles import RoleSerializer @@ -24,7 +23,7 @@ __all__ = ( ) -class VLANGroupSerializer(NetBoxModelSerializer): +class VLANGroupSerializer(OrganizationalModelSerializer): scope_type = ContentTypeField( queryset=ContentType.objects.filter( model__in=VLANGROUP_SCOPE_TYPES @@ -34,7 +33,7 @@ class VLANGroupSerializer(NetBoxModelSerializer): default=None ) scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) - scope = serializers.SerializerMethodField(read_only=True) + scope = GFKSerializerField(read_only=True) vid_ranges = IntegerRangeSerializer(many=True, required=False) utilization = serializers.CharField(read_only=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True) @@ -46,21 +45,14 @@ class VLANGroupSerializer(NetBoxModelSerializer): model = VLANGroup fields = [ 'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vid_ranges', - 'tenant', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization' + 'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'vlan_count', 'utilization', ] brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count') validators = [] - @extend_schema_field(serializers.JSONField(allow_null=True)) - def get_scope(self, obj): - if obj.scope_id is None: - return None - serializer = get_serializer_for_model(obj.scope) - context = {'request': self.context['request']} - return serializer(obj.scope, nested=True, context=context).data - -class VLANSerializer(NetBoxModelSerializer): +class VLANSerializer(PrimaryModelSerializer): site = SiteSerializer(nested=True, required=False, allow_null=True) group = VLANGroupSerializer(nested=True, required=False, allow_null=True, default=None) tenant = TenantSerializer(nested=True, required=False, allow_null=True) @@ -77,7 +69,7 @@ class VLANSerializer(NetBoxModelSerializer): model = VLAN fields = [ 'id', 'url', 'display_url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', - 'description', 'qinq_role', 'qinq_svlan', 'comments', 'l2vpn_termination', 'tags', 'custom_fields', + 'description', 'qinq_role', 'qinq_svlan', 'owner', 'comments', 'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count', ] brief_fields = ('id', 'url', 'display', 'vid', 'name', 'description') @@ -125,10 +117,10 @@ class VLANTranslationRuleSerializer(NetBoxModelSerializer): fields = ['id', 'url', 'display', 'policy', 'local_vid', 'remote_vid', 'description'] -class VLANTranslationPolicySerializer(NetBoxModelSerializer): +class VLANTranslationPolicySerializer(PrimaryModelSerializer): rules = VLANTranslationRuleSerializer(many=True, read_only=True) class Meta: model = VLANTranslationPolicy - fields = ['id', 'url', 'display', 'name', 'description', 'display', 'rules'] + fields = ['id', 'url', 'display', 'name', 'description', 'display', 'rules', 'owner', 'comments'] brief_fields = ('id', 'url', 'display', 'name', 'description') diff --git a/netbox/ipam/api/serializers_/vrfs.py b/netbox/ipam/api/serializers_/vrfs.py index a23909108..67630f83c 100644 --- a/netbox/ipam/api/serializers_/vrfs.py +++ b/netbox/ipam/api/serializers_/vrfs.py @@ -1,6 +1,6 @@ from ipam.models import RouteTarget, VRF from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField -from netbox.api.serializers import NetBoxModelSerializer +from netbox.api.serializers import PrimaryModelSerializer from tenancy.api.serializers_.tenants import TenantSerializer __all__ = ( @@ -9,19 +9,19 @@ __all__ = ( ) -class RouteTargetSerializer(NetBoxModelSerializer): +class RouteTargetSerializer(PrimaryModelSerializer): tenant = TenantSerializer(nested=True, required=False, allow_null=True) class Meta: model = RouteTarget fields = [ - 'id', 'url', 'display_url', 'display', 'name', 'tenant', 'description', 'comments', 'tags', + 'id', 'url', 'display_url', 'display', 'name', 'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'name', 'description') -class VRFSerializer(NetBoxModelSerializer): +class VRFSerializer(PrimaryModelSerializer): tenant = TenantSerializer(nested=True, required=False, allow_null=True) import_targets = SerializedPKRelatedField( queryset=RouteTarget.objects.all(), @@ -43,8 +43,8 @@ class VRFSerializer(NetBoxModelSerializer): class Meta: model = VRF fields = [ - 'id', 'url', 'display_url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', - 'import_targets', 'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', - 'prefix_count', + 'id', 'url', 'display_url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'owner', + 'comments', 'import_targets', 'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', + 'ipaddress_count', 'prefix_count', ] brief_fields = ('id', 'url', 'display', 'name', 'rd', 'description', 'prefix_count') diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index a82976326..c5930d839 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -26,6 +26,9 @@ class BaseIPField(models.Field): def from_db_value(self, value, expression, connection): return self.to_python(value) + def get_internal_type(self): + return 'CharField' + def to_python(self, value): if not value: return value @@ -57,7 +60,7 @@ class IPNetworkField(BaseIPField): """ IP prefix (network and mask) """ - description = "PostgreSQL CIDR field" + description = 'PostgreSQL CIDR field' default_validators = [validators.prefix_validator] def db_type(self, connection): @@ -83,7 +86,7 @@ class IPAddressField(BaseIPField): """ IP address (host address and mask) """ - description = "PostgreSQL INET field" + description = 'PostgreSQL INET field' def db_type(self, connection): return 'inet' @@ -110,7 +113,7 @@ IPAddressField.register_lookup(lookups.Inet) class ASNField(models.BigIntegerField): - description = "32-bit ASN field" + description = '32-bit ASN field' default_validators = [ MinValueValidator(BGP_ASN_MIN), MaxValueValidator(BGP_ASN_MAX), diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 36e195900..daf7cfc6e 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -1,6 +1,5 @@ import django_filters import netaddr -from dcim.base_filtersets import ScopedFilterSet from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db.models import Q @@ -10,13 +9,16 @@ from drf_spectacular.utils import extend_schema_field from netaddr.core import AddrFormatError from circuits.models import Provider +from dcim.base_filtersets import ScopedFilterSet from dcim.models import Device, Interface, Region, Site, SiteGroup -from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet +from netbox.filtersets import ( + ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet, PrimaryModelFilterSet, +) from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet - from utilities.filters import ( ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, ) +from utilities.filtersets import register_filterset from virtualization.models import VirtualMachine, VMInterface from vpn.models import L2VPN from .choices import * @@ -45,7 +47,8 @@ __all__ = ( ) -class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet): +@register_filterset +class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet): import_target_id = django_filters.ModelMultipleChoiceFilter( field_name='import_targets', queryset=RouteTarget.objects.all(), @@ -83,7 +86,8 @@ class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet): fields = ('id', 'name', 'rd', 'enforce_unique', 'description') -class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet): +@register_filterset +class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet): importing_vrf_id = django_filters.ModelMultipleChoiceFilter( field_name='importing_vrfs', queryset=VRF.objects.all(), @@ -142,6 +146,7 @@ class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet): fields = ('id', 'name', 'description') +@register_filterset class RIRFilterSet(OrganizationalModelFilterSet): class Meta: @@ -149,7 +154,8 @@ class RIRFilterSet(OrganizationalModelFilterSet): fields = ('id', 'name', 'slug', 'is_private', 'description') -class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet): +@register_filterset +class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet): family = django_filters.NumberFilter( field_name='prefix', lookup_expr='family' @@ -196,6 +202,7 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil return queryset.none() +@register_filterset class ASNRangeFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): rir_id = django_filters.ModelMultipleChoiceFilter( queryset=RIR.objects.all(), @@ -221,7 +228,8 @@ class ASNRangeFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): ) -class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): +@register_filterset +class ASNFilterSet(PrimaryModelFilterSet, TenancyFilterSet): rir_id = django_filters.ModelMultipleChoiceFilter( queryset=RIR.objects.all(), label=_('RIR (ID)'), @@ -283,6 +291,7 @@ class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): return queryset.filter(qs_filter) +@register_filterset class RoleFilterSet(OrganizationalModelFilterSet): class Meta: @@ -290,7 +299,8 @@ class RoleFilterSet(OrganizationalModelFilterSet): fields = ('id', 'name', 'slug', 'description', 'weight') -class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, ContactModelFilterSet): +@register_filterset +class PrefixFilterSet(PrimaryModelFilterSet, ScopedFilterSet, TenancyFilterSet, ContactModelFilterSet): family = django_filters.NumberFilter( field_name='prefix', lookup_expr='family' @@ -374,13 +384,13 @@ class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, C vlan_group_id = django_filters.ModelMultipleChoiceFilter( field_name='vlan__group', queryset=VLANGroup.objects.all(), - to_field_name="id", + to_field_name='id', label=_('VLAN Group (ID)'), ) vlan_group = django_filters.ModelMultipleChoiceFilter( field_name='vlan__group__slug', queryset=VLANGroup.objects.all(), - to_field_name="slug", + to_field_name='slug', label=_('VLAN Group (slug)'), ) vlan_id = django_filters.ModelMultipleChoiceFilter( @@ -476,7 +486,8 @@ class PrefixFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet, C ).distinct() -class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilterSet): +@register_filterset +class IPRangeFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet): family = django_filters.NumberFilter( field_name='start_address', lookup_expr='family' @@ -578,7 +589,8 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet, ContactModelFilte return queryset.filter(q) -class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet): +@register_filterset +class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet): family = django_filters.NumberFilter( field_name='address', lookup_expr='family' @@ -635,6 +647,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil to_field_name='rd', label=_('VRF (RD)'), ) + assigned_object_type = ContentTypeFilter() device = MultiValueCharFilter( method='filter_device', field_name='name', @@ -735,12 +748,12 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil return queryset.filter(q) def parse_inet_addresses(self, value): - ''' + """ Parse networks or IP addresses and cast to a format acceptable by the Postgres inet type. Skips invalid values. - ''' + """ parsed = [] for addr in value: if netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr): @@ -758,7 +771,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil # as argument. If they are all invalid, # we return an empty queryset value = self.parse_inet_addresses(value) - if (len(value) == 0): + if len(value) == 0: return queryset.none() try: @@ -823,7 +836,8 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil ) -class FHRPGroupFilterSet(NetBoxModelFilterSet): +@register_filterset +class FHRPGroupFilterSet(PrimaryModelFilterSet): protocol = django_filters.MultipleChoiceFilter( choices=FHRPGroupProtocolChoices ) @@ -844,6 +858,7 @@ class FHRPGroupFilterSet(NetBoxModelFilterSet): return queryset return queryset.filter( Q(description__icontains=value) | + Q(group_id__contains=value) | Q(name__icontains=value) ) @@ -869,6 +884,7 @@ class FHRPGroupFilterSet(NetBoxModelFilterSet): return queryset.filter(ip_filter) +@register_filterset class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet): interface_type = ContentTypeFilter() group_id = django_filters.ModelMultipleChoiceFilter( @@ -923,6 +939,7 @@ class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet): ) +@register_filterset class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): scope_type = ContentTypeFilter() region = django_filters.NumberFilter( @@ -947,7 +964,8 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): method='filter_scope' ) contains_vid = django_filters.NumberFilter( - method='filter_contains_vid' + field_name='vid_ranges', + lookup_expr='range_contains', ) class Meta: @@ -970,23 +988,9 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): scope_id=value ) - def filter_contains_vid(self, queryset, name, value): - """ - Return all VLANGroups which contain the given VLAN ID. - """ - table_name = VLANGroup._meta.db_table - # TODO: See if this can be optimized without compromising queryset integrity - # Expand VLAN ID ranges to query by integer - groups = VLANGroup.objects.raw( - f'SELECT id FROM {table_name}, unnest(vid_ranges) vid_range WHERE %s <@ vid_range', - params=(value,) - ) - return queryset.filter( - pk__in=[g.id for g in groups] - ) - -class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet): +@register_filterset +class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet): region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', @@ -1118,6 +1122,7 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet): def get_for_virtualmachine(self, queryset, name, value): return queryset.get_for_virtualmachine(value) + @extend_schema_field(OpenApiTypes.INT) def filter_interface_id(self, queryset, name, value): if value is None: return queryset.none() @@ -1126,6 +1131,7 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet): Q(interfaces_as_untagged=value) ).distinct() + @extend_schema_field(OpenApiTypes.INT) def filter_vminterface_id(self, queryset, name, value): if value is None: return queryset.none() @@ -1135,7 +1141,8 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet): ).distinct() -class VLANTranslationPolicyFilterSet(NetBoxModelFilterSet): +@register_filterset +class VLANTranslationPolicyFilterSet(PrimaryModelFilterSet): class Meta: model = VLANTranslationPolicy @@ -1151,6 +1158,7 @@ class VLANTranslationPolicyFilterSet(NetBoxModelFilterSet): return queryset.filter(qs_filter) +@register_filterset class VLANTranslationRuleFilterSet(NetBoxModelFilterSet): policy_id = django_filters.ModelMultipleChoiceFilter( queryset=VLANTranslationPolicy.objects.all(), @@ -1182,7 +1190,8 @@ class VLANTranslationRuleFilterSet(NetBoxModelFilterSet): return queryset.filter(qs_filter) -class ServiceTemplateFilterSet(NetBoxModelFilterSet): +@register_filterset +class ServiceTemplateFilterSet(PrimaryModelFilterSet): port = NumericArrayFilter( field_name='ports', lookup_expr='contains' @@ -1202,7 +1211,9 @@ class ServiceTemplateFilterSet(NetBoxModelFilterSet): return queryset.filter(qs_filter) -class ServiceFilterSet(ContactModelFilterSet, NetBoxModelFilterSet): +@register_filterset +class ServiceFilterSet(ContactModelFilterSet, PrimaryModelFilterSet): + parent_object_type = ContentTypeFilter() device = MultiValueCharFilter( method='filter_device', field_name='name', diff --git a/netbox/ipam/forms/bulk_edit.py b/netbox/ipam/forms/bulk_edit.py index e727785e1..069412c4a 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -9,11 +9,11 @@ from ipam.choices import * from ipam.constants import * from ipam.models import * from ipam.models import ASN -from netbox.forms import NetBoxModelBulkEditForm +from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm from tenancy.models import Tenant from utilities.forms import add_blank_choice, get_field_value from utilities.forms.fields import ( - CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, + ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, NumericRangeArrayField, ) from utilities.forms.rendering import FieldSet @@ -41,7 +41,7 @@ __all__ = ( ) -class VRFBulkEditForm(NetBoxModelBulkEditForm): +class VRFBulkEditForm(PrimaryModelBulkEditForm): tenant = DynamicModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), @@ -52,12 +52,6 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm): widget=BulkEditNullBooleanSelect(), label=_('Enforce unique space') ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = VRF fieldsets = ( @@ -66,18 +60,12 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('tenant', 'description', 'comments') -class RouteTargetBulkEditForm(NetBoxModelBulkEditForm): +class RouteTargetBulkEditForm(PrimaryModelBulkEditForm): tenant = DynamicModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = RouteTarget fieldsets = ( @@ -86,26 +74,21 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('tenant', 'description', 'comments') -class RIRBulkEditForm(NetBoxModelBulkEditForm): +class RIRBulkEditForm(OrganizationalModelBulkEditForm): is_private = forms.NullBooleanField( label=_('Is private'), required=False, widget=BulkEditNullBooleanSelect ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) model = RIR fieldsets = ( FieldSet('is_private', 'description'), ) - nullable_fields = ('is_private', 'description') + nullable_fields = ('is_private', 'description', 'comments') -class ASNRangeBulkEditForm(NetBoxModelBulkEditForm): +class ASNRangeBulkEditForm(OrganizationalModelBulkEditForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), required=False, @@ -116,20 +99,15 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm): queryset=Tenant.objects.all(), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) model = ASNRange fieldsets = ( FieldSet('rir', 'tenant', 'description'), ) - nullable_fields = ('description',) + nullable_fields = ('description', 'comments') -class ASNBulkEditForm(NetBoxModelBulkEditForm): +class ASNBulkEditForm(PrimaryModelBulkEditForm): sites = DynamicModelMultipleChoiceField( label=_('Sites'), queryset=Site.objects.all(), @@ -145,12 +123,6 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm): queryset=Tenant.objects.all(), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = ASN fieldsets = ( @@ -159,7 +131,7 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('tenant', 'description', 'comments') -class AggregateBulkEditForm(NetBoxModelBulkEditForm): +class AggregateBulkEditForm(PrimaryModelBulkEditForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), required=False, @@ -174,12 +146,6 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm): label=_('Date added'), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = Aggregate fieldsets = ( @@ -188,25 +154,20 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('date_added', 'description', 'comments') -class RoleBulkEditForm(NetBoxModelBulkEditForm): +class RoleBulkEditForm(OrganizationalModelBulkEditForm): weight = forms.IntegerField( label=_('Weight'), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) model = Role fieldsets = ( FieldSet('weight', 'description'), ) - nullable_fields = ('description',) + nullable_fields = ('description', 'comments') -class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm): +class PrefixBulkEditForm(ScopedBulkEditForm, PrimaryModelBulkEditForm): parent = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, @@ -261,12 +222,6 @@ class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm): widget=BulkEditNullBooleanSelect(), label=_('Treat as fully utilized') ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = Prefix fieldsets = ( @@ -280,7 +235,7 @@ class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm): ) -class IPRangeBulkEditForm(NetBoxModelBulkEditForm): +class IPRangeBulkEditForm(PrimaryModelBulkEditForm): prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, @@ -316,12 +271,6 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm): widget=BulkEditNullBooleanSelect(), label=_('Treat as fully utilized') ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = IPRange fieldsets = ( @@ -332,7 +281,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm): ) -class IPAddressBulkEditForm(NetBoxModelBulkEditForm): +class IPAddressBulkEditForm(PrimaryModelBulkEditForm): prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, @@ -374,12 +323,6 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm): required=False, label=_('DNS name') ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = IPAddress fieldsets = ( @@ -391,7 +334,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm): ) -class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm): +class FHRPGroupBulkEditForm(PrimaryModelBulkEditForm): protocol = forms.ChoiceField( label=_('Protocol'), choices=add_blank_choice(FHRPGroupProtocolChoices), @@ -417,12 +360,6 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm): max_length=100, required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = FHRPGroup fieldsets = ( @@ -432,12 +369,7 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm): nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments') -class VLANGroupBulkEditForm(NetBoxModelBulkEditForm): - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) +class VLANGroupBulkEditForm(OrganizationalModelBulkEditForm): scope_type = ContentTypeChoiceField( queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES), widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}), @@ -467,7 +399,7 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm): FieldSet('scope_type', 'scope', name=_('Scope')), FieldSet('tenant', name=_('Tenancy')), ) - nullable_fields = ('description', 'scope') + nullable_fields = ('description', 'scope', 'comments') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -484,7 +416,7 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm): pass -class VLANBulkEditForm(NetBoxModelBulkEditForm): +class VLANBulkEditForm(PrimaryModelBulkEditForm): region = DynamicModelChoiceField( label=_('Region'), queryset=Region.objects.all(), @@ -527,11 +459,6 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm): queryset=Role.objects.all(), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) qinq_role = forms.ChoiceField( label=_('Q-in-Q role'), choices=add_blank_choice(VLANQinQRoleChoices), @@ -545,7 +472,6 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm): 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE, } ) - comments = CommentField() model = VLAN fieldsets = ( @@ -558,13 +484,7 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm): ) -class VLANTranslationPolicyBulkEditForm(NetBoxModelBulkEditForm): - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - +class VLANTranslationPolicyBulkEditForm(PrimaryModelBulkEditForm): model = VLANTranslationPolicy fieldsets = ( FieldSet('description'), @@ -588,7 +508,7 @@ class VLANTranslationRuleBulkEditForm(NetBoxModelBulkEditForm): fields = ('policy', 'local_vid', 'remote_vid') -class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm): +class ServiceTemplateBulkEditForm(PrimaryModelBulkEditForm): protocol = forms.ChoiceField( label=_('Protocol'), choices=add_blank_choice(ServiceProtocolChoices), @@ -602,12 +522,6 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm): ), required=False ) - description = forms.CharField( - label=_('Description'), - max_length=200, - required=False - ) - comments = CommentField() model = ServiceTemplate fieldsets = ( diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index b0f7d2229..ee3232e57 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -7,7 +7,7 @@ from dcim.forms.mixins import ScopedImportForm from ipam.choices import * from ipam.constants import * from ipam.models import * -from netbox.forms import NetBoxModelImportForm +from netbox.forms import NetBoxModelImportForm, OrganizationalModelImportForm, PrimaryModelImportForm from tenancy.models import Tenant from utilities.forms.fields import ( CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField, @@ -36,7 +36,7 @@ __all__ = ( ) -class VRFImportForm(NetBoxModelImportForm): +class VRFImportForm(PrimaryModelImportForm): tenant = CSVModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), @@ -60,12 +60,12 @@ class VRFImportForm(NetBoxModelImportForm): class Meta: model = VRF fields = ( - 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'comments', - 'tags', + 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'owner', + 'comments', 'tags', ) -class RouteTargetImportForm(NetBoxModelImportForm): +class RouteTargetImportForm(PrimaryModelImportForm): tenant = CSVModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), @@ -76,18 +76,18 @@ class RouteTargetImportForm(NetBoxModelImportForm): class Meta: model = RouteTarget - fields = ('name', 'tenant', 'description', 'comments', 'tags') + fields = ('name', 'tenant', 'description', 'owner', 'comments', 'tags') -class RIRImportForm(NetBoxModelImportForm): +class RIRImportForm(OrganizationalModelImportForm): slug = SlugField() class Meta: model = RIR - fields = ('name', 'slug', 'is_private', 'description', 'tags') + fields = ('name', 'slug', 'is_private', 'description', 'owner', 'comments', 'tags') -class AggregateImportForm(NetBoxModelImportForm): +class AggregateImportForm(PrimaryModelImportForm): rir = CSVModelChoiceField( label=_('RIR'), queryset=RIR.objects.all(), @@ -104,10 +104,10 @@ class AggregateImportForm(NetBoxModelImportForm): class Meta: model = Aggregate - fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags') + fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'owner', 'comments', 'tags') -class ASNRangeImportForm(NetBoxModelImportForm): +class ASNRangeImportForm(OrganizationalModelImportForm): rir = CSVModelChoiceField( label=_('RIR'), queryset=RIR.objects.all(), @@ -124,10 +124,10 @@ class ASNRangeImportForm(NetBoxModelImportForm): class Meta: model = ASNRange - fields = ('name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', 'tags') + fields = ('name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', 'owner', 'comments', 'tags') -class ASNImportForm(NetBoxModelImportForm): +class ASNImportForm(PrimaryModelImportForm): rir = CSVModelChoiceField( label=_('RIR'), queryset=RIR.objects.all(), @@ -144,18 +144,17 @@ class ASNImportForm(NetBoxModelImportForm): class Meta: model = ASN - fields = ('asn', 'rir', 'tenant', 'description', 'comments', 'tags') + fields = ('asn', 'rir', 'tenant', 'description', 'owner', 'comments', 'tags') -class RoleImportForm(NetBoxModelImportForm): - slug = SlugField() +class RoleImportForm(OrganizationalModelImportForm): class Meta: model = Role - fields = ('name', 'slug', 'weight', 'description', 'tags') + fields = ('name', 'slug', 'weight', 'description', 'owner', 'comments', 'tags') -class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm): +class PrefixImportForm(ScopedImportForm, PrimaryModelImportForm): aggregate = CSVModelChoiceField( label=_('Aggregate'), queryset=Aggregate.objects.all(), @@ -220,7 +219,7 @@ class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm): model = Prefix fields = ( 'prefix', 'vrf', 'tenant', 'vlan_group', 'vlan_site', 'vlan', 'status', 'role', 'scope_type', 'scope_id', - 'is_pool', 'mark_utilized', 'description', 'comments', 'tags', + 'is_pool', 'mark_utilized', 'description', 'owner', 'comments', 'tags', ) labels = { 'scope_id': _('Scope ID'), @@ -267,7 +266,7 @@ class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm): self.fields['parent'].queryset = queryset -class IPRangeImportForm(NetBoxModelImportForm): +class IPRangeImportForm(PrimaryModelImportForm): prefix = CSVModelChoiceField( label=_('Prefix'), queryset=Prefix.objects.all(), @@ -306,7 +305,7 @@ class IPRangeImportForm(NetBoxModelImportForm): model = IPRange fields = ( 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_populated', 'mark_utilized', - 'description', 'comments', 'tags', + 'description', 'owner', 'comments', 'tags', ) def __init__(self, data=None, *args, **kwargs): @@ -324,7 +323,7 @@ class IPRangeImportForm(NetBoxModelImportForm): self.fields['prefix'].queryset = queryset -class IPAddressImportForm(NetBoxModelImportForm): +class IPAddressImportForm(PrimaryModelImportForm): prefix = CSVModelChoiceField( label=_('Prefix'), queryset=Prefix.objects.all(), @@ -400,7 +399,7 @@ class IPAddressImportForm(NetBoxModelImportForm): model = IPAddress fields = [ 'prefix', 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', - 'fhrp_group', 'is_primary', 'is_oob', 'dns_name', 'description', 'comments', 'tags', + 'fhrp_group', 'is_primary', 'is_oob', 'dns_name', 'owner', 'description', 'comments', 'tags', ] def __init__(self, data=None, *args, **kwargs): @@ -429,6 +428,20 @@ class IPAddressImportForm(NetBoxModelImportForm): **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']} ) + def clean_is_primary(self): + # Make sure is_primary is None when it's not included in the uploaded data + if 'is_primary' not in self.data: + return None + else: + return self.cleaned_data['is_primary'] + + def clean_is_oob(self): + # Make sure is_oob is None when it's not included in the uploaded data + if 'is_oob' not in self.data: + return None + else: + return self.cleaned_data['is_oob'] + def clean(self): super().clean() @@ -471,24 +484,24 @@ class IPAddressImportForm(NetBoxModelImportForm): ipaddress = super().save(*args, **kwargs) # Set as primary for device/VM - if self.cleaned_data.get('is_primary'): + if self.cleaned_data.get('is_primary') is not None: parent = self.cleaned_data.get('device') or self.cleaned_data.get('virtual_machine') if self.instance.address.version == 4: - parent.primary_ip4 = ipaddress + parent.primary_ip4 = ipaddress if self.cleaned_data.get('is_primary') else None elif self.instance.address.version == 6: - parent.primary_ip6 = ipaddress + parent.primary_ip6 = ipaddress if self.cleaned_data.get('is_primary') else None parent.save() # Set as OOB for device - if self.cleaned_data.get('is_oob'): + if self.cleaned_data.get('is_oob') is not None: parent = self.cleaned_data.get('device') - parent.oob_ip = ipaddress + parent.oob_ip = ipaddress if self.cleaned_data.get('is_oob') else None parent.save() return ipaddress -class FHRPGroupImportForm(NetBoxModelImportForm): +class FHRPGroupImportForm(PrimaryModelImportForm): protocol = CSVChoiceField( label=_('Protocol'), choices=FHRPGroupProtocolChoices @@ -501,11 +514,10 @@ class FHRPGroupImportForm(NetBoxModelImportForm): class Meta: model = FHRPGroup - fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments', 'tags') + fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'owner', 'comments', 'tags') -class VLANGroupImportForm(NetBoxModelImportForm): - slug = SlugField() +class VLANGroupImportForm(OrganizationalModelImportForm): scope_type = CSVContentTypeField( queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES), required=False, @@ -524,13 +536,15 @@ class VLANGroupImportForm(NetBoxModelImportForm): class Meta: model = VLANGroup - fields = ('name', 'slug', 'scope_type', 'scope_id', 'vid_ranges', 'tenant', 'description', 'tags') + fields = ( + 'name', 'slug', 'scope_type', 'scope_id', 'vid_ranges', 'tenant', 'description', 'owner', 'comments', 'tags' + ) labels = { 'scope_id': 'Scope ID', } -class VLANImportForm(NetBoxModelImportForm): +class VLANImportForm(PrimaryModelImportForm): site = CSVModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -582,15 +596,15 @@ class VLANImportForm(NetBoxModelImportForm): model = VLAN fields = ( 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'qinq_role', 'qinq_svlan', - 'comments', 'tags', + 'owner', 'comments', 'tags', ) -class VLANTranslationPolicyImportForm(NetBoxModelImportForm): +class VLANTranslationPolicyImportForm(PrimaryModelImportForm): class Meta: model = VLANTranslationPolicy - fields = ('name', 'description', 'tags') + fields = ('name', 'description', 'owner', 'comments', 'tags') class VLANTranslationRuleImportForm(NetBoxModelImportForm): @@ -606,7 +620,7 @@ class VLANTranslationRuleImportForm(NetBoxModelImportForm): fields = ('policy', 'local_vid', 'remote_vid') -class ServiceTemplateImportForm(NetBoxModelImportForm): +class ServiceTemplateImportForm(PrimaryModelImportForm): protocol = CSVChoiceField( label=_('Protocol'), choices=ServiceProtocolChoices, @@ -615,10 +629,10 @@ class ServiceTemplateImportForm(NetBoxModelImportForm): class Meta: model = ServiceTemplate - fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags') + fields = ('name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags') -class ServiceImportForm(NetBoxModelImportForm): +class ServiceImportForm(PrimaryModelImportForm): parent_object_type = CSVContentTypeField( queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS), required=True, @@ -650,7 +664,7 @@ class ServiceImportForm(NetBoxModelImportForm): class Meta: model = Service fields = ( - 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'comments', 'tags', + 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags', ) def __init__(self, data=None, *args, **kwargs): diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index c96fbd471..79897b31b 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -5,7 +5,7 @@ from dcim.models import Location, Rack, Region, Site, SiteGroup, Device from ipam.choices import * from ipam.constants import * from ipam.models import * -from netbox.forms import NetBoxModelFilterSetForm +from netbox.forms import NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm, PrimaryModelFilterSetForm from tenancy.forms import ContactModelFilterForm, TenancyFilterForm from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField @@ -42,10 +42,10 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([ ]) -class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class VRFFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): model = VRF fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('import_target_id', 'export_target_id', name=_('Route Targets')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) @@ -62,10 +62,10 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class RouteTargetFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): model = RouteTarget fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('importing_vrf_id', 'exporting_vrf_id', name=_('VRF')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) @@ -82,8 +82,12 @@ class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class RIRFilterForm(NetBoxModelFilterSetForm): +class RIRFilterForm(OrganizationalModelFilterSetForm): model = RIR + fieldsets = ( + FieldSet('q', 'filter_id', 'tag', 'owner_id'), + FieldSet('is_private', name=_('RIR')), + ) is_private = forms.NullBooleanField( required=False, label=_('Private'), @@ -94,10 +98,10 @@ class RIRFilterForm(NetBoxModelFilterSetForm): tag = TagFilterField(model) -class AggregateFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm): +class AggregateFilterForm(ContactModelFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm): model = Aggregate fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('family', 'rir_id', name=_('Attributes')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')), @@ -115,10 +119,10 @@ class AggregateFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModel tag = TagFilterField(model) -class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class ASNRangeFilterForm(TenancyFilterForm, OrganizationalModelFilterSetForm): model = ASNRange fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('rir_id', 'start', 'end', name=_('Range')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) @@ -138,10 +142,10 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class ASNFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): model = ASN fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('rir_id', 'site_group_id', 'site_id', name=_('Assignment')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) @@ -163,15 +167,18 @@ class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class RoleFilterForm(NetBoxModelFilterSetForm): +class RoleFilterForm(OrganizationalModelFilterSetForm): model = Role + fieldsets = ( + FieldSet('q', 'filter_id', 'tag', 'owner_id'), + ) tag = TagFilterField(model) -class PrefixFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm, ): +class PrefixFilterForm(ContactModelFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm): model = Prefix fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet( 'within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized', name=_('Addressing') @@ -280,10 +287,10 @@ class PrefixFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFil tag = TagFilterField(model) -class IPRangeFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm): +class IPRangeFilterForm(ContactModelFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm): model = IPRange fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet( 'prefix', 'family', 'vrf_id', 'status', 'role_id', 'mark_populated', 'mark_utilized', name=_('Attributes') ), @@ -335,10 +342,10 @@ class IPRangeFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFi tag = TagFilterField(model) -class IPAddressFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm): +class IPAddressFilterForm(ContactModelFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm): model = IPAddress fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet( 'prefix', 'parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name', name=_('Attributes') @@ -418,10 +425,10 @@ class IPAddressFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModel tag = TagFilterField(model) -class FHRPGroupFilterForm(NetBoxModelFilterSetForm): +class FHRPGroupFilterForm(PrimaryModelFilterSetForm): model = FHRPGroup fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('name', 'protocol', 'group_id', name=_('Attributes')), FieldSet('auth_type', 'auth_key', name=_('Authentication')), ) @@ -451,9 +458,9 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm): tag = TagFilterField(model) -class VLANGroupFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class VLANGroupFilterForm(TenancyFilterForm, OrganizationalModelFilterSetForm): fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('region', 'site_group', 'site', 'location', 'rack', name=_('Location')), FieldSet('cluster_group', 'cluster', name=_('Cluster')), FieldSet('contains_vid', name=_('VLANs')), @@ -504,10 +511,10 @@ class VLANGroupFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class VLANTranslationPolicyFilterForm(NetBoxModelFilterSetForm): +class VLANTranslationPolicyFilterForm(PrimaryModelFilterSetForm): model = VLANTranslationPolicy fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('name', name=_('Attributes')), ) name = forms.CharField( @@ -541,10 +548,10 @@ class VLANTranslationRuleFilterForm(NetBoxModelFilterSetForm): ) -class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): +class VLANFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm): model = VLAN fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')), FieldSet('group_id', 'status', 'role_id', 'vid', 'l2vpn_id', name=_('Attributes')), FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')), @@ -613,10 +620,10 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): tag = TagFilterField(model) -class ServiceTemplateFilterForm(NetBoxModelFilterSetForm): +class ServiceTemplateFilterForm(PrimaryModelFilterSetForm): model = ServiceTemplate fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('protocol', 'port', name=_('Attributes')), ) protocol = forms.ChoiceField( @@ -634,7 +641,7 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm): class ServiceFilterForm(ContactModelFilterForm, ServiceTemplateFilterForm): model = Service fieldsets = ( - FieldSet('q', 'filter_id', 'tag'), + FieldSet('q', 'filter_id', 'tag', 'owner_id'), FieldSet('protocol', 'port', name=_('Attributes')), FieldSet('device_id', 'virtual_machine_id', 'fhrpgroup_id', name=_('Assignment')), FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')), diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 13b43ea42..51a7b561c 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -9,13 +9,13 @@ from ipam.choices import * from ipam.constants import * from ipam.formfields import IPNetworkFormField from ipam.models import * -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.forms import TenancyForm from utilities.exceptions import PermissionsViolation from utilities.forms import add_blank_choice from utilities.forms.fields import ( - CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, - NumericRangeArrayField, SlugField + ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, + NumericRangeArrayField, ) from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups from utilities.forms.utils import get_field_value @@ -49,7 +49,7 @@ __all__ = ( ) -class VRFForm(TenancyForm, NetBoxModelForm): +class VRFForm(TenancyForm, PrimaryModelForm): import_targets = DynamicModelMultipleChoiceField( label=_('Import targets'), queryset=RouteTarget.objects.all(), @@ -60,7 +60,6 @@ class VRFForm(TenancyForm, NetBoxModelForm): queryset=RouteTarget.objects.all(), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'rd', 'enforce_unique', 'description', 'tags', name=_('VRF')), @@ -72,30 +71,27 @@ class VRFForm(TenancyForm, NetBoxModelForm): model = VRF fields = [ 'name', 'rd', 'enforce_unique', 'import_targets', 'export_targets', 'tenant_group', 'tenant', 'description', - 'comments', 'tags', + 'owner', 'comments', 'tags', ] labels = { 'rd': "RD", } -class RouteTargetForm(TenancyForm, NetBoxModelForm): +class RouteTargetForm(TenancyForm, PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Route Target')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), ) - comments = CommentField() class Meta: model = RouteTarget fields = [ - 'name', 'tenant_group', 'tenant', 'description', 'comments', 'tags', + 'name', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags', ] -class RIRForm(NetBoxModelForm): - slug = SlugField() - +class RIRForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'is_private', 'description', 'tags', name=_('RIR')), ) @@ -103,17 +99,16 @@ class RIRForm(NetBoxModelForm): class Meta: model = RIR fields = [ - 'name', 'slug', 'is_private', 'description', 'tags', + 'name', 'slug', 'is_private', 'description', 'owner', 'comments', 'tags', ] -class AggregateForm(TenancyForm, NetBoxModelForm): +class AggregateForm(TenancyForm, PrimaryModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet('prefix', 'rir', 'date_added', 'description', 'tags', name=_('Aggregate')), @@ -123,20 +118,19 @@ class AggregateForm(TenancyForm, NetBoxModelForm): class Meta: model = Aggregate fields = [ - 'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'comments', 'tags', + 'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags', ] widgets = { 'date_added': DatePicker(), } -class ASNRangeForm(TenancyForm, NetBoxModelForm): +class ASNRangeForm(TenancyForm, OrganizationalModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), quick_add=True ) - slug = SlugField() fieldsets = ( FieldSet('name', 'slug', 'rir', 'start', 'end', 'description', 'tags', name=_('ASN Range')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), @@ -145,11 +139,11 @@ class ASNRangeForm(TenancyForm, NetBoxModelForm): class Meta: model = ASNRange fields = [ - 'name', 'slug', 'rir', 'start', 'end', 'tenant_group', 'tenant', 'description', 'tags' + 'name', 'slug', 'rir', 'start', 'end', 'tenant_group', 'tenant', 'owner', 'description', 'comments', 'tags' ] -class ASNForm(TenancyForm, NetBoxModelForm): +class ASNForm(TenancyForm, PrimaryModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), @@ -160,7 +154,6 @@ class ASNForm(TenancyForm, NetBoxModelForm): label=_('Sites'), required=False ) - comments = CommentField() fieldsets = ( FieldSet('asn', 'rir', 'sites', 'description', 'tags', name=_('ASN')), @@ -170,7 +163,7 @@ class ASNForm(TenancyForm, NetBoxModelForm): class Meta: model = ASN fields = [ - 'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'comments', 'tags' + 'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags' ] widgets = { 'date_added': DatePicker(), @@ -188,9 +181,7 @@ class ASNForm(TenancyForm, NetBoxModelForm): return instance -class RoleForm(NetBoxModelForm): - slug = SlugField() - +class RoleForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'weight', 'description', 'tags', name=_('Role')), ) @@ -198,11 +189,11 @@ class RoleForm(NetBoxModelForm): class Meta: model = Role fields = [ - 'name', 'slug', 'weight', 'description', 'tags', + 'name', 'slug', 'weight', 'description', 'owner', 'comments', 'tags', ] -class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): +class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm): vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -223,7 +214,6 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): required=False, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -238,7 +228,7 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): model = Prefix fields = [ 'prefix', 'vrf', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'scope_type', 'tenant_group', - 'tenant', 'description', 'comments', 'tags', + 'tenant', 'description', 'owner', 'comments', 'tags', ] def __init__(self, *args, **kwargs): @@ -250,7 +240,7 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None) -class IPRangeForm(TenancyForm, NetBoxModelForm): +class IPRangeForm(TenancyForm, PrimaryModelForm): prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, @@ -267,7 +257,6 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): required=False, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -281,11 +270,11 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): model = IPRange fields = [ 'prefix', 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', - 'mark_populated', 'mark_utilized', 'description', 'comments', 'tags', + 'mark_populated', 'mark_utilized', 'description', 'owner', 'comments', 'tags', ] -class IPAddressForm(TenancyForm, NetBoxModelForm): +class IPAddressForm(TenancyForm, PrimaryModelForm): prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, @@ -338,7 +327,6 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): required=False, label=_('Make this the out-of-band IP for the device') ) - comments = CommentField() fieldsets = ( FieldSet('prefix', 'address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags', name=_('IP Address')), @@ -358,7 +346,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): model = IPAddress fields = [ 'prefix', 'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'oob_for_parent', - 'nat_inside', 'tenant_group', 'tenant', 'description', 'comments', 'tags', + 'nat_inside', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags', ] def __init__(self, *args, **kwargs): @@ -517,7 +505,7 @@ class IPAddressAssignForm(forms.Form): ) -class FHRPGroupForm(NetBoxModelForm): +class FHRPGroupForm(PrimaryModelForm): # Optionally create a new IPAddress along with the FHRPGroup ip_vrf = DynamicModelChoiceField( @@ -534,7 +522,6 @@ class FHRPGroupForm(NetBoxModelForm): required=False, label=_('Status') ) - comments = CommentField() fieldsets = ( FieldSet('protocol', 'group_id', 'name', 'description', 'tags', name=_('FHRP Group')), @@ -546,7 +533,7 @@ class FHRPGroupForm(NetBoxModelForm): model = FHRPGroup fields = ( 'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'ip_vrf', 'ip_address', 'ip_status', 'description', - 'comments', 'tags', + 'owner', 'comments', 'tags', ) def save(self, *args, **kwargs): @@ -603,13 +590,6 @@ class FHRPGroupAssignmentForm(forms.ModelForm): model = FHRPGroupAssignment fields = ('group', 'priority') - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ipaddresses = self.instance.interface.ip_addresses.all() - for ipaddress in ipaddresses: - self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk) - def clean_group(self): group = self.cleaned_data['group'] @@ -629,8 +609,7 @@ class FHRPGroupAssignmentForm(forms.ModelForm): return group -class VLANGroupForm(TenancyForm, NetBoxModelForm): - slug = SlugField() +class VLANGroupForm(TenancyForm, OrganizationalModelForm): vid_ranges = NumericRangeArrayField( label=_('VLAN IDs') ) @@ -658,7 +637,8 @@ class VLANGroupForm(TenancyForm, NetBoxModelForm): class Meta: model = VLANGroup fields = [ - 'name', 'slug', 'description', 'vid_ranges', 'scope_type', 'tenant_group', 'tenant', 'tags', + 'name', 'slug', 'description', 'vid_ranges', 'scope_type', 'tenant_group', 'tenant', 'owner', 'comments', + 'tags', ] def __init__(self, *args, **kwargs): @@ -692,7 +672,7 @@ class VLANGroupForm(TenancyForm, NetBoxModelForm): self.instance.scope = self.cleaned_data.get('scope') -class VLANForm(TenancyForm, NetBoxModelForm): +class VLANForm(TenancyForm, PrimaryModelForm): group = DynamicModelChoiceField( queryset=VLANGroup.objects.all(), required=False, @@ -728,17 +708,16 @@ class VLANForm(TenancyForm, NetBoxModelForm): 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE, } ) - comments = CommentField() class Meta: model = VLAN fields = [ 'site', 'group', 'vid', 'name', 'status', 'role', 'tenant_group', 'tenant', 'qinq_role', 'qinq_svlan', - 'description', 'comments', 'tags', + 'description', 'owner', 'comments', 'tags', ] -class VLANTranslationPolicyForm(NetBoxModelForm): +class VLANTranslationPolicyForm(PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('VLAN Translation Policy')), @@ -747,7 +726,7 @@ class VLANTranslationPolicyForm(NetBoxModelForm): class Meta: model = VLANTranslationPolicy fields = [ - 'name', 'description', 'tags', + 'name', 'description', 'owner', 'tags', ] @@ -769,7 +748,7 @@ class VLANTranslationRuleForm(NetBoxModelForm): ] -class ServiceTemplateForm(NetBoxModelForm): +class ServiceTemplateForm(PrimaryModelForm): ports = NumericArrayField( label=_('Ports'), base_field=forms.IntegerField( @@ -778,7 +757,6 @@ class ServiceTemplateForm(NetBoxModelForm): ), help_text=_("Comma-separated list of one or more port numbers. A range may be specified using a hyphen.") ) - comments = CommentField() fieldsets = ( FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Application Service Template')), @@ -786,10 +764,10 @@ class ServiceTemplateForm(NetBoxModelForm): class Meta: model = ServiceTemplate - fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags') + fields = ('name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags') -class ServiceForm(NetBoxModelForm): +class ServiceForm(PrimaryModelForm): parent_object_type = ContentTypeChoiceField( queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS), widget=HTMXSelect(), @@ -816,7 +794,6 @@ class ServiceForm(NetBoxModelForm): required=False, label=_('IP Addresses'), ) - comments = CommentField() fieldsets = ( FieldSet( @@ -829,7 +806,7 @@ class ServiceForm(NetBoxModelForm): class Meta: model = Service fields = [ - 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags', + 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'owner', 'comments', 'tags', 'parent_object_type', ] diff --git a/netbox/ipam/graphql/filter_mixins.py b/netbox/ipam/graphql/filter_mixins.py index 511850285..72d8e1404 100644 --- a/netbox/ipam/graphql/filter_mixins.py +++ b/netbox/ipam/graphql/filter_mixins.py @@ -3,21 +3,20 @@ from typing import Annotated, TYPE_CHECKING import strawberry import strawberry_django - -from core.graphql.filter_mixins import BaseFilterMixin +from strawberry_django import BaseFilterLookup if TYPE_CHECKING: from netbox.graphql.filter_lookups import IntegerLookup from .enums import * __all__ = ( - 'ServiceBaseFilterMixin', + 'ServiceFilterMixin', ) @dataclass -class ServiceBaseFilterMixin(BaseFilterMixin): - protocol: Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( +class ServiceFilterMixin: + protocol: BaseFilterLookup[Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) ports: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py index 1df61efe8..f44bf7dee 100644 --- a/netbox/ipam/graphql/filters.py +++ b/netbox/ipam/graphql/filters.py @@ -7,19 +7,20 @@ import strawberry_django from django.db.models import Q from netaddr.core import AddrFormatError from strawberry.scalars import ID -from strawberry_django import FilterLookup, DateFilterLookup +from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup -from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin from dcim.graphql.filter_mixins import ScopedFilterMixin from dcim.models import Device from ipam import models -from ipam.graphql.filter_mixins import ServiceBaseFilterMixin -from netbox.graphql.filter_mixins import NetBoxModelFilterMixin, OrganizationalModelFilterMixin, PrimaryModelFilterMixin +from ipam.graphql.filter_mixins import ServiceFilterMixin +from netbox.graphql.filters import ( + ChangeLoggedModelFilter, NetBoxModelFilter, OrganizationalModelFilter, PrimaryModelFilter, +) from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin from virtualization.models import VMInterface if TYPE_CHECKING: - from netbox.graphql.filter_lookups import IntegerArrayLookup, IntegerLookup + from netbox.graphql.filter_lookups import IntegerLookup, IntegerRangeArrayLookup from circuits.graphql.filters import ProviderFilter from core.graphql.filters import ContentTypeFilter from dcim.graphql.filters import SiteFilter @@ -49,7 +50,7 @@ __all__ = ( @strawberry_django.filter_type(models.ASN, lookups=True) -class ASNFilter(TenancyFilterMixin, PrimaryModelFilterMixin): +class ASNFilter(TenancyFilterMixin, PrimaryModelFilter): rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() rir_id: ID | None = strawberry_django.filter_field() asn: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( @@ -64,7 +65,7 @@ class ASNFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.ASNRange, lookups=True) -class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilterMixin): +class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() slug: FilterLookup[str] | None = strawberry_django.filter_field() rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -78,24 +79,48 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilterMixin): @strawberry_django.filter_type(models.Aggregate, lookups=True) -class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): - prefix: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() - prefix_id: ID | None = strawberry_django.filter_field() +class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): + prefix: FilterLookup[str] | None = strawberry_django.filter_field() rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() rir_id: ID | None = strawberry_django.filter_field() date_added: DateFilterLookup[date] | None = strawberry_django.filter_field() + @strawberry_django.filter_field() + def contains(self, value: list[str], prefix) -> Q: + """ + Return aggregates whose `prefix` contains any of the supplied networks. + Mirrors PrefixFilter.contains but operates on the Aggregate.prefix field itself. + """ + if not value: + return Q() + q = Q() + for subnet in value: + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + except (AddrFormatError, ValueError): + continue + q |= Q(**{f"{prefix}prefix__net_contains": query}) + return q + + @strawberry_django.filter_field() + def family( + self, + value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], + prefix, + ) -> Q: + return Q(**{f"{prefix}prefix__family": value.value}) + @strawberry_django.filter_type(models.FHRPGroup, lookups=True) -class FHRPGroupFilter(PrimaryModelFilterMixin): +class FHRPGroupFilter(PrimaryModelFilter): group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) auth_key: FilterLookup[str] | None = strawberry_django.filter_field() @@ -105,7 +130,7 @@ class FHRPGroupFilter(PrimaryModelFilterMixin): @strawberry_django.filter_type(models.FHRPGroupAssignment, lookups=True) -class FHRPGroupAssignmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): +class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter): interface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -119,40 +144,40 @@ class FHRPGroupAssignmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin) ) @strawberry_django.filter_field() - def device_id(self, queryset, value: list[str], prefix) -> Q: - return self.filter_device('id', value) + def device_id(self, value: list[str], prefix) -> Q: + return self.filter_device('id', value, prefix) @strawberry_django.filter_field() def device(self, value: list[str], prefix) -> Q: - return self.filter_device('name', value) + return self.filter_device('name', value, prefix) @strawberry_django.filter_field() def virtual_machine_id(self, value: list[str], prefix) -> Q: - return Q(interface_id__in=VMInterface.objects.filter(virtual_machine_id__in=value)) + return Q(**{f"{prefix}interface_id__in": VMInterface.objects.filter(virtual_machine_id__in=value)}) @strawberry_django.filter_field() def virtual_machine(self, value: list[str], prefix) -> Q: - return Q(interface_id__in=VMInterface.objects.filter(virtual_machine__name__in=value)) + return Q(**{f"{prefix}interface_id__in": VMInterface.objects.filter(virtual_machine__name__in=value)}) - def filter_device(self, field, value) -> Q: + def filter_device(self, field, value, prefix) -> Q: """Helper to standardize logic for device and device_id filters""" devices = Device.objects.filter(**{f'{field}__in': value}) interface_ids = [] for device in devices: interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) - return Q(interface_id__in=interface_ids) + return Q(**{f"{prefix}interface_id__in": interface_ids}) @strawberry_django.filter_type(models.IPAddress, lookups=True) -class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): +class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): prefix: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() address: FilterLookup[str] | None = strawberry_django.filter_field() vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) - role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + role: BaseFilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( @@ -171,7 +196,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter @strawberry_django.filter_field() def assigned(self, value: bool, prefix) -> Q: - return Q(assigned_object_id__isnull=(not value)) + return Q(**{f"{prefix}assigned_object_id__isnull": not value}) @strawberry_django.filter_field() def parent(self, value: list[str], prefix) -> Q: @@ -181,9 +206,9 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter for subnet in value: try: query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(address__net_host_contained=query) except (AddrFormatError, ValueError): - return Q() + continue + q |= Q(**{f"{prefix}address__net_host_contained": query}) return q @strawberry_django.filter_field() @@ -196,7 +221,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter @strawberry_django.filter_type(models.IPRange, lookups=True) -class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): +class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): prefix: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() start_address: FilterLookup[str] | None = strawberry_django.filter_field() end_address: FilterLookup[str] | None = strawberry_django.filter_field() @@ -205,7 +230,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi ) vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vrf_id: ID | None = strawberry_django.filter_field() - status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: BaseFilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -219,9 +244,14 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi for subnet in value: try: query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(start_address__net_host_contained=query, end_address__net_host_contained=query) except (AddrFormatError, ValueError): - return Q() + continue + q |= Q( + **{ + f"{prefix}start_address__net_host_contained": query, + f"{prefix}end_address__net_host_contained": query, + } + ) return q @strawberry_django.filter_field() @@ -230,16 +260,23 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi return Q() q = Q() for subnet in value: - net = netaddr.IPNetwork(subnet.strip()) + try: + net = netaddr.IPNetwork(subnet.strip()) + query_start = str(netaddr.IPAddress(net.first)) + query_end = str(netaddr.IPAddress(net.last)) + except (AddrFormatError, ValueError): + continue q |= Q( - start_address__host__inet__lte=str(netaddr.IPAddress(net.first)), - end_address__host__inet__gte=str(netaddr.IPAddress(net.last)), + **{ + f"{prefix}start_address__host__inet__lte": query_start, + f"{prefix}end_address__host__inet__gte": query_end, + } ) return q @strawberry_django.filter_type(models.Prefix, lookups=True) -class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin): +class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter): aggregate: Annotated['AggregateFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -249,7 +286,7 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr vrf_id: ID | None = strawberry_django.filter_field() vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() vlan_id: ID | None = strawberry_django.filter_field() - status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + status: BaseFilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @@ -263,25 +300,36 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr return Q() q = Q() for subnet in value: - query = str(netaddr.IPNetwork(subnet.strip()).cidr) - q |= Q(prefix__net_contains=query) + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + except (AddrFormatError, ValueError): + continue + q |= Q(**{f"{prefix}prefix__net_contains": query}) return q + @strawberry_django.filter_field() + def family( + self, + value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], + prefix, + ) -> Q: + return Q(**{f"{prefix}prefix__family": value.value}) + @strawberry_django.filter_type(models.RIR, lookups=True) -class RIRFilter(OrganizationalModelFilterMixin): +class RIRFilter(OrganizationalModelFilter): is_private: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.Role, lookups=True) -class RoleFilter(OrganizationalModelFilterMixin): +class RoleFilter(OrganizationalModelFilter): weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.RouteTarget, lookups=True) -class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilterMixin): +class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() importing_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() @@ -298,7 +346,7 @@ class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.Service, lookups=True) -class ServiceFilter(ContactFilterMixin, ServiceBaseFilterMixin, PrimaryModelFilterMixin): +class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() @@ -310,12 +358,12 @@ class ServiceFilter(ContactFilterMixin, ServiceBaseFilterMixin, PrimaryModelFilt @strawberry_django.filter_type(models.ServiceTemplate, lookups=True) -class ServiceTemplateFilter(ServiceBaseFilterMixin, PrimaryModelFilterMixin): +class ServiceTemplateFilter(ServiceFilterMixin, PrimaryModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.VLAN, lookups=True) -class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): +class VLANFilter(TenancyFilterMixin, PrimaryModelFilter): site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() site_id: ID | None = strawberry_django.filter_field() group: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -326,7 +374,9 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): strawberry_django.filter_field() ) name: FilterLookup[str] | None = strawberry_django.filter_field() - status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field() + status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( + strawberry_django.filter_field() + ) role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() role_id: ID | None = strawberry_django.filter_field() qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( @@ -336,7 +386,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): qinq_cvlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) - qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + qinq_role: BaseFilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( strawberry_django.filter_field() ) l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( @@ -345,19 +395,19 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin): @strawberry_django.filter_type(models.VLANGroup, lookups=True) -class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilterMixin): - vid_ranges: Annotated['IntegerArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( +class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter): + vid_ranges: Annotated['IntegerRangeArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) @strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True) -class VLANTranslationPolicyFilter(PrimaryModelFilterMixin): +class VLANTranslationPolicyFilter(PrimaryModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter_type(models.VLANTranslationRule, lookups=True) -class VLANTranslationRuleFilter(NetBoxModelFilterMixin): +class VLANTranslationRuleFilter(NetBoxModelFilter): policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( strawberry_django.filter_field() ) @@ -372,7 +422,7 @@ class VLANTranslationRuleFilter(NetBoxModelFilterMixin): @strawberry_django.filter_type(models.VRF, lookups=True) -class VRFFilter(TenancyFilterMixin, PrimaryModelFilterMixin): +class VRFFilter(TenancyFilterMixin, PrimaryModelFilter): name: FilterLookup[str] | None = strawberry_django.filter_field() rd: FilterLookup[str] | None = strawberry_django.filter_field() enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 39eec74e5..e2cf7147c 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -8,7 +8,7 @@ from dcim.graphql.types import SiteType from extras.graphql.mixins import ContactsMixin from ipam import models from netbox.graphql.scalars import BigInt -from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType +from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType, PrimaryObjectType from .filters import * from .mixins import IPAddressesMixin @@ -74,7 +74,7 @@ class BaseIPAddressFamilyType: filters=ASNFilter, pagination=True ) -class ASNType(NetBoxObjectType): +class ASNType(ContactsMixin, PrimaryObjectType): asn: BigInt rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None @@ -89,7 +89,7 @@ class ASNType(NetBoxObjectType): filters=ASNRangeFilter, pagination=True ) -class ASNRangeType(NetBoxObjectType): +class ASNRangeType(OrganizationalObjectType): start: BigInt end: BigInt rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None @@ -102,7 +102,7 @@ class ASNRangeType(NetBoxObjectType): filters=AggregateFilter, pagination=True ) -class AggregateType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): +class AggregateType(ContactsMixin, BaseIPAddressFamilyType, PrimaryObjectType): prefix: str rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None @@ -114,8 +114,7 @@ class AggregateType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): filters=FHRPGroupFilter, pagination=True ) -class FHRPGroupType(NetBoxObjectType, IPAddressesMixin): - +class FHRPGroupType(IPAddressesMixin, PrimaryObjectType): fhrpgroupassignment_set: List[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]] @@ -142,7 +141,7 @@ class FHRPGroupAssignmentType(BaseObjectType): filters=IPAddressFilter, pagination=True ) -class IPAddressType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): +class IPAddressType(ContactsMixin, BaseIPAddressFamilyType, PrimaryObjectType): address: str prefix: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None @@ -168,7 +167,7 @@ class IPAddressType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): filters=IPRangeFilter, pagination=True ) -class IPRangeType(NetBoxObjectType, ContactsMixin): +class IPRangeType(ContactsMixin, PrimaryObjectType): prefix: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None start_address: str end_address: str @@ -183,7 +182,7 @@ class IPRangeType(NetBoxObjectType, ContactsMixin): filters=PrefixFilter, pagination=True ) -class PrefixType(NetBoxObjectType, ContactsMixin, BaseIPAddressFamilyType): +class PrefixType(ContactsMixin, BaseIPAddressFamilyType, PrimaryObjectType): aggregate: Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')] | None parent: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None prefix: str @@ -234,7 +233,7 @@ class RoleType(OrganizationalObjectType): filters=RouteTargetFilter, pagination=True ) -class RouteTargetType(NetBoxObjectType): +class RouteTargetType(PrimaryObjectType): tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None importing_l2vpns: List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]] @@ -249,7 +248,7 @@ class RouteTargetType(NetBoxObjectType): filters=ServiceFilter, pagination=True ) -class ServiceType(NetBoxObjectType, ContactsMixin): +class ServiceType(ContactsMixin, PrimaryObjectType): ports: List[int] ipaddresses: List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]] @@ -268,7 +267,7 @@ class ServiceType(NetBoxObjectType, ContactsMixin): filters=ServiceTemplateFilter, pagination=True ) -class ServiceTemplateType(NetBoxObjectType): +class ServiceTemplateType(PrimaryObjectType): ports: List[int] @@ -278,7 +277,7 @@ class ServiceTemplateType(NetBoxObjectType): filters=VLANFilter, pagination=True ) -class VLANType(NetBoxObjectType): +class VLANType(PrimaryObjectType): 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 @@ -327,7 +326,7 @@ class VLANGroupType(OrganizationalObjectType): filters=VLANTranslationPolicyFilter, pagination=True ) -class VLANTranslationPolicyType(NetBoxObjectType): +class VLANTranslationPolicyType(PrimaryObjectType): rules: List[Annotated["VLANTranslationRuleType", strawberry.lazy('ipam.graphql.types')]] @@ -350,7 +349,7 @@ class VLANTranslationRuleType(NetBoxObjectType): filters=VRFFilter, pagination=True ) -class VRFType(NetBoxObjectType): +class VRFType(PrimaryObjectType): tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]] diff --git a/netbox/ipam/migrations/0083_vlangroup_populate_total_vlan_ids.py b/netbox/ipam/migrations/0083_vlangroup_populate_total_vlan_ids.py new file mode 100644 index 000000000..430897588 --- /dev/null +++ b/netbox/ipam/migrations/0083_vlangroup_populate_total_vlan_ids.py @@ -0,0 +1,27 @@ +from django.db import migrations + + +def populate_vlangroup_total_vlan_ids(apps, schema_editor): + VLANGroup = apps.get_model('ipam', 'VLANGroup') + db_alias = schema_editor.connection.alias + + vlan_groups = VLANGroup.objects.using(db_alias).only('id', 'vid_ranges') + for group in vlan_groups: + total_vlan_ids = 0 + if group.vid_ranges: + for r in group.vid_ranges: + # Half-open [lo, hi): length is (hi - lo). + if r is not None and r.lower is not None and r.upper is not None: + total_vlan_ids += r.upper - r.lower + group._total_vlan_ids = total_vlan_ids + VLANGroup.objects.using(db_alias).bulk_update(vlan_groups, ['_total_vlan_ids'], batch_size=100) + + +class Migration(migrations.Migration): + dependencies = [ + ('ipam', '0082_add_prefix_network_containment_indexes'), + ] + + operations = [ + migrations.RunPython(populate_vlangroup_total_vlan_ids, migrations.RunPython.noop), + ] diff --git a/netbox/ipam/migrations/0084_owner.py b/netbox/ipam/migrations/0084_owner.py new file mode 100644 index 000000000..6c872c2a7 --- /dev/null +++ b/netbox/ipam/migrations/0084_owner.py @@ -0,0 +1,124 @@ +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('ipam', '0083_vlangroup_populate_total_vlan_ids'), + ('users', '0015_owner'), + ] + + operations = [ + migrations.AddField( + model_name='aggregate', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='asn', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='asnrange', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='fhrpgroup', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='ipaddress', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='iprange', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='prefix', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='rir', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='role', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='routetarget', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='service', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='servicetemplate', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='vlan', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='vlangroup', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='vlantranslationpolicy', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + migrations.AddField( + model_name='vrf', + name='owner', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner' + ), + ), + ] diff --git a/netbox/ipam/migrations/0085_add_comments_to_organizationalmodel.py b/netbox/ipam/migrations/0085_add_comments_to_organizationalmodel.py new file mode 100644 index 000000000..b7d947350 --- /dev/null +++ b/netbox/ipam/migrations/0085_add_comments_to_organizationalmodel.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.8 on 2025-12-08 17:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0084_owner'), + ] + + operations = [ + migrations.AddField( + model_name='asnrange', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='rir', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='role', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='vlangroup', + name='comments', + field=models.TextField(blank=True), + ), + ] diff --git a/netbox/ipam/migrations/0086_gfk_indexes.py b/netbox/ipam/migrations/0086_gfk_indexes.py new file mode 100644 index 000000000..a5014b7e0 --- /dev/null +++ b/netbox/ipam/migrations/0086_gfk_indexes.py @@ -0,0 +1,19 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dcim', '0225_gfk_indexes'), + ('extras', '0134_owner'), + ('ipam', '0085_add_comments_to_organizationalmodel'), + ('tenancy', '0022_add_comments_to_organizationalmodel'), + ('users', '0015_owner'), + ] + + operations = [ + migrations.AddIndex( + model_name='prefix', + index=models.Index(fields=['scope_type', 'scope_id'], name='ipam_prefix_scope_t_fe84a6_idx'), + ), + ] diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 529611815..5ee93bf71 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -283,14 +283,15 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk') # (vrf, prefix) may be non-unique verbose_name = _('prefix') verbose_name_plural = _('prefixes') - indexes = [ + indexes = ( + models.Index(fields=('scope_type', 'scope_id')), GistIndex( fields=['prefix'], name='ipam_prefix_gist_idx', opclasses=['inet_ops'], ), - ] - triggers = [ + ) + triggers = ( pgtrigger.Trigger( name='ipam_prefix_delete', operation=pgtrigger.Delete, @@ -309,7 +310,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary when=pgtrigger.After, func=ipam_prefix_update_adjust_prefix_parent, ), - ] + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -631,7 +632,7 @@ class IPRange(ContactsMixin, PrimaryModel): mark_utilized = models.BooleanField( verbose_name=_('mark utilized'), default=False, - help_text=_("Report space as 100% utilized") + help_text=_("Report space as fully utilized") ) clone_fields = ( diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 67c9d9414..030633330 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -10,9 +10,9 @@ from django.utils.translation import gettext_lazy as _ from dcim.models import Interface, Site, SiteGroup from ipam.choices import * from ipam.constants import * -from ipam.querysets import VLANQuerySet, VLANGroupQuerySet +from ipam.querysets import VLANGroupQuerySet, VLANQuerySet from netbox.models import OrganizationalModel, PrimaryModel, NetBoxModel -from utilities.data import check_ranges_overlap, ranges_to_string +from utilities.data import check_ranges_overlap, ranges_to_string, ranges_to_string_list from virtualization.models import VMInterface __all__ = ( @@ -132,7 +132,8 @@ class VLANGroup(OrganizationalModel): def save(self, *args, **kwargs): self._total_vlan_ids = 0 for vid_range in self.vid_ranges: - self._total_vlan_ids += vid_range.upper - vid_range.lower + 1 + # VID range is inclusive on lower-bound, exclusive on upper-bound + self._total_vlan_ids += vid_range.upper - vid_range.lower super().save(*args, **kwargs) @@ -164,8 +165,18 @@ class VLANGroup(OrganizationalModel): """ return VLAN.objects.filter(group=self).order_by('vid') + @property + def vid_ranges_items(self): + """ + Property that converts VID ranges to a list of string representations. + """ + return ranges_to_string_list(self.vid_ranges) + @property def vid_ranges_list(self): + """ + Property that converts VID ranges into a string representation. + """ return ranges_to_string(self.vid_ranges) diff --git a/netbox/ipam/search.py b/netbox/ipam/search.py index 664165d73..54ed7aa25 100644 --- a/netbox/ipam/search.py +++ b/netbox/ipam/search.py @@ -31,6 +31,7 @@ class ASNRangeIndex(SearchIndex): fields = ( ('name', 100), ('description', 500), + ('comments', 5000), ) display_attrs = ('rir', 'tenant', 'description') @@ -93,6 +94,7 @@ class RIRIndex(SearchIndex): ('name', 100), ('slug', 110), ('description', 500), + ('comments', 5000), ) display_attrs = ('description',) @@ -104,6 +106,7 @@ class RoleIndex(SearchIndex): ('name', 100), ('slug', 110), ('description', 500), + ('comments', 5000), ) display_attrs = ('description',) @@ -160,6 +163,7 @@ class VLANGroupIndex(SearchIndex): ('name', 100), ('slug', 110), ('description', 500), + ('comments', 5000), ) display_attrs = ('scope_type', 'description') diff --git a/netbox/ipam/tables/asn.py b/netbox/ipam/tables/asn.py index bbe38dc1a..b3cec6b5f 100644 --- a/netbox/ipam/tables/asn.py +++ b/netbox/ipam/tables/asn.py @@ -2,8 +2,8 @@ import django_tables2 as tables from django.utils.translation import gettext_lazy as _ from ipam.models import * -from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenancyColumnsMixin +from netbox.tables import OrganizationalModelTable, PrimaryModelTable, columns +from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin __all__ = ( 'ASNTable', @@ -11,7 +11,7 @@ __all__ = ( ) -class ASNRangeTable(TenancyColumnsMixin, NetBoxTable): +class ASNRangeTable(TenancyColumnsMixin, OrganizationalModelTable): name = tables.Column( verbose_name=_('Name'), linkify=True @@ -27,16 +27,16 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable): verbose_name=_('ASNs') ) - class Meta(NetBoxTable.Meta): + class Meta(OrganizationalModelTable.Meta): model = ASNRange fields = ( - 'pk', 'name', 'slug', 'rir', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', 'tags', - 'created', 'last_updated', 'actions', + 'pk', 'name', 'slug', 'rir', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', + 'comments', 'tags', 'created', 'last_updated', 'actions', ) default_columns = ('pk', 'name', 'rir', 'start', 'end', 'tenant', 'asn_count', 'description') -class ASNTable(TenancyColumnsMixin, NetBoxTable): +class ASNTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable): asn = tables.Column( verbose_name=_('ASN'), linkify=True @@ -65,18 +65,15 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable): linkify_item=True, verbose_name=_('Sites') ) - comments = columns.MarkdownColumn( - verbose_name=_('Comments'), - ) tags = columns.TagColumn( url_name='ipam:asn_list' ) - class Meta(NetBoxTable.Meta): + class Meta(PrimaryModelTable.Meta): model = ASN fields = ( 'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description', - 'comments', 'sites', 'tags', 'created', 'last_updated', 'actions', + 'contacts', 'comments', 'sites', 'tags', 'created', 'last_updated', 'actions', ) default_columns = ( 'pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant', diff --git a/netbox/ipam/tables/fhrp.py b/netbox/ipam/tables/fhrp.py index 789845f25..2d77c62c7 100644 --- a/netbox/ipam/tables/fhrp.py +++ b/netbox/ipam/tables/fhrp.py @@ -1,8 +1,8 @@ -from django.utils.translation import gettext_lazy as _ import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ from ipam.models import * -from netbox.tables import NetBoxTable, columns +from netbox.tables import NetBoxTable, PrimaryModelTable, columns __all__ = ( 'FHRPGroupTable', @@ -17,7 +17,7 @@ IPADDRESSES = """ """ -class FHRPGroupTable(NetBoxTable): +class FHRPGroupTable(PrimaryModelTable): group_id = tables.Column( verbose_name=_('Group ID'), linkify=True @@ -30,9 +30,6 @@ class FHRPGroupTable(NetBoxTable): member_count = tables.Column( verbose_name=_('Members') ) - comments = columns.MarkdownColumn( - verbose_name=_('Comments'), - ) tags = columns.TagColumn( url_name='ipam:fhrpgroup_list' ) @@ -40,7 +37,7 @@ class FHRPGroupTable(NetBoxTable): def value_ip_addresses(self, value): return ",".join([str(obj.address) for obj in value.all()]) - class Meta(NetBoxTable.Meta): + class Meta(PrimaryModelTable.Meta): model = FHRPGroup fields = ( 'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'comments', 'ip_addresses', diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 3b1f66c37..23e8a53b4 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -1,11 +1,11 @@ -from django.utils.translation import gettext_lazy as _ import django_tables2 as tables from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ from django_tables2.utils import Accessor from ipam.models import * -from netbox.tables import NetBoxTable, columns -from tenancy.tables import TenancyColumnsMixin, TenantColumn +from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns +from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin, TenantColumn from .template_code import * __all__ = ( @@ -27,7 +27,7 @@ AVAILABLE_LABEL = mark_safe('AvailableAvailable.