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"
+ },
+ "debug": {
+ "type": "boolean",
+ "description": "Enable verbose error output when rendering this template. Not recommended for production use."
+ },
+ "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": [
+ "id",
+ "name",
+ "template_code"
+ ]
+ },
+ "BulkConsolePortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkConsolePortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkConsoleServerPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkConsoleServerPortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkContactAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "BulkContactGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkContactRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkContactRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkCustomFieldChoiceSetRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ }
+ },
+ "choice_colors": {
+ "type": "object",
+ "additionalProperties": {
+ "enum": [
+ "blue",
+ "indigo",
+ "purple",
+ "pink",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "teal",
+ "cyan",
+ "gray",
+ "black",
+ "white"
+ ],
+ "type": "string",
+ "description": "* `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",
+ "x-spec-enum-id": "de0a6a124020dfe0"
+ }
+ },
+ "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",
+ "id",
+ "name"
+ ]
+ },
+ "BulkCustomFieldRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "validation_schema": {
+ "nullable": true,
+ "description": "A JSON schema definition for validating the custom field value"
+ },
+ "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": [
+ "id",
+ "name",
+ "object_types",
+ "type"
+ ]
+ },
+ "BulkCustomLinkRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "link_text",
+ "link_url",
+ "name",
+ "object_types"
+ ]
+ },
+ "BulkDataSourceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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 or paths 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": [
+ "id",
+ "name",
+ "source_url",
+ "type"
+ ]
+ },
+ "BulkDeviceBayRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkDeviceBayTemplateRequest": {
+ "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"
+ },
+ "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
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "device_type",
+ "id",
+ "name"
+ ]
+ },
+ "BulkDeviceRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkDeviceTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "BulkDeviceWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "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",
+ "id",
+ "role",
+ "site"
+ ]
+ },
+ "BulkEventRuleRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "name",
+ "object_types"
+ ]
+ },
+ "BulkExportTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "object_types",
+ "template_code"
+ ]
+ },
+ "BulkFHRPGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "interface_id",
+ "interface_type",
+ "priority"
+ ]
+ },
+ "BulkFHRPGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "protocol"
+ ]
+ },
+ "BulkFrontPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkFrontPortTemplateRequest": {
+ "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"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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": [
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkGroupRequest": {
+ "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"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkIKEPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "version"
+ ]
+ },
+ "BulkIKEProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "name"
+ ]
+ },
+ "BulkIPAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id"
+ ]
+ },
+ "BulkIPRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "start_address"
+ ]
+ },
+ "BulkIPSecPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkIPSecProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "ike_policy",
+ "ipsec_policy",
+ "mode",
+ "name"
+ ]
+ },
+ "BulkIPSecProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkImageAttachmentRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "image",
+ "object_id",
+ "object_type"
+ ]
+ },
+ "BulkInterfaceRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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-cu",
+ "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",
+ "40gbase-sr4-bd",
+ "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",
+ "1.6tbase-cr8",
+ "1.6tbase-dr8",
+ "1.6tbase-dr8-2",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "2.5gbase-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",
+ "1.6tbase-x-osfp1600",
+ "1.6tbase-x-osfp1600-rhs",
+ "1.6tbase-x-qsfpdd1600",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "1.6tbase-kr8",
+ "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-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\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* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\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* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\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* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\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* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\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": "b067eb1f050c6ae9"
+ },
+ "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": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkInterfaceTemplateRequest": {
+ "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"
+ },
+ "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-cu",
+ "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",
+ "40gbase-sr4-bd",
+ "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",
+ "1.6tbase-cr8",
+ "1.6tbase-dr8",
+ "1.6tbase-dr8-2",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "2.5gbase-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",
+ "1.6tbase-x-osfp1600",
+ "1.6tbase-x-osfp1600-rhs",
+ "1.6tbase-x-qsfpdd1600",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "1.6tbase-kr8",
+ "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-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\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* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\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* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\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* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\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* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\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": "b067eb1f050c6ae9"
+ },
+ "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": [
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkInventoryItemRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkInventoryItemRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkInventoryItemTemplateRequest": {
+ "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"
+ },
+ "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",
+ "id",
+ "name"
+ ]
+ },
+ "BulkJournalEntryRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id"
+ ]
+ },
+ "BulkL2VPNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkL2VPNTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "l2vpn"
+ ]
+ },
+ "BulkLocationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "site",
+ "slug"
+ ]
+ },
+ "BulkMACAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "mac_address"
+ ]
+ },
+ "BulkManufacturerRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkModuleBayRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkModuleBayTemplateRequest": {
+ "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"
+ },
+ "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
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkModuleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ },
+ "replicate_components": {
+ "type": "boolean",
+ "writeOnly": true,
+ "default": true,
+ "description": "Automatically populate components associated with this module type (default: true)"
+ },
+ "adopt_components": {
+ "type": "boolean",
+ "writeOnly": true,
+ "default": false,
+ "description": "Adopt already existing components"
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "module_bay",
+ "module_type"
+ ]
+ },
+ "BulkModuleTypeProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkModuleTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "manufacturer",
+ "model"
+ ]
+ },
+ "BulkNotificationGroupRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkNotificationRequest": {
+ "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"
+ },
+ "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",
+ "id",
+ "object_id",
+ "object_type",
+ "user"
+ ]
+ },
+ "BulkObjectPermissionRequest": {
+ "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"
+ },
+ "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",
+ "id",
+ "name",
+ "object_types"
+ ]
+ },
+ "BulkOwnerGroupRequest": {
+ "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"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkOwnerRequest": {
+ "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"
+ },
+ "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",
+ "id",
+ "name"
+ ]
+ },
+ "BulkPlatformRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkPowerFeedRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "power_panel"
+ ]
+ },
+ "BulkPowerOutletRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkPowerOutletTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkPowerPanelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "site"
+ ]
+ },
+ "BulkPowerPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name"
+ ]
+ },
+ "BulkPowerPortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkPrefixRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "prefix"
+ ]
+ },
+ "BulkProviderAccountRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "provider"
+ ]
+ },
+ "BulkProviderNetworkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "provider"
+ ]
+ },
+ "BulkProviderRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRIRRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRackGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRackRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackGroupRequest"
+ }
+ ],
+ "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": [
+ "id",
+ "name",
+ "site"
+ ]
+ },
+ "BulkRackReservationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "rack",
+ "units",
+ "user"
+ ]
+ },
+ "BulkRackRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRackTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "manufacturer",
+ "model",
+ "slug"
+ ]
+ },
+ "BulkRearPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "device",
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkRearPortTemplateRequest": {
+ "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"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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": [
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "BulkRegionRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkRouteTargetRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkSavedFilterRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "object_types",
+ "parameters",
+ "slug"
+ ]
+ },
+ "BulkServiceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "parent_object_id",
+ "parent_object_type",
+ "ports"
+ ]
+ },
+ "BulkServiceTemplateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "ports"
+ ]
+ },
+ "BulkSiteGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkSiteRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkSubscriptionRequest": {
+ "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"
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "object_id",
+ "object_type",
+ "user"
+ ]
+ },
+ "BulkTableConfigRequest": {
+ "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"
+ },
+ "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",
+ "id",
+ "name",
+ "object_type",
+ "table"
+ ]
+ },
+ "BulkTagRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkTenantGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkTenantRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkTokenRequest": {
+ "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"
+ },
+ "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 token"
+ },
+ "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": [
+ "id",
+ "user"
+ ]
+ },
+ "BulkTunnelGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkTunnelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "name",
+ "status"
+ ]
+ },
+ "BulkTunnelTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "role",
+ "termination_type",
+ "tunnel"
+ ]
+ },
+ "BulkUserRequest": {
+ "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"
+ },
+ "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": [
+ "id",
+ "password",
+ "username"
+ ]
+ },
+ "BulkVLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkVLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "vid"
+ ]
+ },
+ "BulkVLANTranslationPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkVLANTranslationRuleRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "local_vid",
+ "policy",
+ "remote_vid"
+ ]
+ },
+ "BulkVMInterfaceRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "virtual_machine"
+ ]
+ },
+ "BulkVRFRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkVirtualChassisRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkVirtualCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "provider_network",
+ "type"
+ ]
+ },
+ "BulkVirtualCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "interface",
+ "virtual_circuit"
+ ]
+ },
+ "BulkVirtualCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkVirtualDeviceContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "id",
+ "name",
+ "status"
+ ]
+ },
+ "BulkVirtualDiskRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "size",
+ "virtual_machine"
+ ]
+ },
+ "BulkVirtualMachineTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "default_platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "default_vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "default_memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Default memory (MB)"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkVirtualMachineWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "virtual_machine_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "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
+ },
+ "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
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "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"
+ }
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ },
+ "BulkWebhookRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "payload_url"
+ ]
+ },
+ "BulkWirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "name",
+ "slug"
+ ]
+ },
+ "BulkWirelessLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "ssid"
+ ]
+ },
+ "BulkWirelessLinkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id",
+ "interface_a",
+ "interface_b"
+ ]
+ },
"Cable": {
"type": "object",
"description": "Base serializer class for models inheriting from PrimaryModel.",
@@ -244195,6 +264467,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -244229,8 +264505,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"label": {
"type": "string",
@@ -244261,6 +264537,10 @@
"LC/PC",
"LC/UPC",
"LC/APC",
+ "MU",
+ "MU/PC",
+ "MU/UPC",
+ "MU/APC",
"LSH",
"LSH/PC",
"LSH/UPC",
@@ -244516,6 +264796,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -244550,8 +264834,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -244684,6 +264968,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -244718,8 +265006,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"label": {
"type": "string",
@@ -244750,6 +265038,10 @@
"LC/PC",
"LC/UPC",
"LC/APC",
+ "MU",
+ "MU/PC",
+ "MU/UPC",
+ "MU/APC",
"LSH",
"LSH/PC",
"LSH/UPC",
@@ -244946,6 +265238,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -244980,8 +265276,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -259305,6 +279601,12959 @@
}
}
},
+ "PatchedBulkASNRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkASNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRoleRequest"
+ }
+ ],
+ "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": {}
+ },
+ "sites": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkAggregateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkBookmarkRequest": {
+ "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"
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCableBundleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkCableRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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-1c2p-2c1p",
+ "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-1c2p-2c1p` - 1C2P:2C1P breakout\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": "f566e6df6572f5d0"
+ },
+ "tenant": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefTenantRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "bundle": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefCableBundleRequest"
+ }
+ ],
+ "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": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCircuitGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Base serializer for group assignments under CircuitSerializer.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ ""
+ ],
+ "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"
+ }
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCircuitGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkClusterGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkClusterRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkClusterTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkConfigContextProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkConfigContextRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkConfigTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "debug": {
+ "type": "boolean",
+ "description": "Enable verbose error output when rendering this template. Not recommended for production use."
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkConsolePortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkConsolePortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkConsoleServerPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkConsoleServerPortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkContactAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkContactGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkContactRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkContactRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkCustomFieldChoiceSetRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ }
+ },
+ "choice_colors": {
+ "type": "object",
+ "additionalProperties": {
+ "enum": [
+ "blue",
+ "indigo",
+ "purple",
+ "pink",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "teal",
+ "cyan",
+ "gray",
+ "black",
+ "white"
+ ],
+ "type": "string",
+ "description": "* `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",
+ "x-spec-enum-id": "de0a6a124020dfe0"
+ }
+ },
+ "order_alphabetically": {
+ "type": "boolean",
+ "description": "Choices are automatically ordered alphabetically"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkCustomFieldRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "validation_schema": {
+ "nullable": true,
+ "description": "A JSON schema definition for validating the custom field value"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkCustomLinkRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkDataSourceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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 or paths 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": [
+ "id"
+ ]
+ },
+ "PatchedBulkDeviceBayRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "label": {
+ "type": "string",
+ "description": "Physical label",
+ "maxLength": 64
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_device": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkDeviceBayTemplateRequest": {
+ "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"
+ },
+ "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
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkDeviceRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkDeviceTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkDeviceWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkEventRuleRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkExportTemplateRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkFHRPGroupAssignmentRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkFHRPGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkFrontPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkFrontPortTemplateRequest": {
+ "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"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkGroupRequest": {
+ "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"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 150
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkIKEPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIKEProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIPAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIPRangeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIPSecPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIPSecProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkIPSecProposalRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkImageAttachmentRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkInterfaceRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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-cu",
+ "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",
+ "40gbase-sr4-bd",
+ "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",
+ "1.6tbase-cr8",
+ "1.6tbase-dr8",
+ "1.6tbase-dr8-2",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "2.5gbase-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",
+ "1.6tbase-x-osfp1600",
+ "1.6tbase-x-osfp1600-rhs",
+ "1.6tbase-x-qsfpdd1600",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "1.6tbase-kr8",
+ "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-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\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* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\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* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\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* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\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* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\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": "b067eb1f050c6ae9"
+ },
+ "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": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64",
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkInterfaceTemplateRequest": {
+ "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"
+ },
+ "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-cu",
+ "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",
+ "40gbase-sr4-bd",
+ "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",
+ "1.6tbase-cr8",
+ "1.6tbase-dr8",
+ "1.6tbase-dr8-2",
+ "100base-x-sfp",
+ "1000base-x-gbic",
+ "1000base-x-sfp",
+ "2.5gbase-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",
+ "1.6tbase-x-osfp1600",
+ "1.6tbase-x-osfp1600-rhs",
+ "1.6tbase-x-qsfpdd1600",
+ "1000base-kx",
+ "2.5gbase-kx",
+ "5gbase-kr",
+ "10gbase-kr",
+ "10gbase-kx4",
+ "25gbase-kr",
+ "40gbase-kr4",
+ "50gbase-kr",
+ "100gbase-kp4",
+ "100gbase-kr2",
+ "100gbase-kr4",
+ "1.6tbase-kr8",
+ "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-cu` - 10GBASE-CU (10GE DAC Passive Twinax)\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* `40gbase-sr4-bd` - 40GBASE-SR4 (40GE BiDi)\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* `1.6tbase-cr8` - 1.6TBASE-CR8 (1.6TE)\n* `1.6tbase-dr8` - 1.6TBASE-DR8 (1.6TE)\n* `1.6tbase-dr8-2` - 1.6TBASE-DR8-2 (1.6TE)\n* `100base-x-sfp` - SFP (100ME)\n* `1000base-x-gbic` - GBIC (1GE)\n* `1000base-x-sfp` - SFP (1GE)\n* `2.5gbase-x-sfp` - SFP (2.5GE)\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* `1.6tbase-x-osfp1600` - OSFP1600 (1.6TE)\n* `1.6tbase-x-osfp1600-rhs` - OSFP1600-RHS (1.6TE)\n* `1.6tbase-x-qsfpdd1600` - QSFP-DD1600 (1.6TE)\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* `1.6tbase-kr8` - 1.6TBASE-KR8 (1.6TE)\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": "b067eb1f050c6ae9"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkInventoryItemRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkInventoryItemRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkInventoryItemTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkJournalEntryRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkL2VPNRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkL2VPNTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkLocationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkMACAddressRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkManufacturerRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkModuleBayRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "position": {
+ "type": "string",
+ "description": "Identifier to reference when renaming installed components",
+ "maxLength": 30
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "installed_module": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefModuleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkModuleBayTemplateRequest": {
+ "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"
+ },
+ "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
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkModuleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": {}
+ },
+ "replicate_components": {
+ "type": "boolean",
+ "writeOnly": true,
+ "default": true,
+ "description": "Automatically populate components associated with this module type (default: true)"
+ },
+ "adopt_components": {
+ "type": "boolean",
+ "writeOnly": true,
+ "default": false,
+ "description": "Adopt already existing components"
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkModuleTypeProfileRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkModuleTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkNotificationGroupRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkNotificationRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkObjectPermissionRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkOwnerGroupRequest": {
+ "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"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkOwnerRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPlatformRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerFeedRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerOutletRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerOutletTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerPanelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkPowerPortTemplateRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkPrefixRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkProviderAccountRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkProviderNetworkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkProviderRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRIRRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRackGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRackRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "group": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefRackGroupRequest"
+ }
+ ],
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRackReservationRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRackRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRackTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRearPortRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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"
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkRearPortTemplateRequest": {
+ "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"
+ },
+ "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",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRegionRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRoleRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkRouteTargetRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkSavedFilterRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkServiceRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkServiceTemplateRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkSiteGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkSiteRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkSubscriptionRequest": {
+ "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"
+ },
+ "object_type": {
+ "type": "string"
+ },
+ "object_id": {
+ "type": "integer",
+ "maximum": 9223372036854775807,
+ "minimum": 0,
+ "format": "int64"
+ },
+ "user": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "$ref": "#/components/schemas/BriefUserRequest"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkTableConfigRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTagRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTenantGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTenantRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTokenRequest": {
+ "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"
+ },
+ "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 token"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTunnelGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTunnelRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkTunnelTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkUserRequest": {
+ "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"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVLANTranslationPolicyRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVLANTranslationRuleRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVMInterfaceRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkVRFRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualChassisRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualCircuitRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualCircuitTerminationRequest": {
+ "type": "object",
+ "description": "Adds support for custom fields and tags.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualCircuitTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from OrganizationalModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualDeviceContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualDiskRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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
+ },
+ "owner": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefOwnerRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NestedTagRequest"
+ }
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualMachineTypeRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "pattern": "^[-a-zA-Z0-9_]+$"
+ },
+ "default_platform": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefPlatformRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "default_vcpus": {
+ "type": "number",
+ "format": "double",
+ "maximum": 10000,
+ "minimum": 0.01,
+ "exclusiveMaximum": true,
+ "nullable": true
+ },
+ "default_memory": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true,
+ "title": "Default memory (MB)"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkVirtualMachineWithConfigContextRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "virtual_machine_type": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefVirtualMachineTypeRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "role": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefDeviceRoleRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "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
+ },
+ "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
+ },
+ "disk": {
+ "type": "integer",
+ "maximum": 2147483647,
+ "minimum": 0,
+ "nullable": true
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "serial": {
+ "type": "string",
+ "title": "Serial number",
+ "maxLength": 50
+ },
+ "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"
+ }
+ },
+ "local_context_data": {
+ "nullable": true,
+ "description": "Local config context data takes precedence over source contexts in the final rendered config context"
+ },
+ "config_template": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/BriefConfigTemplateRequest"
+ }
+ ],
+ "nullable": true
+ }
+ ],
+ "nullable": true
+ },
+ "custom_fields": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "PatchedBulkWebhookRequest": {
+ "type": "object",
+ "description": "Adds an `owner` field for models which have a ForeignKey to users.Owner.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkWirelessLANGroupRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from NestedGroupModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkWirelessLANRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
+ "PatchedBulkWirelessLinkRequest": {
+ "type": "object",
+ "description": "Base serializer class for models inheriting from PrimaryModel.",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "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": [
+ "id"
+ ]
+ },
"PatchedCableBundleRequest": {
"type": "object",
"description": "Base serializer class for models inheriting from PrimaryModel.",
@@ -264808,6 +298057,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -264842,8 +298095,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -264972,6 +298225,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -265006,8 +298263,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -269221,6 +302478,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -269255,8 +302516,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -269385,6 +302646,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -269419,8 +302684,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -277132,6 +310397,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -277166,8 +310435,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"label": {
"type": "string",
@@ -277198,6 +310467,10 @@
"LC/PC",
"LC/UPC",
"LC/APC",
+ "MU",
+ "MU/PC",
+ "MU/UPC",
+ "MU/APC",
"LSH",
"LSH/PC",
"LSH/UPC",
@@ -277453,6 +310726,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -277487,8 +310764,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -277621,6 +310898,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -277655,8 +310936,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"label": {
"type": "string",
@@ -277687,6 +310968,10 @@
"LC/PC",
"LC/UPC",
"LC/APC",
+ "MU",
+ "MU/PC",
+ "MU/UPC",
+ "MU/APC",
"LSH",
"LSH/PC",
"LSH/UPC",
@@ -277883,6 +311168,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -277917,8 +311206,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -287946,6 +321235,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -287980,8 +321273,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -288115,6 +321408,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -288149,8 +321446,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -292474,6 +325771,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -292508,8 +325809,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
@@ -292643,6 +325944,10 @@
"lc-pc",
"lc-upc",
"lc-apc",
+ "mu",
+ "mu-pc",
+ "mu-upc",
+ "mu-apc",
"lsh",
"lsh-pc",
"lsh-upc",
@@ -292677,8 +325982,8 @@
"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"
+ "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* `mu` - MU\n* `mu-pc` - MU/PC\n* `mu-upc` - MU/UPC\n* `mu-apc` - MU/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": "b64d6804afec405c"
},
"color": {
"type": "string",
diff --git a/docs/installation/2-redis.md b/docs/installation/2-redis.md
index c29deb5c2..f412cced1 100644
--- a/docs/installation/2-redis.md
+++ b/docs/installation/2-redis.md
@@ -8,7 +8,10 @@
sudo apt install -y redis-server
```
-Before continuing, verify that your installed version of Redis is at least v4.0:
+Before continuing, verify that your installed version of Redis is at least v6.0:
+
+!!! warning "Redis v5.x is deprecated"
+ Support for Redis versions older than 6.0 is deprecated and will be removed in NetBox v4.7.
```no-highlight
redis-server -v
diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md
index 9aefd6c55..acb15725b 100644
--- a/docs/installation/upgrading.md
+++ b/docs/installation/upgrading.md
@@ -40,10 +40,10 @@ NetBox requires the following dependencies:
| NetBox Version | Python min | Python max | PostgreSQL min | Redis min | Documentation |
|:--------------:|:----------:|:----------:|:--------------:|:---------:|:-----------------------------------------------------------------------------------------:|
-| 4.6 | 3.12 | 3.14 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.6.0/docs/installation/index.md) |
-| 4.5 | 3.12 | 3.14 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.5.0/docs/installation/index.md) |
-| 4.4 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.4.0/docs/installation/index.md) |
-| 4.3 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.3.0/docs/installation/index.md) |
+| 4.6 | 3.12 | 3.14 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.6.0/docs/installation/index.md) |
+| 4.5 | 3.12 | 3.14 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.5.0/docs/installation/index.md) |
+| 4.4 | 3.10 | 3.12 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.4.0/docs/installation/index.md) |
+| 4.3 | 3.10 | 3.12 | 14 | 5.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.3.0/docs/installation/index.md) |
| 4.2 | 3.10 | 3.12 | 13 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.2.0/docs/installation/index.md) |
| 4.1 | 3.10 | 3.12 | 12 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.1.0/docs/installation/index.md) |
| 4.0 | 3.10 | 3.12 | 12 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.0.0/docs/installation/index.md) |
diff --git a/docs/models/ipam/vlan.md b/docs/models/ipam/vlan.md
index 58fc9f551..545ea6553 100644
--- a/docs/models/ipam/vlan.md
+++ b/docs/models/ipam/vlan.md
@@ -2,6 +2,12 @@
A Virtual LAN (VLAN) represents an isolated layer two domain, identified by a name and a numeric ID (1-4094) as defined in [IEEE 802.1Q](https://en.wikipedia.org/wiki/IEEE_802.1Q). VLANs are arranged into [VLAN groups](./vlangroup.md) to define scope and to enforce uniqueness.
+## Bulk Creation
+
+Multiple VLANs can be created at once by selecting the "Bulk Create" tab on the VLAN creation form. Enter the desired VLAN IDs and/or ID ranges as a comma-separated list (e.g. `100,200-210,4000-4010`). The string `{vid}` may be embedded in the name field as a placeholder for each VLAN's ID; for example, `VLAN-{vid}` yields `VLAN-100`, `VLAN-200`, and so on. All other attributes (status, role, tenant, etc.) are applied to every new VLAN.
+
+The operation is atomic: if any VLAN fails validation (for example, a VLAN ID falling outside the assigned group's permitted ranges), no VLANs are created.
+
## Fields
### ID
diff --git a/docs/plugins/development/ui-components.md b/docs/plugins/development/ui-components.md
index 45766b201..f148443e3 100644
--- a/docs/plugins/development/ui-components.md
+++ b/docs/plugins/development/ui-components.md
@@ -135,29 +135,32 @@ panels.ObjectsTablePanel(
The following classes are available to represent object attributes within an ObjectAttributesPanel. Additionally, plugins can subclass `netbox.ui.attrs.ObjectAttribute` to create custom classes.
-| Class | Description |
-|------------------------------------------|--------------------------------------------------|
-| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. |
-| `netbox.ui.attrs.BooleanAttr` | A boolean value |
-| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices |
-| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB |
-| `netbox.ui.attrs.DateTimeAttr` | A date or datetime value |
-| `netbox.ui.attrs.GenericForeignKeyAttr` | A related object via a generic foreign key |
-| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) |
-| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) |
-| `netbox.ui.attrs.NestedObjectAttr` | A related nested object (includes ancestors) |
-| `netbox.ui.attrs.NumericAttr` | An integer or float value |
-| `netbox.ui.attrs.RelatedObjectAttr` | A related object |
-| `netbox.ui.attrs.RelatedObjectListAttr` | A list of related objects |
-| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template |
-| `netbox.ui.attrs.TextAttr` | A string (text) value |
-| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset |
-| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph |
+| Class | Description |
+|-----------------------------------------|-----------------------------------------------------|
+| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. |
+| `netbox.ui.attrs.ArrayAttr` | An array of values, shown as a comma-separated list |
+| `netbox.ui.attrs.BooleanAttr` | A boolean value |
+| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices |
+| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB |
+| `netbox.ui.attrs.DateTimeAttr` | A date or datetime value |
+| `netbox.ui.attrs.GenericForeignKeyAttr` | A related object via a generic foreign key |
+| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) |
+| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) |
+| `netbox.ui.attrs.NestedObjectAttr` | A related nested object (includes ancestors) |
+| `netbox.ui.attrs.NumericAttr` | An integer or float value |
+| `netbox.ui.attrs.RelatedObjectAttr` | A related object |
+| `netbox.ui.attrs.RelatedObjectListAttr` | A list of related objects |
+| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template |
+| `netbox.ui.attrs.TextAttr` | A string (text) value |
+| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset |
+| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph |
::: netbox.ui.attrs.ObjectAttribute
::: netbox.ui.attrs.AddressAttr
+::: netbox.ui.attrs.ArrayAttr
+
::: netbox.ui.attrs.BooleanAttr
::: netbox.ui.attrs.ChoiceAttr
diff --git a/docs/release-notes/version-4.6.md b/docs/release-notes/version-4.6.md
index f4ea93052..0e152e5cb 100644
--- a/docs/release-notes/version-4.6.md
+++ b/docs/release-notes/version-4.6.md
@@ -1,5 +1,51 @@
# NetBox v4.6
+## v4.6.3 (2026-06-16)
+
+### Enhancements
+
+* [#17598](https://github.com/netbox-community/netbox/issues/17598) - Add bulk creation support for VLANs
+* [#21666](https://github.com/netbox-community/netbox/issues/21666) - Add MU connector type for fiber ports and cables
+* [#22361](https://github.com/netbox-community/netbox/issues/22361) - Introduce an `ArrayAttr` UI panel attribute for rendering array field values
+* [#22457](https://github.com/netbox-community/netbox/issues/22457) - Use `hmac.compare_digest()` for constant-time authentication of API tokens
+
+### Performance Improvements
+
+* [#21870](https://github.com/netbox-community/netbox/issues/21870) - Optimize prefix availability calculations
+* [#22375](https://github.com/netbox-community/netbox/issues/22375) - Improve efficiency of filtering VLANs by interface
+
+### Bug Fixes
+
+* [#21338](https://github.com/netbox-community/netbox/issues/21338) - Include connected endpoint data in interface webhooks generated during cable creation
+* [#21895](https://github.com/netbox-community/netbox/issues/21895) - Restore pagination controls for job log entries (previously limited to 50 rows)
+* [#22210](https://github.com/netbox-community/netbox/issues/22210) - Respect saved filters when rendering IPAM child availability views in additional tabs
+* [#22237](https://github.com/netbox-community/netbox/issues/22237) - Fix server error when opening the standalone "Add Table Configuration" page
+* [#22245](https://github.com/netbox-community/netbox/issues/22245) - Include the `id` field in the OpenAPI request schemas for bulk PATCH/PUT endpoints
+* [#22251](https://github.com/netbox-community/netbox/issues/22251) - Re-parent child module bays when a multi-bay module is moved to a new bay
+* [#22273](https://github.com/netbox-community/netbox/issues/22273) - Fix migration failure when a service has several thousand ports defined
+* [#22303](https://github.com/netbox-community/netbox/issues/22303) - Add the missing `fields` parameter to the OpenAPI schema
+* [#22324](https://github.com/netbox-community/netbox/issues/22324) - Fix GraphQL filtering of custom field choice set extra choices
+* [#22340](https://github.com/netbox-community/netbox/issues/22340) - Display a token's allowed IPs as comma-separated strings rather than `IPNetwork` objects
+* [#22346](https://github.com/netbox-community/netbox/issues/22346) - Render SSO/SAML authentication failures as a login page message instead of an HTTP 500 error
+* [#22357](https://github.com/netbox-community/netbox/issues/22357) - Remove the unused `local_context_data` field from `dcim.Module` (which no longer inherits from `ConfigContextModel`)
+* [#22376](https://github.com/netbox-community/netbox/issues/22376) - Fix `AssertionError` in event rule script jobs when a device type has an image attached
+* [#22388](https://github.com/netbox-community/netbox/issues/22388) - Pin redis-py to <8.0 to avoid a startup failure on older Redis releases
+* [#22397](https://github.com/netbox-community/netbox/issues/22397) - Fix `AttributeError` exception when an unauthenticated user attempts to export devices
+* [#22399](https://github.com/netbox-community/netbox/issues/22399) - Enforce object permissions on the related object when serving static media
+* [#22427](https://github.com/netbox-community/netbox/issues/22427) - Validate `JSONFilter.path` to prevent ORM operator injection over JSONField contents in the GraphQL API
+* [#22429](https://github.com/netbox-community/netbox/issues/22429) - Enforce `ObjectPermission` constraints on `grant_token` in the REST API
+* [#22431](https://github.com/netbox-community/netbox/issues/22431) - Use a cryptographically secure random number generator when generating API tokens
+* [#22444](https://github.com/netbox-community/netbox/issues/22444) - Fix `KeyError` exception on the power feed detail view when the locale is not English
+* [#22448](https://github.com/netbox-community/netbox/issues/22448) - Ensure all object representations are escaped under `handle_protectederror()`
+* [#22454](https://github.com/netbox-community/netbox/issues/22454) - Fix serialization of decimal custom field values to avoid spurious changelog entries
+* [#22466](https://github.com/netbox-community/netbox/issues/22466) - Fix test failure against SSL-enabled PosgtreSQL
+
+### Deprecations
+
+* [#22392](https://github.com/netbox-community/netbox/issues/22392) - Deprecate support for Redis 5.x (to be removed in v4.7)
+
+---
+
## v4.6.2 (2026-06-02)
### Enhancements
diff --git a/netbox/circuits/graphql/filters.py b/netbox/circuits/graphql/filters.py
index 94ec9fec2..238b488ac 100644
--- a/netbox/circuits/graphql/filters.py
+++ b/netbox/circuits/graphql/filters.py
@@ -1,4 +1,3 @@
-from datetime import date
from typing import TYPE_CHECKING, Annotated
import strawberry
@@ -62,9 +61,9 @@ class CircuitTerminationFilter(
upstream_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- xconnect_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
- pp_info: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ xconnect_id: StrFilterLookup | None = strawberry_django.filter_field()
+ pp_info: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
# Cached relations
_provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
@@ -92,7 +91,7 @@ class CircuitFilter(
TenancyFilterMixin,
PrimaryModelFilter
):
- cid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ cid: StrFilterLookup | None = strawberry_django.filter_field()
provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -108,8 +107,8 @@ class CircuitFilter(
status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- install_date: DateFilterLookup[date] | None = strawberry_django.filter_field()
- termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field()
+ install_date: DateFilterLookup | None = strawberry_django.filter_field()
+ termination_date: DateFilterLookup | None = strawberry_django.filter_field()
commit_rate: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -145,8 +144,8 @@ class CircuitGroupAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, Cha
@strawberry_django.filter_type(models.Provider, lookups=True)
class ProviderFilter(ContactFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
circuits: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
strawberry_django.filter_field()
@@ -159,18 +158,18 @@ class ProviderAccountFilter(ContactFilterMixin, PrimaryModelFilter):
strawberry_django.filter_field()
)
provider_id: ID | None = strawberry_django.filter_field()
- account: StrFilterLookup[str] | None = strawberry_django.filter_field()
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ account: StrFilterLookup | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ProviderNetwork, lookups=True)
class ProviderNetworkFilter(PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
strawberry_django.filter_field()
)
provider_id: ID | None = strawberry_django.filter_field()
- service_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ service_id: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.VirtualCircuitType, lookups=True)
@@ -180,7 +179,7 @@ class VirtualCircuitTypeFilter(CircuitTypeFilterMixin, OrganizationalModelFilter
@strawberry_django.filter_type(models.VirtualCircuit, lookups=True)
class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilter):
- cid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ cid: StrFilterLookup | None = strawberry_django.filter_field()
provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -218,4 +217,4 @@ class VirtualCircuitTerminationFilter(CustomFieldsFilterMixin, TagsFilterMixin,
strawberry_django.filter_field()
)
interface_id: ID | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py
index 5ceb3c61d..4c63296d3 100644
--- a/netbox/core/api/schema.py
+++ b/netbox/core/api/schema.py
@@ -14,7 +14,7 @@ from drf_spectacular.plumbing import (
get_doc,
)
from drf_spectacular.types import OpenApiTypes
-from drf_spectacular.utils import Direction
+from drf_spectacular.utils import Direction, OpenApiParameter
from netbox.api.fields import ChoiceField
from netbox.api.serializers import WritableNestedSerializer
@@ -138,14 +138,30 @@ class NetBoxAutoSchema(AutoSchema):
return super().get_operation_id()
def get_request_serializer(self) -> typing.Any:
- # bulk operations should specify a list
serializer = super().get_request_serializer()
+ # Bulk update/partial-update has a special request shape: a list of
+ # writable objects plus a required `id` field. The normal writable
+ # serializer omits `id` because it is read-only, so don't use the generic
+ # bulk handling for these actions.
+ action = getattr(self.view, 'action', None)
+ if action in ('bulk_update', 'bulk_partial_update'):
+ get_bulk_update_request_serializer = getattr(
+ self.view,
+ 'get_bulk_update_request_serializer',
+ None,
+ )
+ if get_bulk_update_request_serializer is not None:
+ return get_bulk_update_request_serializer(
+ partial=(action == 'bulk_partial_update' or self.method == 'PATCH')
+ )
+
+ # Bulk creates/deletes should specify a list.
if self.is_bulk_action:
return type(serializer)(many=True)
- # handle mapping for Writable serializers - adapted from dansheps original code
- # for drf-yasg
+ # handle mapping for Writable serializers - adapted from dansheps original
+ # code for drf-yasg.
if serializer is not None and self.method in WRITABLE_ACTIONS:
writable_class = self.get_writable_class(serializer)
if writable_class is not None:
@@ -258,6 +274,37 @@ class NetBoxAutoSchema(AutoSchema):
writable_class = self.writable_serializers[type(serializer)]
return writable_class
+ def get_override_parameters(self):
+ params = super().get_override_parameters()
+ # Expose the ?fields, ?omit, and ?brief query parameters supported by NetBoxModelViewSet
+ # for all non-bulk GET operations (both list and detail).
+ if not self.is_bulk_action and self.method == 'GET':
+ params = list(params) + [
+ OpenApiParameter(
+ name='fields',
+ location=OpenApiParameter.QUERY,
+ required=False,
+ type=OpenApiTypes.STR,
+ description='Comma-separated list of fields to include in the response. Example: `fields=id,name`.',
+ ),
+ OpenApiParameter(
+ name='omit',
+ location=OpenApiParameter.QUERY,
+ required=False,
+ type=OpenApiTypes.STR,
+ description='Comma-separated list of fields to exclude from the response. '
+ 'Example: `omit=description,tags`.',
+ ),
+ OpenApiParameter(
+ name='brief',
+ location=OpenApiParameter.QUERY,
+ required=False,
+ type=OpenApiTypes.BOOL,
+ description='Return only brief fields for each object.',
+ ),
+ ]
+ return params
+
def get_filter_backends(self):
# bulk operations don't have filter params
if self.is_bulk_action:
diff --git a/netbox/core/apps.py b/netbox/core/apps.py
index 86ed763b5..27f457607 100644
--- a/netbox/core/apps.py
+++ b/netbox/core/apps.py
@@ -22,7 +22,7 @@ class CoreConfig(AppConfig):
def ready(self):
from core.api import schema # noqa: F401
- from core.checks import check_duplicate_indexes, check_postgresql_version # noqa: F401
+ from core.checks import check_duplicate_indexes, check_postgresql_version, check_redis_version # noqa: F401
from netbox import context_managers # noqa: F401
from netbox.models.features import register_models
diff --git a/netbox/core/checks.py b/netbox/core/checks.py
index 90300bdd3..d452641c7 100644
--- a/netbox/core/checks.py
+++ b/netbox/core/checks.py
@@ -1,4 +1,5 @@
from django.apps import apps
+from django.core.cache import cache
from django.core.checks import Error, Tags, Warning, register
from django.db import connection
from django.db.models import Index, UniqueConstraint
@@ -6,6 +7,7 @@ from django.db.models import Index, UniqueConstraint
__all__ = (
'check_duplicate_indexes',
'check_postgresql_version',
+ 'check_redis_version',
)
@@ -67,3 +69,27 @@ def check_postgresql_version(app_configs, **kwargs):
except Exception:
pass
return warnings
+
+
+@register(Tags.caches)
+def check_redis_version(app_configs, **kwargs):
+ """
+ Warn if the Redis version is less than 6.0, as support for Redis older than 6.0
+ will be removed in NetBox v4.7.
+ """
+ warnings = []
+ try:
+ client = cache.client.get_client()
+ redis_version = tuple(int(x) for x in client.info()['redis_version'].split('.'))
+ if redis_version < (6, 0):
+ warnings.append(
+ Warning(
+ f'Support for Redis {".".join(str(x) for x in redis_version)} is deprecated and will be '
+ f'removed in NetBox v4.7.',
+ hint='Please upgrade to Redis 6.0 or later.',
+ id='netbox.W002',
+ )
+ )
+ except Exception:
+ pass
+ return warnings
diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py
index 5629e7784..0e097fbcc 100644
--- a/netbox/core/forms/model_forms.py
+++ b/netbox/core/forms/model_forms.py
@@ -115,15 +115,6 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
return self.cleaned_data
- def save(self, *args, **kwargs):
- # If a file was uploaded, save it to disk
- if self.cleaned_data['upload_file']:
- self.instance.file_path = self.cleaned_data['upload_file'].name
- with open(self.instance.full_path, 'wb+') as new_file:
- new_file.write(self.cleaned_data['upload_file'].read())
-
- return super().save(*args, **kwargs)
-
class ConfigFormMetaclass(forms.models.ModelFormMetaclass):
diff --git a/netbox/core/graphql/filter_mixins.py b/netbox/core/graphql/filter_mixins.py
index c094ecb24..96c4b7c29 100644
--- a/netbox/core/graphql/filter_mixins.py
+++ b/netbox/core/graphql/filter_mixins.py
@@ -1,5 +1,4 @@
from dataclasses import dataclass
-from datetime import datetime
from typing import TYPE_CHECKING, Annotated
import strawberry
@@ -20,5 +19,5 @@ class ChangeLoggingMixin:
changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
- last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+ last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field()
diff --git a/netbox/core/graphql/filters.py b/netbox/core/graphql/filters.py
index 3424c5737..05f4803e4 100644
--- a/netbox/core/graphql/filters.py
+++ b/netbox/core/graphql/filters.py
@@ -1,4 +1,3 @@
-from datetime import datetime
from typing import TYPE_CHECKING, Annotated
import strawberry
@@ -26,33 +25,33 @@ __all__ = (
@strawberry_django.filter_type(models.DataFile, lookups=True)
class DataFileFilter(BaseModelFilter):
- created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
- last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+ last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field()
source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
source_id: ID | None = strawberry_django.filter_field()
- path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ path: StrFilterLookup | None = strawberry_django.filter_field()
size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- hash: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ hash: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.DataSource, lookups=True)
class DataSourceFilter(PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- type: StrFilterLookup[str] | None = strawberry_django.filter_field()
- source_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ type: StrFilterLookup | None = strawberry_django.filter_field()
+ source_url: StrFilterLookup | None = strawberry_django.filter_field()
status: (
BaseFilterLookup[Annotated['DataSourceStatusEnum', strawberry.lazy('core.graphql.enums')]] | None
) = strawberry_django.filter_field()
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
- ignore_rules: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ ignore_rules: StrFilterLookup | None = strawberry_django.filter_field()
parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ last_synced: DatetimeFilterLookup | None = strawberry_django.filter_field()
datafiles: Annotated['DataFileFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -60,10 +59,10 @@ class DataSourceFilter(PrimaryModelFilter):
@strawberry_django.filter_type(models.ObjectChange, lookups=True)
class ObjectChangeFilter(BaseModelFilter):
- time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ time: DatetimeFilterLookup | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
- user_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- request_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ user_name: StrFilterLookup | None = strawberry_django.filter_field()
+ request_id: StrFilterLookup | None = strawberry_django.filter_field()
action: (
BaseFilterLookup[Annotated['ObjectChangeActionEnum', strawberry.lazy('core.graphql.enums')]] | None
) = strawberry_django.filter_field()
@@ -76,7 +75,7 @@ class ObjectChangeFilter(BaseModelFilter):
strawberry_django.filter_field()
)
related_object_id: ID | None = strawberry_django.filter_field()
- object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ object_repr: StrFilterLookup | None = strawberry_django.filter_field()
prechange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -87,5 +86,5 @@ class ObjectChangeFilter(BaseModelFilter):
@strawberry_django.filter_type(DjangoContentType, lookups=True)
class ContentTypeFilter(BaseModelFilter):
- app_label: StrFilterLookup[str] | None = strawberry_django.filter_field()
- model: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ app_label: StrFilterLookup | None = strawberry_django.filter_field()
+ model: StrFilterLookup | None = strawberry_django.filter_field()
diff --git a/netbox/core/tests/test_api.py b/netbox/core/tests/test_api.py
index 2a159f6bd..332169f2c 100644
--- a/netbox/core/tests/test_api.py
+++ b/netbox/core/tests/test_api.py
@@ -12,7 +12,7 @@ from rq.registry import FailedJobRegistry, StartedJobRegistry
from users.constants import TOKEN_PREFIX
from users.models import Token
-from utilities.testing import APITestCase, APIViewTestCases, TestCase
+from utilities.testing import APITestCase, APIViewTestCases, GraphQLQueryTest, TestCase
from utilities.testing.mixins import RQQueueTestMixin
from utilities.testing.utils import disable_logging
@@ -39,12 +39,49 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase):
@classmethod
def setUpTestData(cls):
data_sources = (
- DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'),
+ DataSource(
+ name='Data Source 1', type='local', source_url='file:///var/tmp/source1/',
+ parameters={
+ 'sync_date': '2024-01-01',
+ 'sync_datetime': '2024-01-01T12:30:00+00:00',
+ 'sync_time': '12:30:00',
+ },
+ ),
DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'),
DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'),
)
DataSource.objects.bulk_create(data_sources)
+ cls.graphql_query_tests = (
+ GraphQLQueryTest(
+ name='parameters_json_date_lookup',
+ query=(
+ '{ data_source_list(filters: {parameters: '
+ '{path: "sync_date", lookup: {date_lookup: {exact: "2024-01-01"}}}}) '
+ '{ id } }'
+ ),
+ assert_result=cls.assert_only_source_1,
+ ),
+ GraphQLQueryTest(
+ name='parameters_json_datetime_lookup',
+ query=(
+ '{ data_source_list(filters: {parameters: '
+ '{path: "sync_datetime", lookup: {datetime_lookup: {exact: "2024-01-01T12:30:00+00:00"}}}}) '
+ '{ id } }'
+ ),
+ assert_result=cls.assert_only_source_1,
+ ),
+ GraphQLQueryTest(
+ name='parameters_json_time_lookup',
+ query=(
+ '{ data_source_list(filters: {parameters: '
+ '{path: "sync_time", lookup: {time_lookup: {exact: "12:30:00"}}}}) '
+ '{ id } }'
+ ),
+ assert_result=cls.assert_only_source_1,
+ ),
+ )
+
cls.create_data = [
{
'name': 'Data Source 4',
@@ -63,6 +100,11 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase):
},
]
+ def assert_only_source_1(self, data):
+ """The JSON lookup returns exactly the source carrying the matching value."""
+ ids = sorted(result['id'] for result in data['data_source_list'])
+ self.assertEqual(ids, [str(DataSource.objects.get(name='Data Source 1').pk)])
+
class DataFileTestCase(
APIViewTestCases.GetObjectViewTestCase,
@@ -279,9 +321,8 @@ class BackgroundTaskTestCase(RQQueueTestMixin, TestCase):
# Enqueue & run a job that will fail
queue = get_queue('default')
job = queue.enqueue(self.dummy_job_failing)
- worker = get_worker('default')
with disable_logging():
- worker.work(burst=True)
+ self.run_rq_jobs('default')
self.assertTrue(job.is_failed)
url = reverse('core-api:rqtask-requeue', args=[job.id])
diff --git a/netbox/core/tests/test_views.py b/netbox/core/tests/test_views.py
index b763eaed2..dfa2c9ed8 100644
--- a/netbox/core/tests/test_views.py
+++ b/netbox/core/tests/test_views.py
@@ -1,7 +1,7 @@
import json
import urllib.parse
import uuid
-from datetime import datetime
+from datetime import UTC, datetime
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
@@ -152,6 +152,77 @@ class JobTestCase(
)
+class JobLogViewTestCase(TestCase):
+ user_permissions = (
+ 'core.view_job',
+ )
+
+ @classmethod
+ def setUpTestData(cls):
+ cls.job = Job.objects.create(
+ name='Test Job',
+ job_id=uuid.uuid4(),
+ )
+ cls.job.log_entries = [
+ {
+ 'level': 'info',
+ 'message': f'log line {i}',
+ 'timestamp': datetime(2026, 1, 1, tzinfo=UTC),
+ }
+ for i in range(120)
+ ]
+ cls.job.save()
+
+ def setUp(self):
+ super().setUp()
+ # UserConfig.set() mutates self.data in place, which can mutate DEFAULT_USER_PREFERENCES
+ # (the signal in users/signals.py initializes data with a shared reference). Assign a
+ # fresh literal instead. Pin per_page so page-boundary assertions don't depend on PAGINATE_COUNT.
+ self.user.config.data = {'pagination': {'per_page': 50}}
+ self.user.config.save()
+
+ def test_log_page_renders_table_inline(self):
+ """The full page renders the first log page inside an HTMX container."""
+ url = reverse('core:job_log', kwargs={'pk': self.job.pk})
+ response = self.client.get(url)
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, 'htmx-container')
+ self.assertContains(response, 'log line 0')
+ self.assertContains(response, 'Showing 1-50 of 120')
+
+ def test_log_page_table_is_embedded(self):
+ """The embedded table never pushes page/per_page into the browser URL."""
+ url = reverse('core:job_log', kwargs={'pk': self.job.pk})
+ response = self.client.get(url)
+ self.assertHttpStatus(response, 200)
+ self.assertNotContains(response, 'hx-push-url="true"')
+
+ def test_log_table_htmx_renders_partial(self):
+ """An HTMX request returns the paginated table partial."""
+ url = reverse('core:job_log', kwargs={'pk': self.job.pk})
+ response = self.client.get(url, headers={'hx-request': 'true'})
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, 'log line 0')
+ self.assertContains(response, 'Showing 1-50 of 120')
+ self.assertContains(response, 'Per Page')
+
+ def test_log_table_htmx_page_navigation(self):
+ """`?page=2` advances the embedded table to the second page."""
+ url = reverse('core:job_log', kwargs={'pk': self.job.pk})
+ response = self.client.get(f'{url}?page=2', headers={'hx-request': 'true'})
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, 'log line 50')
+ self.assertNotContains(response, 'log line 49')
+
+ def test_log_table_htmx_per_page(self):
+ """`?per_page=100` widens the embedded table page size."""
+ url = reverse('core:job_log', kwargs={'pk': self.job.pk})
+ response = self.client.get(f'{url}?per_page=100', headers={'hx-request': 'true'})
+ self.assertHttpStatus(response, 200)
+ self.assertContains(response, 'log line 99')
+ self.assertNotContains(response, 'log line 100')
+
+
# TODO: Convert to StandardTestCases.Views
class ObjectChangeTestCase(TestCase):
user_permissions = (
@@ -315,9 +386,8 @@ class BackgroundTaskTestCase(RQQueueTestMixin, TestCase):
# Enqueue & run a job that will fail
job = queue.enqueue(self.dummy_job_failing)
- worker = get_worker('default')
with disable_logging():
- worker.work(burst=True)
+ self.run_rq_jobs('default')
self.assertTrue(job.is_failed)
# Re-enqueue the failed job and check that its status has been reset
diff --git a/netbox/core/views.py b/netbox/core/views.py
index 760346b3d..78059ce2d 100644
--- a/netbox/core/views.py
+++ b/netbox/core/views.py
@@ -39,7 +39,6 @@ from netbox.plugins.utils import get_installed_plugins
from netbox.ui import layout
from netbox.ui.panels import (
CommentsPanel,
- ContextTablePanel,
JSONPanel,
ObjectsTablePanel,
PluginContentPanel,
@@ -269,7 +268,7 @@ class JobLogView(generic.ObjectView):
layout = layout.Layout(
layout.Row(
layout.Column(
- ContextTablePanel('table', title=_('Log Entries')),
+ TemplatePanel('core/job/log_entries.html', title=_('Log Entries')),
PluginContentPanel('left_page'),
),
),
@@ -280,13 +279,27 @@ class JobLogView(generic.ObjectView):
),
)
- def get_extra_context(self, request, instance):
+ def get_table(self, request, instance):
table = JobLogEntryTable(instance.log_entries)
+ table.embedded = True
+ table.htmx_url = reverse('core:job_log', kwargs={'pk': instance.pk})
table.configure(request)
+ return table
+
+ def get_extra_context(self, request, instance):
return {
- 'table': table,
+ 'table': self.get_table(request, instance),
}
+ def get(self, request, **kwargs):
+ if htmx_partial(request):
+ instance = self.get_object(**kwargs)
+ return render(request, 'htmx/table.html', {
+ 'object': instance,
+ 'table': self.get_table(request, instance),
+ })
+ return super().get(request, **kwargs)
+
@register_model_view(Job, 'delete')
class JobDeleteView(generic.ObjectDeleteView):
diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py
index 514d4aedf..58011b5f7 100644
--- a/netbox/dcim/choices.py
+++ b/netbox/dcim/choices.py
@@ -1637,6 +1637,10 @@ class PortTypeChoices(ChoiceSet):
TYPE_LC_PC = 'lc-pc'
TYPE_LC_UPC = 'lc-upc'
TYPE_LC_APC = 'lc-apc'
+ TYPE_MU = 'mu'
+ TYPE_MU_PC = 'mu-pc'
+ TYPE_MU_UPC = 'mu-upc'
+ TYPE_MU_APC = 'mu-apc'
TYPE_MTRJ = 'mtrj'
TYPE_MPO = 'mpo'
TYPE_LSH = 'lsh'
@@ -1700,6 +1704,10 @@ class PortTypeChoices(ChoiceSet):
(TYPE_LC_PC, 'LC/PC'),
(TYPE_LC_UPC, 'LC/UPC'),
(TYPE_LC_APC, 'LC/APC'),
+ (TYPE_MU, 'MU'),
+ (TYPE_MU_PC, 'MU/PC'),
+ (TYPE_MU_UPC, 'MU/UPC'),
+ (TYPE_MU_APC, 'MU/APC'),
(TYPE_LSH, 'LSH'),
(TYPE_LSH_PC, 'LSH/PC'),
(TYPE_LSH_UPC, 'LSH/UPC'),
diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py
index 6f3197919..f756672fe 100644
--- a/netbox/dcim/forms/bulk_import.py
+++ b/netbox/dcim/forms/bulk_import.py
@@ -1583,7 +1583,8 @@ class CableImportForm(PrimaryModelImportForm):
:param side: 'a' or 'b'
"""
- assert side in 'ab', f"Invalid side designation: {side}"
+ if side not in ('a', 'b'):
+ raise ValueError(_("Invalid side designation: {side}").format(side=side))
device = self.cleaned_data.get(f'side_{side}_device')
power_panel = self.cleaned_data.get(f'side_{side}_power_panel')
diff --git a/netbox/dcim/graphql/filter_mixins.py b/netbox/dcim/graphql/filter_mixins.py
index 07fa34616..fb5a21a0b 100644
--- a/netbox/dcim/graphql/filter_mixins.py
+++ b/netbox/dcim/graphql/filter_mixins.py
@@ -66,9 +66,9 @@ class ComponentModelFilterMixin:
)
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
device_id: ID | None = strawberry_django.filter_field()
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- label: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ label: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
@dataclass
@@ -96,9 +96,9 @@ class ComponentTemplateFilterMixin:
strawberry_django.filter_field()
)
device_type_id: ID | None = strawberry_django.filter_field()
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- label: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ label: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
@dataclass
diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py
index 02fb3ef64..09ba07acd 100644
--- a/netbox/dcim/graphql/filters.py
+++ b/netbox/dcim/graphql/filters.py
@@ -116,7 +116,7 @@ __all__ = (
@strawberry_django.filter_type(models.CableBundle, lookups=True)
class CableBundleFilter(PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Cable, lookups=True)
@@ -127,7 +127,7 @@ class CableFilter(TenancyFilterMixin, PrimaryModelFilter):
status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- label: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ label: StrFilterLookup | None = strawberry_django.filter_field()
color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -223,9 +223,9 @@ class DeviceFilter(
platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
- asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ serial: StrFilterLookup | None = strawberry_django.filter_field()
+ asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
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 = (
@@ -353,7 +353,7 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedMode
strawberry_django.filter_field()
)
manufacturer_id: ID | None = strawberry_django.filter_field()
- part_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ part_id: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.DeviceRole, lookups=True)
@@ -370,13 +370,13 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod
strawberry_django.filter_field()
)
manufacturer_id: ID | None = strawberry_django.filter_field()
- model: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ model: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
default_platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
default_platform_id: ID | None = strawberry_django.filter_field()
- part_number: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ part_number: StrFilterLookup | None = strawberry_django.filter_field()
instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -493,7 +493,7 @@ class PortTemplateMappingFilter(BaseModelFilter):
@strawberry_django.filter_type(models.MACAddress, lookups=True)
class MACAddressFilter(PrimaryModelFilter):
- mac_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ mac_address: StrFilterLookup | None = strawberry_django.filter_field()
assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -539,7 +539,7 @@ class InterfaceFilter(
duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- wwn: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ wwn: StrFilterLookup | None = strawberry_django.filter_field()
parent: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -659,9 +659,9 @@ class InventoryItemFilter(ComponentModelFilterMixin, NetBoxModelFilter):
strawberry_django.filter_field()
)
manufacturer_id: ID | None = strawberry_django.filter_field()
- part_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
- serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
- asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ part_id: StrFilterLookup | None = strawberry_django.filter_field()
+ serial: StrFilterLookup | None = strawberry_django.filter_field()
+ asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
discovered: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -679,7 +679,7 @@ class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilt
status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- facility: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ facility: StrFilterLookup | None = strawberry_django.filter_field()
prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -708,8 +708,8 @@ class ModuleFilter(ConfigContextFilterMixin, PrimaryModelFilter):
status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
- asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ serial: StrFilterLookup | None = strawberry_django.filter_field()
+ asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
consoleports: Annotated['ConsolePortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field(name='console_ports')
)
@@ -748,19 +748,19 @@ class ModuleBayFilter(ModularComponentFilterMixin, NetBoxModelFilter):
strawberry_django.filter_field()
)
parent_id: ID | None = strawberry_django.filter_field()
- position: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ position: StrFilterLookup | None = strawberry_django.filter_field()
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True)
class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
- position: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ position: StrFilterLookup | None = strawberry_django.filter_field()
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True)
class ModuleTypeProfileFilter(PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.ModuleType, lookups=True)
@@ -773,8 +773,8 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod
strawberry_django.filter_field()
)
profile_id: ID | None = strawberry_django.filter_field()
- model: StrFilterLookup[str] | None = strawberry_django.filter_field()
- part_number: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ model: StrFilterLookup | None = strawberry_django.filter_field()
+ part_number: StrFilterLookup | None = strawberry_django.filter_field()
instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -831,7 +831,7 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
power_panel_id: ID | None = strawberry_django.filter_field()
rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
rack_id: ID | None = strawberry_django.filter_field()
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -902,7 +902,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo
location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.PowerPort, lookups=True)
@@ -940,8 +940,8 @@ class RackTypeFilter(ImageAttachmentFilterMixin, RackFilterMixin, WeightFilterMi
strawberry_django.filter_field()
)
manufacturer_id: ID | None = strawberry_django.filter_field()
- model: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ model: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | 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()
@@ -962,8 +962,8 @@ class RackFilter(
strawberry_django.filter_field()
)
rack_type_id: ID | None = strawberry_django.filter_field()
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- facility_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ facility_id: StrFilterLookup | None = strawberry_django.filter_field()
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 = (
@@ -981,8 +981,8 @@ class RackFilter(
)
role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
role_id: ID | None = strawberry_django.filter_field()
- serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
- asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ serial: StrFilterLookup | None = strawberry_django.filter_field()
+ asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -1006,7 +1006,7 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilter):
unit_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -1057,8 +1057,8 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilter):
@strawberry_django.filter_type(models.Site, lookups=True)
class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -1072,11 +1072,11 @@ class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- facility: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ facility: StrFilterLookup | None = strawberry_django.filter_field()
asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
- time_zone: StrFilterLookup[str] | None = strawberry_django.filter_field()
- physical_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
- shipping_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ time_zone: StrFilterLookup | None = strawberry_django.filter_field()
+ physical_address: StrFilterLookup | None = strawberry_django.filter_field()
+ shipping_address: StrFilterLookup | None = strawberry_django.filter_field()
latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -1105,8 +1105,8 @@ class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilter):
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: StrFilterLookup[str] | None = strawberry_django.filter_field()
- domain: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ domain: StrFilterLookup | None = strawberry_django.filter_field()
members: (
Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None
) = strawberry_django.filter_field()
@@ -1117,7 +1117,7 @@ class VirtualChassisFilter(PrimaryModelFilter):
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: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
status: (
BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None
) = (
@@ -1134,7 +1134,7 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter):
strawberry_django.filter_field()
)
primary_ip6_id: ID | None = strawberry_django.filter_field()
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
interfaces: (
Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None
) = strawberry_django.filter_field()
diff --git a/netbox/dcim/migrations/0239_device__config_context_data.py b/netbox/dcim/migrations/0240_device__config_context_data.py
similarity index 93%
rename from netbox/dcim/migrations/0239_device__config_context_data.py
rename to netbox/dcim/migrations/0240_device__config_context_data.py
index 39d198784..4c523deb8 100644
--- a/netbox/dcim/migrations/0239_device__config_context_data.py
+++ b/netbox/dcim/migrations/0240_device__config_context_data.py
@@ -3,7 +3,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('dcim', '0238_ltree_paths'),
+ ('dcim', '0239_denormalization_triggers'),
]
operations = [
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
index 218300a53..d1c70d942 100644
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -675,7 +675,10 @@ class CableTermination(ChangeLoggedModel):
Cache objects related to the termination (e.g. device, rack, site) directly on the object to
enable efficient filtering.
"""
- assert self.termination is not None
+ if self.termination is None:
+ raise ValueError(
+ _("Invalid cable termination: the assigned termination object does not exist.")
+ )
# Device components
if getattr(self.termination, 'device', None):
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index ce2e376bf..af008ceca 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -384,12 +384,27 @@ class PathEndpoint(models.Model):
a stale in-memory `_path` relation while the database already points to
a different CablePath (or to no path at all).
- If the cached relation points to a CablePath that has just been
- deleted, refresh only the `_path` field from the database and retry.
- This keeps the fix cheap and narrowly scoped to the denormalized FK.
+ Two stale cases are repaired by refreshing only the `_path` field
+ from the database:
+
+ 1. The endpoint is linked (by cable or wireless link) but `_path` is
+ unset, because the instance was loaded before its path was traced
+ (e.g. while queued for event serialization during link creation).
+ 2. The cached relation points to a CablePath row that has just been
+ deleted.
+
+ Repairing case 1 costs one query per access for a linked endpoint
+ whose path is genuinely absent in the database. That state is
+ transient outside of tracing failures, so no result caching is
+ attempted here.
"""
if self._path_id is None:
- return None
+ has_link = self.cable_id is not None or getattr(self, 'wireless_link_id', None) is not None
+ if self.pk and has_link:
+ self.refresh_from_db(fields=['_path'])
+
+ if self._path_id is None:
+ return None
try:
return self._path
diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py
index 100293bd6..05fd74495 100644
--- a/netbox/dcim/models/modules.py
+++ b/netbox/dcim/models/modules.py
@@ -325,9 +325,20 @@ class Module(TrackingModelMixin, PrimaryModel):
def save(self, *args, **kwargs):
is_new = self.pk is None
+ old_module_bay_id = None
+
+ if not is_new:
+ old_module_bay_id = Module.objects.filter(pk=self.pk).values_list(
+ 'module_bay_id', flat=True
+ ).first()
super().save(*args, **kwargs)
+ if old_module_bay_id is not None and old_module_bay_id != self.module_bay_id:
+ for child_bay in self.modulebays.select_related('module__module_bay'):
+ child_bay.snapshot()
+ child_bay.save()
+
adopt_components = getattr(self, '_adopt_components', False)
disable_replication = getattr(self, '_disable_replication', False)
diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py
index 7a226e0c6..1b7eb1d32 100644
--- a/netbox/dcim/models/racks.py
+++ b/netbox/dcim/models/racks.py
@@ -638,9 +638,6 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, TrackingModelMixin, RackBase):
return elevation.render(face)
- def get_0u_devices(self):
- return self.devices.filter(position=0)
-
def get_utilization(self):
"""
Determine the utilization rate of the rack and return it as a percentage. Occupied and reserved units both count
diff --git a/netbox/dcim/tests/query_counts.json b/netbox/dcim/tests/query_counts.json
index 1d7cdda72..7861ee6b7 100644
--- a/netbox/dcim/tests/query_counts.json
+++ b/netbox/dcim/tests/query_counts.json
@@ -72,7 +72,7 @@
"rearporttemplate:api_list_objects": 12,
"region:api_list_objects": 13,
"region:list_objects_with_permission": 20,
- "site:api_list_objects": 16,
+ "site:api_list_objects": 17,
"site:list_objects_with_permission": 22,
"sitegroup:api_list_objects": 13,
"sitegroup:list_objects_with_permission": 20,
diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py
index f6b8a4726..42e550a5c 100644
--- a/netbox/dcim/tests/test_api.py
+++ b/netbox/dcim/tests/test_api.py
@@ -20,6 +20,8 @@ from users.models import ObjectPermission, Token, User
from utilities.testing import (
APITestCase,
APIViewTestCases,
+ GraphQLFilterTest,
+ GraphQLQueryTest,
create_test_device,
create_test_nat_ip_pair,
disable_logging,
@@ -146,6 +148,19 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase):
bulk_update_data = {
'status': 'planned',
}
+ graphql_filter_tests = (
+ GraphQLFilterTest(
+ name='tenant__name__exact',
+ filters='tenant: {name: {exact: "Tenant 1"}}',
+ expected=lambda qs: qs.filter(tenant__name='Tenant 1'),
+ permissions=('tenancy.view_tenant',),
+ ),
+ )
+
+ def assert_nested_locations_active(self, data):
+ site_data = data.get('site') or {}
+ location_names = sorted(location['name'] for location in site_data.get('locations', []))
+ self.assertEqual(location_names, ['Site1 Active A', 'Site1 Active B'])
@classmethod
def setUpTestData(cls):
@@ -160,15 +175,32 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase):
SiteGroup.objects.create(name='Site Group 2', slug='site-group-2'),
)
+ tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
+
+ # Site 1's tenant activates the dynamic tenant prefetch (+1 in api_list_objects baseline).
sites = (
- Site(region=regions[0], group=groups[0], name='Site 1', slug='site-1'),
+ Site(region=regions[0], group=groups[0], tenant=tenant, name='Site 1', slug='site-1'),
Site(region=regions[0], group=groups[0], name='Site 2', slug='site-2'),
Site(region=regions[0], group=groups[0], name='Site 3', slug='site-3'),
)
Site.objects.bulk_create(sites)
+ nested_site = Site.objects.get(slug='site-1')
+ cls.nested_site_pk = nested_site.pk
+ Location.objects.create(
+ site=nested_site, name='Site1 Active A', slug='site1-active-a',
+ status=LocationStatusChoices.STATUS_ACTIVE,
+ )
+ Location.objects.create(
+ site=nested_site, name='Site1 Active B', slug='site1-active-b',
+ status=LocationStatusChoices.STATUS_ACTIVE,
+ )
+ Location.objects.create(
+ site=nested_site, name='Site1 Planned', slug='site1-planned',
+ status=LocationStatusChoices.STATUS_PLANNED,
+ )
+
rir = RIR.objects.create(name='RFC 6996', is_private=True)
- tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
asns = [
ASN(asn=65000 + i, rir=rir) for i in range(8)
@@ -203,6 +235,19 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase):
},
]
+ cls.graphql_query_tests = (
+ GraphQLQueryTest(
+ name='nested_locations_by_status',
+ query=(
+ '{ site(id: ' + str(cls.nested_site_pk) + ') { '
+ 'locations(filters: {status: {exact: STATUS_ACTIVE}}) { name } '
+ '} }'
+ ),
+ assert_result=cls.assert_nested_locations_active,
+ permissions=('dcim.view_location',),
+ ),
+ )
+
def test_add_tags(self):
"""
Add tags to an existing object via the add_tags field.
@@ -427,6 +472,16 @@ class LocationTestCase(APIViewTestCases.APIViewTestCase):
'description': 'New description',
}
user_permissions = ('dcim.view_site',)
+ graphql_filter_tests = (
+ GraphQLFilterTest(
+ name='status__in_list',
+ filters='status: {in_list: [STATUS_PLANNED, STATUS_STAGING]}',
+ expected=lambda qs: qs.filter(status__in=[
+ LocationStatusChoices.STATUS_PLANNED,
+ LocationStatusChoices.STATUS_STAGING,
+ ]),
+ ),
+ )
@classmethod
def setUpTestData(cls):
@@ -476,6 +531,20 @@ class LocationTestCase(APIViewTestCases.APIViewTestCase):
parent=parent_locations[0],
status=LocationStatusChoices.STATUS_ACTIVE,
)
+ Location.objects.create(
+ site=sites[0],
+ name='GraphQL Planned Location',
+ slug='graphql-planned-location',
+ parent=parent_locations[0],
+ status=LocationStatusChoices.STATUS_PLANNED,
+ )
+ Location.objects.create(
+ site=sites[0],
+ name='GraphQL Staging Location',
+ slug='graphql-staging-location',
+ parent=parent_locations[0],
+ status=LocationStatusChoices.STATUS_STAGING,
+ )
cls.create_data = [
{
diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py
index 3fe568384..2bb0d241d 100644
--- a/netbox/dcim/tests/test_forms.py
+++ b/netbox/dcim/tests/test_forms.py
@@ -503,6 +503,15 @@ class InterfaceTestCase(TestCase):
self.assertNotIn('qinq_svlan', form.cleaned_data.keys())
+class CableTestCase(TestCase):
+
+ def test_invalid_side_designation_raises_value_error(self):
+ """_clean_side rejects a side other than 'a' or 'b' with ValueError."""
+ form = CableImportForm.__new__(CableImportForm)
+ with self.assertRaisesMessage(ValueError, "Invalid side designation: c"):
+ form._clean_side('c')
+
+
class SiteFormTestCase(TestCase):
"""
Tests for M2MAddRemoveFields using Site ASN assignments as the test case.
diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py
index aa7c43b15..db65164cd 100644
--- a/netbox/dcim/tests/test_models.py
+++ b/netbox/dcim/tests/test_models.py
@@ -1,4 +1,5 @@
from django.core.exceptions import ValidationError
+from django.db.models.signals import post_save
from django.test import TestCase, tag
from circuits.models import *
@@ -1187,6 +1188,105 @@ class ModuleBayTestCase(TestCase):
# tree: its path is now a strict descendant of host_bay's path.
self.assertTrue(str(movable_bay.path).startswith(f'{host_bay.path}.'))
+ @tag('regression') # #22251
+ def test_moving_module_reparents_child_module_bays(self):
+ """
+ When a module is moved to a different module bay, each child ModuleBay
+ (a bay that belongs to the module) must have its parent updated to the
+ new host bay. Without the fix the children stay parented to the old bay
+ even though Module.module_bay_id has changed.
+ """
+ device_type = DeviceType.objects.first()
+ device_role = DeviceRole.objects.first()
+ site = Site.objects.first()
+ device = Device.objects.create(
+ name='Move Module Device',
+ device_type=device_type,
+ role=device_role,
+ site=site,
+ )
+ bay_a = ModuleBay.objects.create(device=device, name='Bay A')
+ bay_b = ModuleBay.objects.create(device=device, name='Bay B')
+
+ manufacturer = Manufacturer.objects.first()
+ module_type = ModuleType.objects.create(
+ manufacturer=manufacturer, model='Move Module Type'
+ )
+ module = Module.objects.create(
+ device=device, module_bay=bay_a, module_type=module_type
+ )
+
+ child_1 = ModuleBay.objects.create(device=device, module=module, name='Child Bay 1')
+ child_2 = ModuleBay.objects.create(device=device, module=module, name='Child Bay 2')
+ self.assertEqual(child_1.parent_id, bay_a.pk)
+ self.assertEqual(child_2.parent_id, bay_a.pk)
+
+ # Move the module to bay_b.
+ module.module_bay = bay_b
+ module.save()
+
+ child_1.refresh_from_db()
+ child_2.refresh_from_db()
+ self.assertEqual(child_1.parent_id, bay_b.pk)
+ self.assertEqual(child_2.parent_id, bay_b.pk)
+ # Children must be re-rooted under bay_b in the ltree hierarchy.
+ bay_b.refresh_from_db()
+ self.assertTrue(str(child_1.path).startswith(f'{bay_b.path}.'))
+ self.assertTrue(str(child_2.path).startswith(f'{bay_b.path}.'))
+
+ @tag('regression') # #22251
+ def test_moving_module_reparents_grandchild_module_bays(self):
+ """
+ When a module is moved, grandchild ModuleBays (bays inside a module
+ that is itself installed inside a child bay of the moved module) must
+ also land in the new ltree subtree. The trigger cascade moves subtrees
+ atomically, so calling save() only on direct children is sufficient —
+ this test documents and preserves that invariant for future tree-backend
+ changes.
+ """
+ device_type = DeviceType.objects.first()
+ device_role = DeviceRole.objects.first()
+ site = Site.objects.first()
+ device = Device.objects.create(
+ name='Grandchild Move Device',
+ device_type=device_type,
+ role=device_role,
+ site=site,
+ )
+ bay_a = ModuleBay.objects.create(device=device, name='Bay A')
+ bay_b = ModuleBay.objects.create(device=device, name='Bay B')
+
+ manufacturer = Manufacturer.objects.first()
+ module_type = ModuleType.objects.create(
+ manufacturer=manufacturer, model='Grandchild Move Type'
+ )
+ # Depth-1: module installed in bay_a, with one child bay.
+ module_1 = Module.objects.create(device=device, module_bay=bay_a, module_type=module_type)
+ child_bay = ModuleBay.objects.create(device=device, module=module_1, name='Child Bay')
+
+ # Depth-2: module installed in child_bay, with one grandchild bay.
+ module_2 = Module.objects.create(device=device, module_bay=child_bay, module_type=module_type)
+ grandchild_bay = ModuleBay.objects.create(device=device, module=module_2, name='Grandchild Bay')
+
+ self.assertEqual(child_bay.parent_id, bay_a.pk)
+ self.assertEqual(grandchild_bay.parent_id, child_bay.pk)
+ bay_a.refresh_from_db()
+ self.assertTrue(str(grandchild_bay.path).startswith(f'{bay_a.path}.'))
+
+ # Move the top-level module to bay_b.
+ module_1.module_bay = bay_b
+ module_1.save()
+
+ child_bay.refresh_from_db()
+ grandchild_bay.refresh_from_db()
+ bay_b.refresh_from_db()
+
+ self.assertEqual(child_bay.parent_id, bay_b.pk)
+ self.assertTrue(str(child_bay.path).startswith(f'{bay_b.path}.'))
+ # Grandchild's direct parent (child_bay) is unchanged; only tree placement moves.
+ self.assertEqual(grandchild_bay.parent_id, child_bay.pk)
+ self.assertTrue(str(grandchild_bay.path).startswith(f'{bay_b.path}.'))
+
def test_single_module_token(self):
device_type = DeviceType.objects.first()
device_role = DeviceRole.objects.first()
@@ -2066,6 +2166,73 @@ class CableTestCase(TestCase):
self.assertIsNone(data['connected_endpoints_type'])
self.assertFalse(data['connected_endpoints_reachable'])
+ @tag('regression') # #21338
+ def test_path_refreshes_unset_cablepath_reference(self):
+ """
+ An endpoint instance saved during cable creation, before path tracing,
+ should resolve its path and connected endpoints.
+
+ The stale-instance preconditions rely on Cable.save() saving each
+ CableTermination (which re-saves the endpoint) before trace_paths
+ creates the CablePath records.
+ """
+ device = Device.objects.get(name='TestDevice2')
+ interface_a = Interface.objects.create(device=device, name='eth2')
+ interface_b = Interface.objects.create(device=device, name='eth3')
+
+ # Capture the instances handed to the event machinery on save
+ saved_instances = []
+
+ def capture(sender, instance, **kwargs):
+ saved_instances.append(instance)
+
+ post_save.connect(capture, sender=Interface)
+ try:
+ Cable(a_terminations=[interface_a], b_terminations=[interface_b]).save()
+ finally:
+ post_save.disconnect(capture, sender=Interface)
+
+ self.assertEqual(len(saved_instances), 2)
+ captured_a = next(i for i in saved_instances if i.pk == interface_a.pk)
+ captured_b = next(i for i in saved_instances if i.pk == interface_b.pk)
+
+ # The captured instances predate path tracing: cabled, but no path yet
+ self.assertIsNotNone(captured_a.cable_id)
+ self.assertIsNone(captured_a._path_id)
+ self.assertIsNone(captured_b._path_id)
+
+ # The accessor must repair the unset denormalized reference
+ self.assertIsNotNone(captured_a.path)
+ self.assertEqual(captured_a.connected_endpoints, [interface_b])
+
+ # Serialization as performed by the event queue must see the peer
+ data = serialize_for_event(captured_b)
+ self.assertEqual([endpoint['id'] for endpoint in data['connected_endpoints']], [interface_a.pk])
+ self.assertEqual([peer['id'] for peer in data['link_peers']], [interface_a.pk])
+ self.assertTrue(data['connected_endpoints_reachable'])
+
+ def test_path_returns_none_for_unsaved_endpoint(self):
+ """
+ An unsaved endpoint with a link assigned should report no path rather
+ than attempting a database refresh.
+ """
+ device = Device.objects.get(name='TestDevice1')
+ cable = Cable.objects.first()
+ interface = Interface(device=device, name='tmp', cable=cable)
+ self.assertIsNone(interface.path)
+
+
+class CableTerminationTestCase(TestCase):
+
+ def test_cache_related_objects_requires_resolvable_termination(self):
+ """cache_related_objects raises ValueError when the termination cannot be resolved."""
+ cable_termination = CableTermination(
+ termination_type=ObjectType.objects.get_for_model(Interface),
+ termination_id=0,
+ )
+ with self.assertRaises(ValueError):
+ cable_termination.cache_related_objects()
+
class VirtualDeviceContextTestCase(TestCase):
diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py
index 478ddf634..bd2adc7f6 100644
--- a/netbox/dcim/ui/panels.py
+++ b/netbox/dcim/ui/panels.py
@@ -32,7 +32,7 @@ class RackDimensionsPanel(panels.ObjectAttributesPanel):
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=_('{} millimeters'))
+ mounting_depth = attrs.TextAttr('mounting_depth', format_string=_('{0} millimeters'))
class RackNumberingPanel(panels.ObjectAttributesPanel):
@@ -355,8 +355,8 @@ class PowerFeedElectricalPanel(panels.ObjectAttributesPanel):
title = _('Electrical Characteristics')
supply = attrs.ChoiceAttr('supply')
- voltage = attrs.TextAttr('voltage', format_string=_('{}V'))
- amperage = attrs.TextAttr('amperage', format_string=_('{}A'))
+ voltage = attrs.TextAttr('voltage', format_string='{}V')
+ amperage = attrs.TextAttr('amperage', format_string='{}A')
phase = attrs.ChoiceAttr('phase')
max_utilization = attrs.TextAttr('max_utilization', format_string='{}%')
diff --git a/netbox/extras/events.py b/netbox/extras/events.py
index 470adc875..c01e02ed5 100644
--- a/netbox/extras/events.py
+++ b/netbox/extras/events.py
@@ -125,7 +125,13 @@ def enqueue_event(queue, instance, request, event_type):
app_label = instance._meta.app_label
model_name = instance._meta.model_name
- assert instance.pk is not None
+ if instance.pk is None:
+ raise ValueError(
+ _("Cannot enqueue an event for an unsaved {app_label}.{model} instance.").format(
+ app_label=app_label,
+ model=model_name,
+ )
+ )
key = f'{app_label}.{model_name}:{instance.pk}'
if key in queue:
@@ -251,7 +257,7 @@ def process_event_rules(event_rules, object_type, event):
if 'snapshots' in event:
params['snapshots'] = event['snapshots']
if 'request' in event:
- params['request'] = copy_safe_request(event['request'])
+ params['request'] = copy_safe_request(event['request'], include_files=False)
# Enqueue the job
ScriptJob.enqueue(**params)
diff --git a/netbox/extras/fields.py b/netbox/extras/fields.py
index 6cd44432f..735471757 100644
--- a/netbox/extras/fields.py
+++ b/netbox/extras/fields.py
@@ -1,4 +1,10 @@
-from django.db.models import TextField
+from django.contrib.postgres.fields import ArrayField
+from django.db.models import CharField, TextField
+
+__all__ = (
+ 'CachedValueField',
+ 'ChoiceSetField',
+)
class CachedValueField(TextField):
@@ -6,3 +12,18 @@ class CachedValueField(TextField):
Currently a dummy field to prevent custom lookups being applied globally to TextField.
"""
pass
+
+
+class ChoiceSetField(ArrayField):
+ """
+ An ArrayField of two-element [value, label] string pairs representing custom field choices.
+ """
+ def __init__(self, **kwargs):
+ kwargs['base_field'] = ArrayField(base_field=CharField(max_length=100), size=2)
+ super().__init__(**kwargs)
+
+ def deconstruct(self):
+ name, path, args, kwargs = super().deconstruct()
+ # base_field is fixed by __init__ and omitted from migrations
+ del kwargs['base_field']
+ return name, path, args, kwargs
diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py
index 9fecfdb7a..abfe22f5a 100644
--- a/netbox/extras/forms/bulk_edit.py
+++ b/netbox/extras/forms/bulk_edit.py
@@ -212,7 +212,7 @@ class SavedFilterBulkEditForm(ChangelogMessageMixin, OwnerMixin, BulkEditForm):
nullable_fields = ('description',)
-class TableConfigBulkEditForm(BulkEditForm):
+class TableConfigBulkEditForm(ChangelogMessageMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=TableConfig.objects.all(),
widget=forms.MultipleHiddenInput
diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py
index 688f19563..0f8161632 100644
--- a/netbox/extras/forms/model_forms.py
+++ b/netbox/extras/forms/model_forms.py
@@ -403,7 +403,7 @@ class SavedFilterForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm):
super().__init__(*args, initial=initial, **kwargs)
-class TableConfigForm(forms.ModelForm):
+class TableConfigForm(ChangelogMessageMixin, forms.ModelForm):
object_type = ContentTypeChoiceField(
label=_('Object type'),
queryset=ObjectType.objects.all()
@@ -439,10 +439,29 @@ class TableConfigForm(forms.ModelForm):
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
- object_type = ObjectType.objects.get(pk=get_field_value(self, 'object_type'))
+ self.fields['available_columns'].widget.choices = ()
+ self.fields['columns'].widget.choices = ()
+
+ # Table context may be absent e.g. when the add view is requested directly
+ object_type_pk = get_field_value(self, 'object_type')
+ object_type_pk = getattr(object_type_pk, 'pk', object_type_pk)
+ if not object_type_pk:
+ return
+
+ try:
+ object_type = ObjectType.objects.get(pk=object_type_pk)
+ except (ObjectType.DoesNotExist, TypeError, ValueError):
+ return
+
model = object_type.model_class()
+ if model is None:
+ return
+
table_name = get_field_value(self, 'table')
table_class = get_table_for_model(model, table_name)
+ if table_class is None:
+ return
+
table = table_class([])
if columns := self._get_columns():
diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py
index b646c397d..2aa8f4786 100644
--- a/netbox/extras/forms/scripts.py
+++ b/netbox/extras/forms/scripts.py
@@ -112,5 +112,4 @@ class ScriptFileForm(ManagedFileForm):
data = self.cleaned_data['upload_file']
storage.save(filename, data)
- # need to skip ManagedFileForm save method
- return super(ManagedFileForm, self).save(*args, **kwargs)
+ return super().save(*args, **kwargs)
diff --git a/netbox/extras/graphql/filter_lookups.py b/netbox/extras/graphql/filter_lookups.py
new file mode 100644
index 000000000..8394f8541
--- /dev/null
+++ b/netbox/extras/graphql/filter_lookups.py
@@ -0,0 +1,30 @@
+import strawberry
+import strawberry_django
+from django.db.models import Q, QuerySet
+from strawberry.directive import DirectiveValue
+from strawberry.types import Info
+
+__all__ = (
+ 'ExtraChoicesLookup',
+)
+
+
+@strawberry.input(
+ one_of=True,
+ description='Lookup for extra choices defined on a choice set. Only one of the lookup fields can be set.',
+)
+class ExtraChoicesLookup:
+ contains: str | None = strawberry.field(
+ default=strawberry.UNSET, description='Has an extra choice with this value'
+ )
+ length: int | None = strawberry.field(
+ default=strawberry.UNSET, description='Number of extra choices'
+ )
+
+ @strawberry_django.filter_field
+ def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]:
+ if self.contains is not strawberry.UNSET and self.contains is not None:
+ return queryset, Q(**{f'{prefix}choice_value': self.contains})
+ if self.length is not strawberry.UNSET and self.length is not None:
+ return queryset, Q(**{f'{prefix}len': self.length})
+ return queryset, Q()
diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py
index afc1ff8f9..5b6e2eab6 100644
--- a/netbox/extras/graphql/filters.py
+++ b/netbox/extras/graphql/filters.py
@@ -1,4 +1,3 @@
-from datetime import datetime
from typing import TYPE_CHECKING, Annotated
import strawberry
@@ -23,6 +22,7 @@ if TYPE_CHECKING:
SiteFilter,
SiteGroupFilter,
)
+ from extras.graphql.filter_lookups import ExtraChoicesLookup
from netbox.graphql.enums import ColorEnum
from netbox.graphql.filter_lookups import FloatLookup, IntegerLookup, JSONFilter, StringArrayLookup, TreeNodeFilter
from tenancy.graphql.filters import TenantFilter, TenantGroupFilter
@@ -54,11 +54,11 @@ __all__ = (
@strawberry_django.filter_type(models.ConfigContext, lookups=True)
class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | 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()
@@ -111,22 +111,22 @@ class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.ConfigContextProfile, lookups=True)
class ConfigContextProfileFilter(SyncedDataFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = 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(SyncedDataFilterMixin, ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
- template_code: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
+ template_code: StrFilterLookup | None = strawberry_django.filter_field()
environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
- file_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ mime_type: StrFilterLookup | None = strawberry_django.filter_field()
+ file_name: StrFilterLookup | None = strawberry_django.filter_field()
+ file_extension: StrFilterLookup | None = strawberry_django.filter_field()
as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -141,10 +141,10 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
related_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- label: StrFilterLookup[str] | None = strawberry_django.filter_field()
- group_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ label: StrFilterLookup | None = strawberry_django.filter_field()
+ group_name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
required: FilterLookup[bool] | None = strawberry_django.filter_field()
unique: FilterLookup[bool] | None = strawberry_django.filter_field()
search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -170,7 +170,7 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- validation_regex: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ validation_regex: StrFilterLookup | None = strawberry_django.filter_field()
choice_set: Annotated['CustomFieldChoiceSetFilter', strawberry.lazy('extras.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -186,19 +186,19 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
strawberry_django.filter_field()
)
is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field()
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True)
class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
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 = (
+ extra_choices: Annotated['ExtraChoicesLookup', strawberry.lazy('extras.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
order_alphabetically: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -233,14 +233,14 @@ class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.CustomLink, lookups=True)
class CustomLinkFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
- link_text: StrFilterLookup[str] | None = strawberry_django.filter_field()
- link_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ link_text: StrFilterLookup | None = strawberry_django.filter_field()
+ link_url: StrFilterLookup | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- group_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ group_name: StrFilterLookup | None = strawberry_django.filter_field()
button_class: (
BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None
) = (
@@ -251,15 +251,15 @@ class CustomLinkFilter(ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.ExportTemplate, lookups=True)
class ExportTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
- template_code: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
+ template_code: StrFilterLookup | None = strawberry_django.filter_field()
environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
- file_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ mime_type: StrFilterLookup | None = strawberry_django.filter_field()
+ file_name: StrFilterLookup | None = strawberry_django.filter_field()
+ file_extension: StrFilterLookup | None = strawberry_django.filter_field()
as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -275,7 +275,7 @@ class ImageAttachmentFilter(ChangeLoggedModelFilter):
image_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.JournalEntry, lookups=True)
@@ -291,13 +291,13 @@ class JournalEntryFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedM
kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Notification, lookups=True)
class NotificationFilter(BaseModelFilter):
- created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
- read: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+ read: DatetimeFilterLookup | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -305,23 +305,23 @@ class NotificationFilter(BaseModelFilter):
)
object_type_id: ID | None = strawberry_django.filter_field()
object_id: ID | None = strawberry_django.filter_field()
- object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field()
- event_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ object_repr: StrFilterLookup | None = strawberry_django.filter_field()
+ event_type: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.NotificationGroup, lookups=True)
class NotificationGroupFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.SavedFilter, lookups=True)
class SavedFilterFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -336,7 +336,7 @@ class SavedFilterFilter(ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.Subscription, lookups=True)
class SubscriptionFilter(BaseModelFilter):
- created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ created: DatetimeFilterLookup | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -348,8 +348,8 @@ class SubscriptionFilter(BaseModelFilter):
@strawberry_django.filter_type(models.TableConfig, lookups=True)
class TableConfigFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
user_id: ID | None = strawberry_django.filter_field()
weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -361,30 +361,30 @@ class TableConfigFilter(ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.Tag, lookups=True)
class TagFilter(ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.Webhook, lookups=True)
class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
- payload_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
+ payload_url: StrFilterLookup | None = strawberry_django.filter_field()
http_method: (
BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None
) = (
strawberry_django.filter_field()
)
- http_content_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
- additional_headers: StrFilterLookup[str] | None = strawberry_django.filter_field()
- body_template: StrFilterLookup[str] | None = strawberry_django.filter_field()
- secret: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ http_content_type: StrFilterLookup | None = strawberry_django.filter_field()
+ additional_headers: StrFilterLookup | None = strawberry_django.filter_field()
+ body_template: StrFilterLookup | None = strawberry_django.filter_field()
+ secret: StrFilterLookup | None = strawberry_django.filter_field()
ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field()
- ca_file_path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ ca_file_path: StrFilterLookup | None = strawberry_django.filter_field()
events: Annotated['EventRuleFilter', strawberry.lazy('extras.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -392,8 +392,8 @@ class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelF
@strawberry_django.filter_type(models.EventRule, lookups=True)
class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -412,4 +412,4 @@ class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedMode
action_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
diff --git a/netbox/extras/lookups.py b/netbox/extras/lookups.py
index 062a2a01b..e1b4d2339 100644
--- a/netbox/extras/lookups.py
+++ b/netbox/extras/lookups.py
@@ -3,7 +3,16 @@ 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
+from .fields import CachedValueField, ChoiceSetField
+
+__all__ = (
+ 'ChoiceValueLookup',
+ 'Empty',
+ 'JSONEmpty',
+ 'NetContainsOrEquals',
+ 'NetHost',
+ 'RangeContains',
+)
class RangeContains(Lookup):
@@ -34,6 +43,22 @@ class RangeContains(Lookup):
return sql, params
+class ChoiceValueLookup(Lookup):
+ """
+ Match rows where any [value, label] pair in a ChoiceSetField has the given value.
+
+ Compares the RHS against the first element (the value) of each pair.
+ """
+ lookup_name = 'choice_value'
+ prepare_rhs = False
+
+ def as_sql(self, compiler, connection):
+ lhs, lhs_params = self.process_lhs(compiler, connection)
+ rhs, rhs_params = self.process_rhs(compiler, connection)
+ # Slice the value column of the two-dimensional array and match any element
+ return f'{rhs} = ANY({lhs}[:][1:1])', [*rhs_params, *lhs_params]
+
+
class Empty(Lookup):
"""
Filter on whether a string is empty.
@@ -99,6 +124,7 @@ class NetContainsOrEquals(Lookup):
ArrayField.register_lookup(RangeContains)
+ChoiceSetField.register_lookup(ChoiceValueLookup)
CharField.register_lookup(Empty)
JSONField.register_lookup(JSONEmpty)
CachedValueField.register_lookup(NetHost)
diff --git a/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py b/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py
new file mode 100644
index 000000000..b0f5704c6
--- /dev/null
+++ b/netbox/extras/migrations/0139_alter_customfieldchoiceset_extra_choices.py
@@ -0,0 +1,18 @@
+from django.db import migrations
+
+import extras.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('extras', '0138_customfieldchoiceset_choice_colors'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='customfieldchoiceset',
+ name='extra_choices',
+ field=extras.fields.ChoiceSetField(blank=True, null=True),
+ ),
+ ]
diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py
index 011afa42d..31d417564 100644
--- a/netbox/extras/models/customfields.py
+++ b/netbox/extras/models/customfields.py
@@ -7,7 +7,6 @@ import django_filters
import jsonschema
from django import forms
from django.conf import settings
-from django.contrib.postgres.fields import ArrayField
from django.core.validators import RegexValidator, ValidationError
from django.db import models
from django.db.models import F, Func, Value
@@ -21,6 +20,7 @@ from jsonschema.exceptions import ValidationError as JSONValidationError
from core.models import ObjectType
from extras.choices import *
from extras.data import CHOICE_SETS
+from extras.fields import ChoiceSetField
from netbox.context import query_cache
from netbox.models import ChangeLoggedModel
from netbox.models.features import CloningMixin, ExportTemplatesMixin
@@ -461,6 +461,8 @@ class CustomField(CloningMixin, ExportTemplatesMixin, OwnerMixin, ChangeLoggedMo
"""
if value is None:
return value
+ if self.type == CustomFieldTypeChoices.TYPE_DECIMAL:
+ return float(value)
if self.type == CustomFieldTypeChoices.TYPE_DATE and type(value) is date:
return value.isoformat()
if self.type == CustomFieldTypeChoices.TYPE_DATETIME and type(value) is datetime:
@@ -877,11 +879,7 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, OwnerMixin, Chang
null=True,
help_text=_('Base set of predefined choices (optional)')
)
- extra_choices = ArrayField(
- ArrayField(
- base_field=models.CharField(max_length=100),
- size=2
- ),
+ extra_choices = ChoiceSetField(
blank=True,
null=True
)
diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py
index 14cae8d9d..d538f94bf 100644
--- a/netbox/extras/models/models.py
+++ b/netbox/extras/models/models.py
@@ -649,6 +649,10 @@ class TableConfig(CloningMixin, ChangeLoggedModel):
def clean(self):
super().clean()
+ # Skip table validation until the object type and table have been set
+ if not self.object_type_id or not self.table:
+ return
+
# Validate table
if self.table_class is None:
raise ValidationError({
@@ -667,7 +671,7 @@ class TableConfig(CloningMixin, ChangeLoggedModel):
})
# Validate selected columns
- for name in self.columns:
+ for name in self.columns or []:
if name not in table.columns:
raise ValidationError({
'columns': _('Unknown column: {name}').format(name=name)
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
index 3a9703671..74eeb191c 100644
--- a/netbox/extras/tests/test_api.py
+++ b/netbox/extras/tests/test_api.py
@@ -325,6 +325,64 @@ class CustomFieldChoiceSetTestCase(APIViewTestCases.APIViewTestCase):
response = self.client.post(self._get_list_url(), data, format='json', **self.header)
self.assertEqual(response.status_code, 400)
+ def test_graphql_filter_extra_choices(self):
+ """Filter choice sets by choice value and by number of choices."""
+ self.add_permissions('extras.view_customfieldchoiceset')
+
+ # '1A' appears here only as a label, so it must not match contains
+ CustomFieldChoiceSet.objects.create(
+ name='Choice Set Labels',
+ extra_choices=[['sel1', 'Selection 1'], ['other', '1A']],
+ )
+
+ def run(lookup):
+ query = '{ custom_field_choice_set_list(filters: {extra_choices: ' + lookup + '}) { name } }'
+ response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ data = response.json()
+ self.assertNotIn('errors', data)
+ return sorted(row['name'] for row in data['data']['custom_field_choice_set_list'])
+
+ # contains matches choice values only, never labels
+ self.assertEqual(run('{contains: "1A"}'), ['Choice Set 1'])
+ self.assertEqual(run('{contains: "sel1"}'), ['Choice Set Labels'])
+ self.assertEqual(run('{contains: "Selection 1"}'), [])
+ # length is the number of [value, label] pairs
+ self.assertEqual(run('{length: 2}'), ['Choice Set Labels'])
+ self.assertEqual(run('{length: 1}'), [])
+
+ def test_graphql_filter_extra_choices_rejects_array_operands(self):
+ """The legacy flat and nested array operand shapes fail schema validation."""
+ self.add_permissions('extras.view_customfieldchoiceset')
+
+ def run_invalid(lookup):
+ query = '{ custom_field_choice_set_list(filters: {extra_choices: ' + lookup + '}) { name } }'
+ response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ self.assertIn('errors', response.json())
+
+ # shapes advertised or attempted before #22324
+ run_invalid('{contains: ["1A"]}')
+ run_invalid('{contains: [["1A", "Choice 1A"]]}')
+
+ def test_graphql_filter_extra_choices_via_relation(self):
+ """The extra_choices lookup composes through the choice_set relation prefix."""
+ self.add_permissions('extras.view_customfield')
+
+ for choice_set in CustomFieldChoiceSet.objects.filter(name__in=['Choice Set 1', 'Choice Set 2']):
+ CustomField.objects.create(
+ name=f'cf_{choice_set.name[-1]}',
+ type=CustomFieldTypeChoices.TYPE_SELECT,
+ choice_set=choice_set,
+ )
+
+ query = '{ custom_field_list(filters: {choice_set: {extra_choices: {contains: "1A"}}}) { name } }'
+ response = self.client.post(reverse('graphql'), data={'query': query}, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+ data = response.json()
+ self.assertNotIn('errors', data)
+ self.assertEqual([row['name'] for row in data['data']['custom_field_list']], ['cf_1'])
+
class CustomLinkTestCase(APIViewTestCases.APIViewTestCase):
model = CustomLink
diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py
index ddd1b05b8..cc0f0bd4d 100644
--- a/netbox/extras/tests/test_event_rules.py
+++ b/netbox/extras/tests/test_event_rules.py
@@ -1,30 +1,36 @@
import json
+import logging
import uuid
+from io import BytesIO
from unittest import skipIf
from unittest.mock import Mock, patch
import django_rq
from django.conf import settings
from django.http import HttpResponse
-from django.test import RequestFactory
+from django.test import RequestFactory, tag
from django.urls import reverse
+from PIL import Image
from requests import Session
from rest_framework import status
+from core.choices import ManagedFileRootPathChoices
from core.events import *
-from core.models import ObjectType
+from core.models import Job, ObjectType
from dcim.choices import SiteStatusChoices
-from dcim.models import Site
+from dcim.models import DeviceType, Interface, Manufacturer, Site
from extras.choices import EventRuleActionChoices
from extras.events import enqueue_event, flush_events, serialize_for_event
-from extras.models import EventRule, Script, Tag, Webhook
+from extras.models import EventRule, Script, ScriptModule, Tag, Webhook
+from extras.scripts import Script as ScriptBase
from extras.signals import process_job_end_event_rules
from extras.webhooks import generate_signature, send_webhook
from netbox.context_managers import event_tracking
-from utilities.testing import APITestCase
+from utilities.testing import APITestCase, create_test_device
+from utilities.testing.mixins import RQQueueTestMixin
-class EventRuleTestCase(APITestCase):
+class EventRuleTestCase(RQQueueTestMixin, APITestCase):
def setUp(self):
super().setUp()
@@ -39,6 +45,16 @@ class EventRuleTestCase(APITestCase):
# Clear the queue so leftover jobs do not leak to the next test suite
self.queue.empty()
+ def test_enqueue_event_requires_saved_instance(self):
+ """enqueue_event raises ValueError for an unsaved instance."""
+ request = RequestFactory().get('/')
+ request.id = uuid.uuid4()
+ request.user = self.user
+ site = Site(name='Site 1', slug='site-1')
+ with patch('extras.events.has_feature', return_value=True):
+ with self.assertRaises(ValueError):
+ enqueue_event({}, site, request, OBJECT_CREATED)
+
@classmethod
def setUpTestData(cls):
@@ -531,6 +547,48 @@ class EventRuleTestCase(APITestCase):
self.assertEqual(event['data']['name'], 'Site 1')
self.assertIsNone(event['snapshots']['postchange'])
+ @tag('regression') # #21338
+ def test_cable_creation_event_payload_includes_connected_endpoints(self):
+ """
+ Interface update events queued during cable creation must include the
+ peer interface in connected_endpoints and link_peers.
+ """
+ webhook = Webhook.objects.get(name='Webhook 1')
+ event_rule = EventRule.objects.create(
+ name='Interface Update Rule',
+ event_types=[OBJECT_UPDATED],
+ action_type=EventRuleActionChoices.WEBHOOK,
+ action_object_type=ObjectType.objects.get_for_model(Webhook),
+ action_object_id=webhook.id,
+ )
+ event_rule.object_types.set([ObjectType.objects.get_for_model(Interface)])
+
+ device = create_test_device('Device 1')
+ interface_a = Interface.objects.create(device=device, name='eth0')
+ interface_b = Interface.objects.create(device=device, name='eth1')
+
+ # Create a cable between the two interfaces via the REST API
+ data = {
+ 'a_terminations': [{'object_type': 'dcim.interface', 'object_id': interface_a.pk}],
+ 'b_terminations': [{'object_type': 'dcim.interface', 'object_id': interface_b.pk}],
+ }
+ url = reverse('dcim-api:cable-list')
+ self.add_permissions('dcim.add_cable')
+ response = self.client.post(url, data, format='json', **self.header)
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
+
+ # One update event was queued for each interface
+ self.assertEqual(self.queue.count, 2)
+ payloads = {job.kwargs['data']['id']: job.kwargs['data'] for job in self.queue.jobs}
+ peers = {interface_a.pk: interface_b.pk, interface_b.pk: interface_a.pk}
+ self.assertEqual(set(payloads), set(peers))
+ for interface_id, payload in payloads.items():
+ peer_id = peers[interface_id]
+ self.assertIsNotNone(payload['connected_endpoints'])
+ self.assertEqual([endpoint['id'] for endpoint in payload['connected_endpoints']], [peer_id])
+ self.assertEqual([peer['id'] for peer in payload['link_peers']], [peer_id])
+ self.assertTrue(payload['connected_endpoints_reachable'])
+
def test_duplicate_triggers(self):
"""
Test for erroneous duplicate event triggers resulting from saving an object multiple times
@@ -601,3 +659,145 @@ class EventRuleTestCase(APITestCase):
self.add_permissions('dcim.add_site')
response = self.client.post(url, {'name': 'Site X', 'slug': 'site-x'}, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
+
+ @tag('regression')
+ def test_eventrule_script_action_with_object_image_files(self):
+ """
+ Verify that a Script event-rule action can be enqueued and executed cleanly when the
+ triggering object carries uploaded files (e.g. DeviceType images).
+ This is a regression test for issue #22376.
+
+ """
+ # Create a dummy script class and an instance of it
+ class DummyScript(ScriptBase):
+ class Meta:
+ name = "Dummy Script"
+
+ def run(self, data, commit=True):
+ return "finished successfully"
+
+ dummy_script = DummyScript()
+
+ # Create ScriptModule and Script
+ with patch.object(ScriptModule, 'sync_classes'):
+ module = ScriptModule.objects.create(
+ file_root=ManagedFileRootPathChoices.SCRIPTS,
+ file_path='dummy_script.py',
+ )
+ script = Script.objects.create(
+ module=module,
+ name='Dummy Script',
+ is_executable=True,
+ )
+ script_type = ObjectType.objects.get_for_model(Script)
+
+ # Create an event rule that triggers on DeviceType update with Script action
+ devicetype_type = ObjectType.objects.get_for_model(DeviceType)
+ event_rule = EventRule.objects.create(
+ name='Test Script Event Rule with Files',
+ event_types=[OBJECT_UPDATED],
+ action_type=EventRuleActionChoices.SCRIPT,
+ action_object_type=script_type,
+ action_object_id=script.pk,
+ )
+ event_rule.object_types.set([devicetype_type])
+
+ # Create a manufacturer and DeviceType
+ manufacturer = Manufacturer.objects.create(
+ name='Test Manufacturer',
+ slug='test-manufacturer',
+ )
+ devicetype = DeviceType.objects.create(
+ model='Test DeviceType',
+ slug="test-devicetype",
+ manufacturer=manufacturer,
+ )
+
+ # Create an image file
+ image = BytesIO()
+ Image.new('RGB', (1, 1)).save(image, format='PNG')
+ image.name = 'test_image.png'
+ image.seek(0)
+
+ # PATCH the DeviceType via REST API to add the image
+ data = {
+ 'front_image': image,
+ }
+ url = reverse('dcim-api:devicetype-detail', kwargs={'pk': devicetype.pk})
+ self.add_permissions('dcim.change_devicetype')
+
+ # Mock the script's python_class to prevent the test from trying to load from disk
+ with patch.object(Script, 'python_class') as mock:
+ mock.return_value = dummy_script
+ # Since in core/models/jobs.py Jobs are enqueued with a transaction.on_commit-handler
+ # we simulate commit by using captureOnCommitCallbacks context manager
+ with self.captureOnCommitCallbacks(execute=True):
+ response = self.client.patch(url, data, format='multipart', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+
+ # Assert that the script job was enqueued cleanly and is waiting for execution
+ self.assertEqual(self.queue.count, 1)
+ script_job = Job.objects.filter(name=dummy_script.name).last()
+ self.assertEqual(script_job.status, "pending")
+
+ # silence rqworker (cleaner output) and trigger job execution
+ logging.getLogger('rq.worker').setLevel(logging.ERROR)
+ self.run_rq_jobs('default')
+
+ # Assert that our script was executed without any errors
+ script_job.refresh_from_db()
+ self.assertEqual(script_job.status, "completed")
+ self.assertEqual(script_job.data.get('output', ''), "finished successfully")
+
+ @tag('regression')
+ def test_eventrule_webhook_action_with_object_image_files(self):
+ """
+ Verify that a Webhook event-rule action can be enqueued and executed cleanly when
+ the triggering object carries uploaded files (e.g. DeviceType images).
+ This is a regression test for issue #20873.
+ """
+ # Create an event rule that triggers on DeviceType update with Script action
+ webhook = Webhook.objects.get(name='Webhook 1')
+ webhook_type = ObjectType.objects.get_for_model(Webhook)
+ devicetype_type = ObjectType.objects.get_for_model(DeviceType)
+ event_rule = EventRule.objects.create(
+ name='Test Webhook Event Rule with Files',
+ event_types=[OBJECT_UPDATED],
+ action_type=EventRuleActionChoices.WEBHOOK,
+ action_object_type=webhook_type,
+ action_object_id=webhook.pk,
+ )
+ event_rule.object_types.set([devicetype_type])
+
+ # Create a manufacturer and DeviceType
+ manufacturer = Manufacturer.objects.create(
+ name='Test Manufacturer',
+ slug='test-manufacturer',
+ )
+ devicetype = DeviceType.objects.create(
+ model='Test DeviceType',
+ slug="test-devicetype",
+ manufacturer=manufacturer,
+ )
+
+ # Create an image file
+ image = BytesIO()
+ Image.new('RGB', (1, 1)).save(image, format='PNG')
+ image.name = 'test_image.png'
+ image.seek(0)
+
+ # PATCH the DeviceType via REST API to add the image
+ data = {
+ 'front_image': image,
+ }
+ url = reverse('dcim-api:devicetype-detail', kwargs={'pk': devicetype.pk})
+ self.add_permissions('dcim.change_devicetype')
+
+ response = self.client.patch(url, data, format='multipart', **self.header)
+ self.assertHttpStatus(response, status.HTTP_200_OK)
+
+ # Assert that the webhook job was enqueued cleanly
+ self.assertEqual(self.queue.count, 1)
+ job = self.queue.jobs[0]
+ self.assertEqual(job.kwargs['event_rule'], event_rule)
+ self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
diff --git a/netbox/extras/tests/test_forms.py b/netbox/extras/tests/test_forms.py
index 26de1de80..909d72801 100644
--- a/netbox/extras/tests/test_forms.py
+++ b/netbox/extras/tests/test_forms.py
@@ -10,7 +10,7 @@ from core.models import DataSource, ObjectType
from dcim.forms import SiteForm
from dcim.models import Site
from extras.choices import CustomFieldTypeChoices
-from extras.forms import SavedFilterForm
+from extras.forms import SavedFilterForm, TableConfigBulkEditForm, TableConfigForm
from extras.forms.model_forms import CustomFieldChoiceSetForm
from extras.forms.scripts import ScriptFileForm
from extras.models import CustomField, CustomFieldChoiceSet, ScriptModule
@@ -288,3 +288,52 @@ class ScriptFileFormTestCase(TestCase):
form = ScriptFileForm(files={'upload_file': upload_file}, instance=self._new_module())
self.assertTrue(form.is_valid())
+
+
+class TableConfigFormTestCase(TestCase):
+
+ def test_form_without_table_context(self):
+ """The form must be constructible without an object type."""
+ form = TableConfigForm()
+ self.assertEqual(list(form.fields['available_columns'].widget.choices), [])
+ self.assertEqual(list(form.fields['columns'].widget.choices), [])
+
+ def test_form_with_invalid_object_type(self):
+ """An unknown object type must yield empty column choices."""
+ last_pk = ObjectType.objects.order_by('pk').last().pk
+ form = TableConfigForm(initial={'object_type': last_pk + 1})
+ self.assertEqual(list(form.fields['available_columns'].widget.choices), [])
+ self.assertEqual(list(form.fields['columns'].widget.choices), [])
+
+ def test_form_with_unknown_table(self):
+ """An unresolvable table name must yield empty column choices."""
+ object_type = ObjectType.objects.get_for_model(Site)
+ form = TableConfigForm(initial={'object_type': object_type.pk, 'table': 'NoSuchTable'})
+ self.assertEqual(list(form.fields['columns'].widget.choices), [])
+
+ def test_form_with_table_context(self):
+ """Column choices must be populated from the resolved table."""
+ object_type = ObjectType.objects.get_for_model(Site)
+ form = TableConfigForm(initial={
+ 'object_type': object_type.pk,
+ 'table': 'SiteTable',
+ 'columns': ['name', 'status'],
+ })
+ self.assertEqual(
+ [name for name, _ in form.fields['columns'].widget.choices],
+ ['name', 'status']
+ )
+ self.assertIn('region', dict(form.fields['available_columns'].widget.choices))
+
+ def test_form_includes_changelog_message(self):
+ """The model form must expose the changelog_message meta field."""
+ object_type = ObjectType.objects.get_for_model(Site)
+ form = TableConfigForm(initial={'object_type': object_type.pk, 'table': 'SiteTable'})
+ self.assertIn('changelog_message', form.fields)
+ self.assertIn('changelog_message', form.meta_fields)
+
+ def test_bulk_edit_form_includes_changelog_message(self):
+ """The bulk edit form must expose the changelog_message meta field."""
+ form = TableConfigBulkEditForm()
+ self.assertIn('changelog_message', form.fields)
+ self.assertIn('changelog_message', form.meta_fields)
diff --git a/netbox/extras/tests/test_lookups.py b/netbox/extras/tests/test_lookups.py
new file mode 100644
index 000000000..7188c2ed6
--- /dev/null
+++ b/netbox/extras/tests/test_lookups.py
@@ -0,0 +1,31 @@
+from django.core.exceptions import FieldError
+from django.test import TestCase
+
+from extras.choices import CustomFieldChoiceSetBaseChoices
+from extras.models import CustomFieldChoiceSet, EventRule
+
+
+class ChoiceValueLookupTestCase(TestCase):
+
+ def test_choice_value_matches_values_only(self):
+ """choice_value matches the value element of a pair, never the label."""
+ CustomFieldChoiceSet.objects.create(
+ name='Choice Set 1',
+ extra_choices=[['sel1', 'Selection 1'], ['other', 'sel2']],
+ )
+ self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel1').count(), 1)
+ self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel2').count(), 0)
+
+ def test_choice_value_excludes_null_extra_choices(self):
+ """Choice sets without extra choices are excluded without raising."""
+ CustomFieldChoiceSet.objects.create(
+ name='Base Only',
+ base_choices=CustomFieldChoiceSetBaseChoices.IATA,
+ )
+ self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__choice_value='sel1').count(), 0)
+ self.assertEqual(CustomFieldChoiceSet.objects.filter(extra_choices__len=2).count(), 0)
+
+ def test_choice_value_not_registered_on_plain_array_fields(self):
+ """choice_value is scoped to ChoiceSetField and unavailable on other ArrayFields."""
+ with self.assertRaises(FieldError):
+ EventRule.objects.filter(event_types__choice_value='x').exists()
diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py
index 7f7721258..fbbe93267 100644
--- a/netbox/extras/tests/test_models.py
+++ b/netbox/extras/tests/test_models.py
@@ -210,6 +210,26 @@ class TableConfigTestCase(TestCase):
# Must not raise TypeError: 'NoneType' object is not iterable
tc.full_clean()
+ def test_clean_without_object_type(self):
+ """full_clean() on an instance missing its object type must raise ValidationError."""
+ tc = TableConfig(
+ table=self.table_name,
+ name='No object type',
+ columns=['name'],
+ )
+ with self.assertRaises(ValidationError):
+ tc.full_clean()
+
+ def test_clean_accepts_columns_none(self):
+ """full_clean() must report missing columns rather than raise TypeError."""
+ tc = TableConfig(
+ object_type=self.site_ct,
+ table=self.table_name,
+ name='No columns',
+ )
+ with self.assertRaises(ValidationError):
+ tc.full_clean()
+
class TagTestCase(TestCase):
diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py
index 74c88cea8..8dd82c239 100644
--- a/netbox/extras/tests/test_views.py
+++ b/netbox/extras/tests/test_views.py
@@ -2,6 +2,7 @@ import uuid
from unittest.mock import PropertyMock, patch
from django.contrib.contenttypes.models import ContentType
+from django.contrib.messages import get_messages
from django.test import tag
from django.urls import reverse
@@ -277,13 +278,16 @@ class SavedFilterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
class TableConfigTestCase(
ViewTestCases.GetObjectViewTestCase,
ViewTestCases.GetObjectChangelogViewTestCase,
- ViewTestCases.ListObjectsViewTestCase,
+ ViewTestCases.CreateObjectViewTestCase,
+ ViewTestCases.EditObjectViewTestCase,
ViewTestCases.DeleteObjectViewTestCase,
+ ViewTestCases.ListObjectsViewTestCase,
+ ViewTestCases.BulkEditObjectsViewTestCase,
ViewTestCases.BulkDeleteObjectsViewTestCase,
):
- # Add/Edit/BulkEdit views require an object_type pre-context from the source
- # table view, so they are not exercised here.
model = TableConfig
+ # Selected columns are POSTed as a list but compared as a CSV string
+ validation_excluded_fields = ('columns',)
@classmethod
def setUpTestData(cls):
@@ -320,6 +324,45 @@ class TableConfigTestCase(
)
TableConfig.objects.bulk_create(table_configs)
+ cls.form_data = {
+ 'name': 'Table Config X',
+ 'object_type': site_type.pk,
+ 'table': 'SiteTable',
+ 'description': 'A table config',
+ 'weight': 100,
+ 'enabled': True,
+ 'shared': True,
+ 'columns': ['name', 'status'],
+ 'ordering': 'name',
+ }
+ cls.bulk_edit_data = {
+ 'weight': 999,
+ }
+
+ def _get_url(self, action, instance=None):
+ url = super()._get_url(action, instance)
+ # The add view requires the table context from the source table view
+ if action == 'add':
+ site_type = ObjectType.objects.get_for_model(Site)
+ url = f'{url}?object_type={site_type.pk}&table=SiteTable'
+ return url
+
+ def test_add_view_without_table_context(self):
+ """A GET without the table context params must redirect to the home page."""
+ self.add_permissions('extras.add_tableconfig')
+ response = self.client.get(reverse('extras:tableconfig_add'))
+ self.assertRedirects(response, reverse('home'))
+
+ messages_list = list(get_messages(response.wsgi_request))
+ self.assertEqual(len(messages_list), 1)
+ self.assertEqual(str(messages_list[0]), 'Table configurations must be created from an object list view.')
+
+ def test_add_view_post_without_table_context(self):
+ """A POST without the table context must return form errors rather than a server error."""
+ self.add_permissions('extras.add_tableconfig')
+ response = self.client.post(reverse('extras:tableconfig_add'), data={})
+ self.assertHttpStatus(response, 200)
+
class BookmarkTestCase(
ViewTestCases.DeleteObjectViewTestCase,
@@ -365,6 +408,9 @@ class BookmarkTestCase(
def test_list_objects_anonymous(self):
return
+ def test_export_objects_anonymous(self):
+ return
+
def test_list_objects_with_constrained_permission(self):
return
@@ -919,6 +965,9 @@ class SubscriptionTestCase(
login_url = reverse('login')
self.assertRedirects(self.client.get(url), f'{login_url}?next={url}')
+ def test_export_objects_anonymous(self):
+ return
+
def test_list_objects_with_permission(self):
return
@@ -1027,6 +1076,9 @@ class NotificationTestCase(
login_url = reverse('login')
self.assertRedirects(self.client.get(url), f'{login_url}?next={url}')
+ def test_export_objects_anonymous(self):
+ return
+
def test_list_objects_with_permission(self):
return
diff --git a/netbox/extras/views.py b/netbox/extras/views.py
index 344830e67..cec119378 100644
--- a/netbox/extras/views.py
+++ b/netbox/extras/views.py
@@ -481,6 +481,14 @@ class TableConfigEditView(SharedObjectViewMixin, generic.ObjectEditView):
form = forms.TableConfigForm
template_name = 'extras/tableconfig_edit.html'
+ def get(self, request, *args, **kwargs):
+ # The add view requires the object_type & table parameters from the source table view
+ if not kwargs and not (request.GET.get('object_type') and request.GET.get('table')):
+ messages.warning(request, _('Table configurations must be created from an object list view.'))
+ return redirect('home')
+
+ return super().get(request, *args, **kwargs)
+
def alter_object(self, obj, request, url_args, url_kwargs):
if not obj.pk:
obj.user = request.user
diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py
index 14f39903f..cdc6786f0 100644
--- a/netbox/ipam/api/views.py
+++ b/netbox/ipam/api/views.py
@@ -407,7 +407,7 @@ class AvailableIPAddressesView(AvailableObjectsView):
def get_available_objects(self, parent, limit=None):
# Calculate available IPs within the parent
ip_list = []
- for index, ip in enumerate(parent.get_available_ips(), start=1):
+ for index, ip in enumerate(parent.iter_available_ips(), start=1):
ip_list.append(ip)
if index == limit:
break
diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py
index b83c4ca30..11fa5bac0 100644
--- a/netbox/ipam/fields.py
+++ b/netbox/ipam/fields.py
@@ -42,7 +42,10 @@ class BaseIPField(models.Field):
raise ValidationError(e)
def get_prep_value(self, value):
- if not value:
+ # Membership check; `not value` incorrectly treats the valid zero addresses
+ # 0.0.0.0 and :: as empty. netaddr objects compare unequal to all three
+ # sentinels; raw int 0 stays "empty" for backward compatibility.
+ if value in (None, '', 0):
return None
if isinstance(value, list):
return [str(self.to_python(v)) for v in value]
@@ -107,6 +110,7 @@ IPAddressField.register_lookup(lookups.NetContainsOrEquals)
IPAddressField.register_lookup(lookups.NetHost)
IPAddressField.register_lookup(lookups.NetIn)
IPAddressField.register_lookup(lookups.NetHostContained)
+IPAddressField.register_lookup(lookups.NetHostBetween)
IPAddressField.register_lookup(lookups.NetFamily)
IPAddressField.register_lookup(lookups.NetMaskLength)
IPAddressField.register_lookup(lookups.Host)
diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py
index bba050215..c95e76d14 100644
--- a/netbox/ipam/filtersets.py
+++ b/netbox/ipam/filtersets.py
@@ -1142,19 +1142,17 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
def filter_interface_id(self, queryset, name, value):
if value is None:
return queryset.none()
- return queryset.filter(
- Q(interfaces_as_tagged=value) |
- Q(interfaces_as_untagged=value)
- ).distinct()
+ tagged = queryset.filter(interfaces_as_tagged=value)
+ untagged = queryset.filter(interfaces_as_untagged=value)
+ return queryset.filter(pk__in=tagged.union(untagged).values('pk'))
@extend_schema_field(OpenApiTypes.INT)
def filter_vminterface_id(self, queryset, name, value):
if value is None:
return queryset.none()
- return queryset.filter(
- Q(vminterfaces_as_tagged=value) |
- Q(vminterfaces_as_untagged=value)
- ).distinct()
+ tagged = queryset.filter(vminterfaces_as_tagged=value)
+ untagged = queryset.filter(vminterfaces_as_untagged=value)
+ return queryset.filter(pk__in=tagged.union(untagged).values('pk'))
@register_filterset
diff --git a/netbox/ipam/forms/bulk_create.py b/netbox/ipam/forms/bulk_create.py
index 763dbac9c..da6bd0fdb 100644
--- a/netbox/ipam/forms/bulk_create.py
+++ b/netbox/ipam/forms/bulk_create.py
@@ -1,10 +1,12 @@
from django import forms
from django.utils.translation import gettext_lazy as _
-from utilities.forms.fields import ExpandableIPNetworkField
+from ipam.constants import VLAN_VID_MAX, VLAN_VID_MIN
+from utilities.forms.fields import ExpandableIPNetworkField, NumericArrayField
__all__ = (
'IPNetworkBulkCreateForm',
+ 'VLANIDBulkCreateForm',
)
@@ -15,3 +17,17 @@ class IPNetworkBulkCreateForm(forms.Form):
pattern = ExpandableIPNetworkField(
label=_('Pattern')
)
+
+
+class VLANIDBulkCreateForm(forms.Form):
+ pattern = NumericArrayField(
+ base_field=forms.IntegerField(
+ min_value=VLAN_VID_MIN,
+ max_value=VLAN_VID_MAX
+ ),
+ label=_('VLAN IDs'),
+ help_text=_(
+ 'Enter VLAN IDs and ranges separated by commas. '
+ 'Example: 100,200-210,3100-3299'
+ )
+ )
diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py
index ac629c05c..4dce7baff 100644
--- a/netbox/ipam/forms/model_forms.py
+++ b/netbox/ipam/forms/model_forms.py
@@ -45,6 +45,7 @@ __all__ = (
'ServiceCreateForm',
'ServiceForm',
'ServiceTemplateForm',
+ 'VLANBulkAddForm',
'VLANForm',
'VLANGroupForm',
'VLANTranslationPolicyForm',
@@ -727,6 +728,26 @@ class VLANForm(TenancyForm, PrimaryModelForm):
]
+class VLANBulkAddForm(VLANForm):
+ """
+ Subclass of VLANForm for bulk creation.
+
+ The VID field is inherited but excluded from the visible fieldsets, as it is
+ populated programmatically by BulkCreateView from the expanded pattern.
+ """
+ fieldsets = (
+ FieldSet('group', 'site', 'name', 'status', 'role', 'description', 'tags', name=_('VLAN')),
+ FieldSet('qinq_role', 'qinq_svlan', name=_('Q-in-Q/802.1ad')),
+ FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['name'].help_text = _(
+ 'Use {vid} as a placeholder for the VLAN ID. Example: VLAN-{vid}.'
+ )
+
+
class VLANTranslationPolicyForm(PrimaryModelForm):
fieldsets = (
diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py
index f3c6637fa..7d16c0fe6 100644
--- a/netbox/ipam/graphql/filters.py
+++ b/netbox/ipam/graphql/filters.py
@@ -1,4 +1,3 @@
-from datetime import date
from typing import TYPE_CHECKING, Annotated
import netaddr
@@ -72,8 +71,8 @@ class ASNFilter(TenancyFilterMixin, PrimaryModelFilter):
@strawberry_django.filter_type(models.ASNRange, lookups=True)
class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | 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()
start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -86,10 +85,10 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
@strawberry_django.filter_type(models.Aggregate, lookups=True)
class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
- prefix: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ prefix: StrFilterLookup | 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()
+ date_added: DateFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_field()
def contains(self, value: list[str], prefix) -> Q:
@@ -122,14 +121,14 @@ class FHRPGroupFilter(PrimaryModelFilter):
group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
- auth_key: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ auth_key: StrFilterLookup | None = strawberry_django.filter_field()
ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -140,7 +139,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter):
interface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
strawberry_django.filter_field()
)
- interface_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ interface_id: StrFilterLookup | None = strawberry_django.filter_field()
group: Annotated['FHRPGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -176,7 +175,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter):
@strawberry_django.filter_type(models.IPAddress, lookups=True)
class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
- address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ address: StrFilterLookup | 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: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
@@ -197,7 +196,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter
strawberry_django.filter_field()
)
nat_outside_id: ID | None = strawberry_django.filter_field()
- dns_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ dns_name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_field()
def assigned(self, value: bool, prefix) -> Q:
@@ -227,8 +226,8 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter
@strawberry_django.filter_type(models.IPRange, lookups=True)
class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
- start_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
- end_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ start_address: StrFilterLookup | None = strawberry_django.filter_field()
+ end_address: StrFilterLookup | None = strawberry_django.filter_field()
size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -281,7 +280,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
@strawberry_django.filter_type(models.Prefix, lookups=True)
class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
- prefix: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ prefix: StrFilterLookup | 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()
vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
@@ -330,7 +329,7 @@ class RoleFilter(OrganizationalModelFilter):
@strawberry_django.filter_type(models.RouteTarget, lookups=True)
class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
importing_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -347,7 +346,7 @@ class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter):
@strawberry_django.filter_type(models.Service, lookups=True)
class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
)
@@ -359,7 +358,7 @@ class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter):
@strawberry_django.filter_type(models.ServiceTemplate, lookups=True)
class ServiceTemplateFilter(ServiceFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.VLAN, lookups=True)
@@ -373,7 +372,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilter):
vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
strawberry_django.filter_field()
)
@@ -404,7 +403,7 @@ class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter):
@strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True)
class VLANTranslationPolicyFilter(PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
@strawberry_django.filter_type(models.VLANTranslationRule, lookups=True)
@@ -413,7 +412,7 @@ class VLANTranslationRuleFilter(NetBoxModelFilter):
strawberry_django.filter_field()
)
policy_id: ID | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field()
)
@@ -424,8 +423,8 @@ class VLANTranslationRuleFilter(NetBoxModelFilter):
@strawberry_django.filter_type(models.VRF, lookups=True)
class VRFFilter(TenancyFilterMixin, PrimaryModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- rd: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ rd: StrFilterLookup | None = strawberry_django.filter_field()
enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field()
import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
strawberry_django.filter_field()
diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py
index 31b93b419..f17c9c72e 100644
--- a/netbox/ipam/graphql/types.py
+++ b/netbox/ipam/graphql/types.py
@@ -244,7 +244,7 @@ class RouteTargetType(PrimaryObjectType):
@strawberry_django.type(
models.Service,
- exclude=('parent_object_type', 'parent_object_id'),
+ exclude=('_ports_lowest', 'parent_object_type', 'parent_object_id'),
filters=ServiceFilter,
pagination=True
)
@@ -264,7 +264,7 @@ class ServiceType(ContactsMixin, PrimaryObjectType):
@strawberry_django.type(
models.ServiceTemplate,
- fields='__all__',
+ exclude=('_ports_lowest',),
filters=ServiceTemplateFilter,
pagination=True
)
diff --git a/netbox/ipam/lookups.py b/netbox/ipam/lookups.py
index 693903f3f..309917699 100644
--- a/netbox/ipam/lookups.py
+++ b/netbox/ipam/lookups.py
@@ -1,3 +1,4 @@
+import netaddr
from django.db.models import IntegerField, Lookup, Transform, lookups
@@ -99,7 +100,8 @@ class NetHost(Lookup):
if rhs_params:
rhs_params[0] = rhs_params[0].split('/')[0]
params = list(lhs_params) + rhs_params
- return f'HOST({lhs}) = {rhs}', params
+ # Cast to INET so the predicate matches the inet ipam_ipaddress_host index.
+ return f'CAST(HOST({lhs}) AS INET) = {rhs}', params
class NetIn(Lookup):
@@ -120,7 +122,8 @@ class NetIn(Lookup):
without_mask.append(address)
address_in_clause = self.create_in_clause('{} IN ('.format(lhs), len(with_mask))
- host_in_clause = self.create_in_clause('HOST({}) IN ('.format(lhs), len(without_mask))
+ # Cast to INET so the predicate matches the inet ipam_ipaddress_host index.
+ host_in_clause = self.create_in_clause('CAST(HOST({}) AS INET) IN ('.format(lhs), len(without_mask))
if with_mask and not without_mask:
return address_in_clause, with_mask
@@ -156,6 +159,34 @@ class NetHostContained(Lookup):
return f'CAST(HOST({lhs}) AS INET) <<= {rhs}', params
+class NetHostBetween(Lookup):
+ """
+ Match host addresses (mask ignored) falling inclusively between two bounds. The left-hand
+ side is kept as an inet-typed host expression so PostgreSQL can use the host expression
+ indexes on the IPAM address and range tables; the CAST(HOST(...) AS INET) spelling matches
+ NetHost/NetIn for consistency (PostgreSQL canonicalizes the INET(HOST(...)) function form
+ to the same expression).
+ """
+ lookup_name = 'host_between'
+
+ def get_prep_lookup(self):
+ if not isinstance(self.rhs, (list, tuple)) or len(self.rhs) != 2:
+ raise ValueError('The host_between lookup requires a (lower, upper) pair of bounds')
+ try:
+ # Normalize to bare hosts; reject malformed values before they reach SQL.
+ lower, upper = (netaddr.IPNetwork(str(bound)).ip for bound in self.rhs)
+ except (netaddr.AddrFormatError, ValueError) as e:
+ raise ValueError(f'Invalid host_between bound: {e}') from e
+ if lower.version != upper.version:
+ raise ValueError('host_between bounds must not mix address families')
+ return lower, upper
+
+ def as_sql(self, qn, connection):
+ lhs, lhs_params = self.process_lhs(qn, connection)
+ params = list(lhs_params) + [str(bound) for bound in self.rhs]
+ return f'CAST(HOST({lhs}) AS INET) BETWEEN %s AND %s', params
+
+
class NetFamily(Transform):
lookup_name = 'family'
function = 'FAMILY'
diff --git a/netbox/ipam/managers.py b/netbox/ipam/managers.py
index 1ef00e125..8f1eb9ff1 100644
--- a/netbox/ipam/managers.py
+++ b/netbox/ipam/managers.py
@@ -1,10 +1,10 @@
from django.db.models import Manager
from ipam.lookups import Host, Inet
-from utilities.querysets import RestrictedQuerySet
+from ipam.querysets import IPAddressQuerySet
-class IPAddressManager(Manager.from_queryset(RestrictedQuerySet)):
+class IPAddressManager(Manager.from_queryset(IPAddressQuerySet)):
def get_queryset(self):
"""
diff --git a/netbox/ipam/migrations/0089_default_ordering_indexes.py b/netbox/ipam/migrations/0089_default_ordering_indexes.py
index a718aa3fc..64ec1b020 100644
--- a/netbox/ipam/migrations/0089_default_ordering_indexes.py
+++ b/netbox/ipam/migrations/0089_default_ordering_indexes.py
@@ -32,9 +32,12 @@ class Migration(migrations.Migration):
model_name='role',
index=models.Index(fields=['weight', 'name'], name='ipam_role_weight_01396b_idx'),
),
+ # Adding a dummy index, to allow a safe migration in case updating users already have services
+ # with a large number of ports configured (see issue #22273)
+ # Will get removed in 0091_alter_service_index_and_ordering
migrations.AddIndex(
model_name='service',
- index=models.Index(fields=['protocol', 'ports', 'id'], name='ipam_servic_protoco_687d13_idx'),
+ index=models.Index(fields=['id'], name='ipam_servic_protoco_687d13_idx'),
),
migrations.AddIndex(
model_name='vlangroup',
diff --git a/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py b/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py
new file mode 100644
index 000000000..aa91e863c
--- /dev/null
+++ b/netbox/ipam/migrations/0091_alter_service_index_and_ordering.py
@@ -0,0 +1,58 @@
+from django.db import migrations, models
+
+
+def populate__ports_lowest(apps, schema_editor):
+ Service = apps.get_model('ipam', 'Service')
+ ServiceTemplate = apps.get_model('ipam', 'ServiceTemplate')
+ CHUNK_SIZE = 500
+
+ for model in (Service, ServiceTemplate):
+ chunk = []
+ qs = model.objects.filter(_ports_lowest__isnull=True).only('id', 'ports', '_ports_lowest')
+ for obj in qs.iterator(chunk_size=CHUNK_SIZE):
+ if obj.ports:
+ obj._ports_lowest = min(obj.ports)
+ chunk.append(obj)
+ if len(chunk) >= CHUNK_SIZE:
+ model.objects.bulk_update(chunk, ['_ports_lowest'])
+ chunk = []
+ if chunk:
+ model.objects.bulk_update(chunk, ['_ports_lowest'])
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ipam', '0090_vlangroup_recompute_total_vlan_ids'),
+ ]
+
+ operations = [
+ migrations.RemoveIndex(
+ model_name='service',
+ name='ipam_servic_protoco_687d13_idx',
+ ),
+ migrations.AddField(
+ model_name='service',
+ name='_ports_lowest',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='servicetemplate',
+ name='_ports_lowest',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ migrations.RunPython(populate__ports_lowest, migrations.RunPython.noop),
+ migrations.AddIndex(
+ model_name='service',
+ index=models.Index(
+ fields=['protocol', '_ports_lowest', 'id'],
+ name='ipam_servic_protoco_e2901d_idx'
+ ),
+ ),
+ migrations.AlterModelOptions(
+ name='service',
+ options={
+ 'ordering': ('protocol', '_ports_lowest', 'id')
+ },
+ ),
+ ]
diff --git a/netbox/ipam/migrations/0092_iprange_host_indexes.py b/netbox/ipam/migrations/0092_iprange_host_indexes.py
new file mode 100644
index 000000000..76065bd27
--- /dev/null
+++ b/netbox/ipam/migrations/0092_iprange_host_indexes.py
@@ -0,0 +1,34 @@
+import django.db.models.functions.comparison
+from django.db import migrations, models
+
+import ipam.fields
+import ipam.lookups
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('ipam', '0091_alter_service_index_and_ordering'),
+ ]
+
+ operations = [
+ migrations.AddIndex(
+ model_name='iprange',
+ index=models.Index(
+ django.db.models.functions.comparison.Cast(
+ ipam.lookups.Host('start_address'),
+ output_field=ipam.fields.IPAddressField(),
+ ),
+ name='ipam_iprange_start_host',
+ ),
+ ),
+ migrations.AddIndex(
+ model_name='iprange',
+ index=models.Index(
+ django.db.models.functions.comparison.Cast(
+ ipam.lookups.Host('end_address'),
+ output_field=ipam.fields.IPAddressField(),
+ ),
+ name='ipam_iprange_end_host',
+ ),
+ ),
+ ]
diff --git a/netbox/ipam/migrations/0091_denormalization_triggers.py b/netbox/ipam/migrations/0093_denormalization_triggers.py
similarity index 89%
rename from netbox/ipam/migrations/0091_denormalization_triggers.py
rename to netbox/ipam/migrations/0093_denormalization_triggers.py
index e92b2a41c..902f7f294 100644
--- a/netbox/ipam/migrations/0091_denormalization_triggers.py
+++ b/netbox/ipam/migrations/0093_denormalization_triggers.py
@@ -10,7 +10,7 @@ from utilities.migration import cached_scope_triggers
class Migration(migrations.Migration):
dependencies = [
- ('ipam', '0090_vlangroup_recompute_total_vlan_ids'),
+ ('ipam', '0092_iprange_host_indexes'),
# Source tables (dcim_site, dcim_location) must already exist.
('dcim', '0238_ltree_paths'),
]
diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py
index cd4554257..51941526e 100644
--- a/netbox/ipam/models/ip.py
+++ b/netbox/ipam/models/ip.py
@@ -15,7 +15,7 @@ from ipam.constants import *
from ipam.fields import IPAddressField, IPNetworkField
from ipam.lookups import Host
from ipam.managers import IPAddressManager
-from ipam.querysets import PrefixQuerySet
+from ipam.querysets import IPRangeQuerySet, PrefixQuerySet
from ipam.validators import DNSValidator
from netbox.config import get_config
from netbox.models import OrganizationalModel, PrimaryModel
@@ -425,14 +425,63 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
+ @property
+ def usable_ip_bounds(self):
+ """
+ Return the first and last IPs considered usable for available-IP calculations.
+
+ Pools and IPv4 /31-/32 / IPv6 /127-/128 are fully usable; otherwise IPv4 excludes
+ network and broadcast, IPv6 excludes the subnet-router anycast address.
+ """
+ network = netaddr.IPNetwork(self.prefix)
+ family = network.version
+ first = network.first
+ last = network.last
+ mask_length = network.prefixlen
+
+ if (
+ self.is_pool
+ or (family == 4 and mask_length >= 31)
+ or (family == 6 and mask_length >= 127)
+ ):
+ return (
+ netaddr.IPAddress(first, version=family),
+ netaddr.IPAddress(last, version=family),
+ )
+
+ if family == 4:
+ return (
+ netaddr.IPAddress(first + 1, version=family),
+ netaddr.IPAddress(last - 1, version=family),
+ )
+
+ return (
+ netaddr.IPAddress(first + 1, version=family),
+ netaddr.IPAddress(last, version=family),
+ )
+
+ @property
+ def usable_size(self):
+ """
+ The number of usable host addresses within the prefix (excludes reserved addresses).
+ """
+ first_ip, last_ip = self.usable_ip_bounds
+ return int(last_ip) - int(first_ip) + 1
+
def get_child_ranges(self, **kwargs):
"""
Return all IPRanges within this Prefix and VRF.
"""
+ # A host BETWEEN over the prefix span uses the ipam_iprange_*_host btree indexes.
+ prefix = netaddr.IPNetwork(self.prefix)
+ bounds = (
+ netaddr.IPAddress(prefix.first, version=prefix.version),
+ netaddr.IPAddress(prefix.last, version=prefix.version),
+ )
return IPRange.objects.filter(
vrf=self.vrf,
- start_address__net_host_contained=str(self.prefix),
- end_address__net_host_contained=str(self.prefix),
+ start_address__host_between=bounds,
+ end_address__host_between=bounds,
**kwargs
)
@@ -441,52 +490,106 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
child IPAddresses belonging to any VRF.
"""
+ # A host BETWEEN over the prefix span is index-sargable without the <<= containment recheck.
+ prefix = netaddr.IPNetwork(self.prefix)
+ bounds = (
+ netaddr.IPAddress(prefix.first, version=prefix.version),
+ netaddr.IPAddress(prefix.last, version=prefix.version),
+ )
if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
- return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
- return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
+ return IPAddress.objects.filter(address__host_between=bounds)
+ return IPAddress.objects.filter(address__host_between=bounds, vrf=self.vrf)
def get_available_ips(self):
"""
Return all available IPs within this prefix as an IPSet.
"""
- prefix = netaddr.IPSet(self.prefix)
- child_ips = netaddr.IPSet([
- ip.address.ip for ip in self.get_child_ips()
- ])
- child_ranges = netaddr.IPSet([
- iprange.range for iprange in self.get_child_ranges().filter(mark_populated=True)
- ])
- available_ips = prefix - child_ips - child_ranges
+ return netaddr.IPSet(
+ cidr
+ for start, end in self._available_intervals()
+ for cidr in netaddr.iprange_to_cidrs(start, end)
+ )
- # Pool, IPv4 /31-/32 or IPv6 /127-/128 sets are fully usable
- if (
- self.is_pool
- or (self.family == 4 and self.prefix.prefixlen >= 31)
- or (self.family == 6 and self.prefix.prefixlen >= 127)
- ):
- return available_ips
+ def iter_available_ips(self):
+ """
+ Yield the available IPs within this prefix as netaddr.IPAddress objects, in
+ ascending order. Unlike get_available_ips(), consumption is lazy: stopping
+ early stops reading from the database.
+ """
+ for start, end in self._available_intervals():
+ yield from netaddr.iter_iprange(start, end)
- if self.family == 4:
- # For "normal" IPv4 prefixes, omit first and last addresses
- available_ips -= netaddr.IPSet([
- netaddr.IPAddress(self.prefix.first),
- netaddr.IPAddress(self.prefix.last),
- ])
- else:
- # For IPv6 prefixes, omit the Subnet-Router anycast address
- # per RFC 4291
- available_ips -= netaddr.IPSet([netaddr.IPAddress(self.prefix.first)])
+ def get_available_ip_count(self):
+ """
+ Return the number of available IPs within the prefix.
+ """
+ first_ip, last_ip = self.usable_ip_bounds
+ usable_size = int(last_ip) - int(first_ip) + 1
- return available_ips
+ populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip)
+ populated_count = sum(int(end) - int(start) + 1 for start, end in populated_intervals)
+
+ # Populated ranges already cover the usable span; skip the child-IP count entirely.
+ if populated_count >= usable_size:
+ return 0
+
+ child_ip_count = (
+ self.get_child_ips()
+ .filter(address__host_between=(first_ip, last_ip))
+ .count_distinct_hosts(exclude_intervals=populated_intervals)
+ )
+
+ return max(usable_size - populated_count - child_ip_count, 0)
+
+ def get_ip_usage_summary(self):
+ """
+ Return the available IP count and utilization together as a dict, sharing a
+ single distinct-host scan. Intended for detail views rendering both values;
+ list views should call get_utilization() alone, which is cheaper per row.
+ """
+ # Marked-utilized and container utilization need no host scan; delegate.
+ if self.mark_utilized or self.status == PrefixStatusChoices.STATUS_CONTAINER:
+ return {
+ 'available_ip_count': self.get_available_ip_count(),
+ 'utilization': self.get_utilization(),
+ }
+
+ first_ip, last_ip = self.usable_ip_bounds
+ usable_size = int(last_ip) - int(first_ip) + 1
+
+ populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip)
+ utilized_intervals = self.get_child_ranges(mark_utilized=True).get_intervals()
+
+ counts = self.get_child_ips().count_distinct_hosts_pair(
+ bounds=(first_ip, last_ip),
+ bounded_exclude=populated_intervals,
+ total_exclude=utilized_intervals,
+ )
+
+ populated_count = sum(int(end) - int(start) + 1 for start, end in populated_intervals)
+ utilized_range_count = sum(int(end) - int(start) + 1 for start, end in utilized_intervals)
+
+ prefix_size = self._get_utilization_denominator()
+
+ return {
+ 'available_ip_count': max(usable_size - populated_count - counts['bounded'], 0),
+ 'utilization': min(float(utilized_range_count + counts['total']) / prefix_size * 100, 100),
+ }
def get_first_available_ip(self):
"""
Return the first available IP within the prefix (or None).
"""
- available_ips = self.get_available_ips()
- if not available_ips:
+ first_ip, last_ip = self.usable_ip_bounds
+ populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip)
+
+ first_available_ip = self.get_child_ips().first_available_host(
+ first_ip, last_ip, exclude_intervals=populated_intervals,
+ )
+
+ if first_available_ip is None:
return None
- return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
+ return f'{first_available_ip}/{self.prefix.prefixlen}'
def get_utilization(self):
"""
@@ -504,20 +607,43 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
utilization = float(child_prefixes.size) / self.prefix.size * 100
else:
- # Compile an IPSet to avoid counting duplicate IPs
- child_ips = netaddr.IPSet()
- for iprange in self.get_child_ranges().filter(mark_utilized=True):
- child_ips.add(iprange.range)
- for ip in self.get_child_ips():
- child_ips.add(ip.address.ip)
+ prefix_size = self._get_utilization_denominator()
+ utilized_intervals = self.get_child_ranges(mark_utilized=True).get_intervals()
+ utilized_range_count = sum(int(end) - int(start) + 1 for start, end in utilized_intervals)
- prefix_size = self.prefix.size
- if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
- prefix_size -= 2
- utilization = float(child_ips.size) / prefix_size * 100
+ # Utilized ranges already saturate the prefix; skip the child-IP count.
+ if utilized_range_count >= prefix_size:
+ return 100
+
+ child_ip_count = self.get_child_ips().count_distinct_hosts(
+ exclude_intervals=utilized_intervals,
+ )
+
+ utilization = float(utilized_range_count + child_ip_count) / prefix_size * 100
return min(utilization, 100)
+ def _available_intervals(self):
+ """
+ Yield the available (start, end) host intervals within the prefix.
+ """
+ first_ip, last_ip = self.usable_ip_bounds
+ populated_intervals = self.get_child_ranges(mark_populated=True).get_intervals(first_ip, last_ip)
+
+ return self.get_child_ips().available_intervals(
+ first_ip, last_ip, exclude_intervals=populated_intervals,
+ )
+
+ def _get_utilization_denominator(self):
+ """
+ The address count utilization is measured against (IPv4 non-pool prefixes
+ exclude the network and broadcast addresses; IPv6 uses the full prefix size).
+ """
+ prefix_size = self.prefix.size
+ if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
+ return prefix_size - 2
+ return prefix_size
+
class IPRange(ContactsMixin, PrimaryModel):
"""
@@ -576,12 +702,24 @@ class IPRange(ContactsMixin, PrimaryModel):
help_text=_("Report space as fully utilized")
)
+ objects = IPRangeQuerySet.as_manager()
+
clone_fields = (
'vrf', 'tenant', 'status', 'role', 'description', 'mark_populated', 'mark_utilized',
)
class Meta:
ordering = (F('vrf').asc(nulls_first=True), 'start_address', 'pk') # (vrf, start_address) may be non-unique
+ indexes = (
+ models.Index(
+ Cast(Host('start_address'), output_field=IPAddressField()),
+ name='ipam_iprange_start_host',
+ ),
+ models.Index(
+ Cast(Host('end_address'), output_field=IPAddressField()),
+ name='ipam_iprange_end_host',
+ ),
+ )
verbose_name = _('IP range')
verbose_name_plural = _('IP ranges')
@@ -709,40 +847,14 @@ class IPRange(ContactsMixin, PrimaryModel):
def get_status_color(self):
return IPRangeStatusChoices.colors.get(self.status)
- def get_child_ips(self):
- """
- Return all IPAddresses within this IPRange and VRF.
- """
- return IPAddress.objects.filter(
- address__gte=self.start_address,
- address__lte=self.end_address,
- vrf=self.vrf
- )
-
- def get_available_ips(self):
- """
- Return all available IPs within this range as an IPSet.
- """
- if self.mark_populated:
- return netaddr.IPSet()
-
- range = netaddr.IPRange(self.start_address.ip, self.end_address.ip)
- child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])
-
- return netaddr.IPSet(range) - child_ips
-
@cached_property
def first_available_ip(self):
"""
Return the first available IP within the range (or None).
"""
- available_ips = self.get_available_ips()
- if not available_ips:
- return None
+ return self.get_first_available_ip()
- return '{}/{}'.format(next(available_ips.__iter__()), self.start_address.prefixlen)
-
- @cached_property
+ @property
def utilization(self):
"""
Determine the utilization of the range and return it as a percentage.
@@ -750,12 +862,79 @@ class IPRange(ContactsMixin, PrimaryModel):
if self.mark_utilized:
return 100
- # Compile an IPSet to avoid counting duplicate IPs
- child_count = netaddr.IPSet([
- ip.address.ip for ip in self.get_child_ips()
- ]).size
+ return min(float(self._occupied_host_count) / self.size * 100, 100)
- return min(float(child_count) / self.size * 100, 100)
+ def get_child_ips(self):
+ """
+ Return all IPAddresses within this IPRange and VRF.
+ """
+ return IPAddress.objects.filter(
+ vrf=self.vrf,
+ address__host_between=(self.start_address.ip, self.end_address.ip),
+ )
+
+ def get_available_ips(self):
+ """
+ Return all available IPs within this range as an IPSet.
+ """
+ return netaddr.IPSet(
+ cidr
+ for start, end in self._available_intervals()
+ for cidr in netaddr.iprange_to_cidrs(start, end)
+ )
+
+ def iter_available_ips(self):
+ """
+ Yield the available IPs within this range as netaddr.IPAddress objects, in
+ ascending order. Unlike get_available_ips(), consumption is lazy: stopping
+ early stops reading from the database.
+ """
+ for start, end in self._available_intervals():
+ yield from netaddr.iter_iprange(start, end)
+
+ def get_available_ip_count(self):
+ """
+ Return the number of available IPs within the range.
+ """
+ if self.mark_populated:
+ return 0
+
+ return max(self.size - self._occupied_host_count, 0)
+
+ def get_first_available_ip(self):
+ """
+ Return the first available IP within the range (or None).
+ """
+ if self.mark_populated:
+ return None
+
+ first_available_ip = self.get_child_ips().first_available_host(
+ self.start_address.ip, self.end_address.ip,
+ )
+
+ if first_available_ip is None:
+ return None
+
+ return f'{first_available_ip}/{self.start_address.prefixlen}'
+
+ def _available_intervals(self):
+ """
+ Yield the available (start, end) host intervals within the range.
+ """
+ if self.mark_populated:
+ return iter(())
+
+ return self.get_child_ips().available_intervals(
+ self.start_address.ip, self.end_address.ip,
+ )
+
+ @cached_property
+ def _occupied_host_count(self):
+ """
+ The number of distinct occupied hosts within the range, cached for the
+ lifetime of the instance.
+ """
+ return self.get_child_ips().count_distinct_hosts()
class IPAddress(ContactsMixin, PrimaryModel):
@@ -948,10 +1127,10 @@ class IPAddress(ContactsMixin, PrimaryModel):
# Disallow the creation of IPAddresses within an IPRange with mark_populated=True
parent_range_qs = IPRange.objects.filter(
- start_address__lte=self.address,
- end_address__gte=self.address,
+ start_address__host__inet__lte=self.address.ip,
+ end_address__host__inet__gte=self.address.ip,
vrf=self.vrf,
- mark_populated=True
+ mark_populated=True,
)
if not self.pk and (parent_range := parent_range_qs.first()):
raise ValidationError({
diff --git a/netbox/ipam/models/services.py b/netbox/ipam/models/services.py
index d934465c3..ee12e3b04 100644
--- a/netbox/ipam/models/services.py
+++ b/netbox/ipam/models/services.py
@@ -31,10 +31,22 @@ class ServiceBase(models.Model):
),
verbose_name=_('port numbers')
)
+ _ports_lowest = models.PositiveIntegerField(
+ null=True,
+ blank=True,
+ )
class Meta:
abstract = True
+ def save(self, *args, **kwargs):
+ # On saving find the smallest port and save for default ordering
+ self._ports_lowest = min(self.ports) if self.ports else None
+ update_fields = kwargs.get('update_fields')
+ if update_fields is not None and '_ports_lowest' not in update_fields:
+ kwargs['update_fields'] = list(update_fields) + ['_ports_lowest']
+ super().save(*args, **kwargs)
+
def __str__(self):
return f'{self.name} ({self.get_protocol_display()}/{self.port_list})'
@@ -74,7 +86,6 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel):
ct_field='parent_object_type',
fk_field='parent_object_id'
)
-
name = models.CharField(
max_length=100,
verbose_name=_('name')
@@ -93,9 +104,9 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel):
class Meta:
indexes = (
- models.Index(fields=('protocol', 'ports', 'id')), # Default ordering
+ models.Index(fields=('protocol', '_ports_lowest', 'id')), # Default ordering
models.Index(fields=('parent_object_type', 'parent_object_id')),
)
- ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique
+ ordering = ('protocol', '_ports_lowest', 'id')
verbose_name = _('application service')
verbose_name_plural = _('application services')
diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py
index 2ed3512df..40f56f87f 100644
--- a/netbox/ipam/models/vlans.py
+++ b/netbox/ipam/models/vlans.py
@@ -330,7 +330,7 @@ class VLAN(PrimaryModel):
)
# Check that the VLAN ID is permitted in the assigned group (if any)
- if self.group:
+ if self.group and self.vid is not None:
if not any([self.vid in r for r in self.group.vid_ranges]):
raise ValidationError({
'vid': _(
diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py
index 12bd8328c..4a48ee81d 100644
--- a/netbox/ipam/querysets.py
+++ b/netbox/ipam/querysets.py
@@ -1,18 +1,51 @@
+import heapq
+
+import netaddr
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, F, OuterRef, Q, Subquery, Value
from django.db.models.expressions import RawSQL
-from django.db.models.functions import NullIf, Round
+from django.db.models.functions import Cast, NullIf, Round
from utilities.query import count_related
from utilities.querysets import RestrictedQuerySet
+from .fields import IPAddressField
+from .lookups import Host
+
__all__ = (
'ASNRangeQuerySet',
+ 'IPAddressQuerySet',
+ 'IPRangeQuerySet',
'PrefixQuerySet',
'VLANGroupQuerySet',
'VLANQuerySet',
)
+# The host portion of an IP address (mask ignored), in the same form as the
+# ipam_ipaddress_host expression index.
+HOST_ADDRESS = Cast(Host('address'), output_field=IPAddressField())
+
+
+def _merge_intervals(intervals):
+ """
+ Return the union of (start, end) netaddr.IPAddress intervals, merged and sorted.
+ """
+ if not intervals:
+ return []
+
+ intervals = sorted(intervals)
+ merged = [intervals[0]]
+
+ for start, end in intervals[1:]:
+ current_start, current_end = merged[-1]
+ # Adjacency math in int space; netaddr raises at the address-space maximum.
+ if start.version == current_end.version and int(start) <= int(current_end) + 1:
+ merged[-1] = (current_start, max(current_end, end))
+ else:
+ merged.append((start, end))
+
+ return merged
+
class ASNRangeQuerySet(RestrictedQuerySet):
@@ -32,6 +65,162 @@ class ASNRangeQuerySet(RestrictedQuerySet):
return self.annotate(asn_count=Subquery(asns))
+class IPAddressQuerySet(RestrictedQuerySet):
+
+ def count_distinct_hosts(self, exclude_intervals=()):
+ """
+ Count distinct host addresses, optionally excluding (start, end) netaddr.IPAddress intervals.
+ """
+ queryset = self
+ for start, end in exclude_intervals:
+ queryset = queryset.exclude(address__host_between=(start, end))
+
+ return queryset.aggregate(count=Count(HOST_ADDRESS, distinct=True))['count']
+
+ def count_distinct_hosts_pair(self, bounds, bounded_exclude=(), total_exclude=()):
+ """
+ Return two distinct host counts computed in a single scan, as a dict:
+ 'bounded' counts hosts within the (first_ip, last_ip) bounds excluding the
+ bounded_exclude intervals; 'total' counts all hosts excluding the
+ total_exclude intervals. Interval arguments match the output of
+ IPRangeQuerySet.get_intervals(). Avoids a second scan of the host expression
+ index when both counts are needed. Use only when both counts are needed (e.g.
+ Prefix.get_ip_usage_summary()); single-purpose callers should prefer
+ count_distinct_hosts().
+ """
+ # The deduplicated column is already a bare host; plain comparisons beat
+ # the host_between lookup here, which would re-wrap it in HOST()::inet.
+ bounded_q = Q(host_address__range=(str(bounds[0]), str(bounds[1])))
+ for start, end in bounded_exclude:
+ bounded_q &= ~Q(host_address__range=(str(start), str(end)))
+ total_q = Q()
+ for start, end in total_exclude:
+ total_q &= ~Q(host_address__range=(str(start), str(end)))
+
+ hosts = self.order_by().annotate(host_address=HOST_ADDRESS).values('host_address').distinct()
+ return hosts.aggregate(
+ bounded=Count('host_address', filter=bounded_q),
+ # An empty Q is falsy; fall back to a plain count of all hosts.
+ total=Count('host_address', filter=total_q or None),
+ )
+
+ def _iter_distinct_hosts(self, first_ip, last_ip, batch_size):
+ """
+ Yield the distinct occupied hosts in [first_ip, last_ip] in ascending order,
+ fetched in LIMIT batches that resume just past the last seen host. (A
+ server-side cursor is unsuitable here: on autocommit connections Django
+ declares it WITH HOLD, which materializes the full result at DECLARE.)
+ """
+ resume = first_ip
+ while True:
+ # order_by() first clears the default ordering, which would otherwise
+ # leak into SELECT and break distinct().
+ hosts = list(
+ self.filter(address__host_between=(resume, last_ip))
+ .order_by()
+ .annotate(host_address=HOST_ADDRESS)
+ .values_list('host_address', flat=True)
+ .distinct()
+ .order_by('host_address')[:batch_size]
+ )
+ for host in hosts:
+ yield host.ip
+ if len(hosts) < batch_size:
+ return
+ last_host = hosts[-1].ip
+ if int(last_host) >= int(last_ip):
+ return
+ resume = netaddr.IPAddress(int(last_host) + 1, version=last_host.version)
+
+ def available_intervals(self, first_ip, last_ip, exclude_intervals=(), batch_size=5000):
+ """
+ Yield the unoccupied (start, end) netaddr.IPAddress intervals (inclusive)
+ within [first_ip, last_ip], in ascending order. exclude_intervals are
+ (start, end) netaddr.IPAddress pairs; they are merged and sorted internally,
+ intervals of a foreign address family are ignored, and addresses they cover
+ count as occupied. Consumption is lazy: a caller that stops early stops
+ fetching host batches.
+ """
+ if batch_size < 1:
+ raise ValueError('batch_size must be greater than zero')
+
+ first_int, last_int = int(first_ip), int(last_ip)
+ version = first_ip.version
+
+ if first_int > last_int:
+ return
+ # Normalize: the sweep below requires sorted, non-overlapping, same-family intervals.
+ exclude_intervals = _merge_intervals([
+ (start, end)
+ for start, end in exclude_intervals
+ if start.version == end.version == version
+ ])
+ intervals = [(int(start), int(end)) for start, end in exclude_intervals]
+
+ # Fast path: one merged excluded interval covers the entire span.
+ if intervals and intervals[0][0] <= first_int and intervals[0][1] >= last_int:
+ return
+
+ hosts = (
+ (int(host), int(host))
+ for host in self._iter_distinct_hosts(first_ip, last_ip, batch_size)
+ )
+
+ candidate = first_int
+ # Ties on `start` are harmless; the sweep handles overlapping intervals.
+ for start, end in heapq.merge(intervals, hosts):
+ if end < candidate:
+ continue
+ if start > candidate:
+ yield (
+ netaddr.IPAddress(candidate, version=version),
+ netaddr.IPAddress(min(start - 1, last_int), version=version),
+ )
+ candidate = max(candidate, end + 1)
+ if candidate > last_int:
+ return
+
+ if candidate <= last_int:
+ yield (
+ netaddr.IPAddress(candidate, version=version),
+ netaddr.IPAddress(last_int, version=version),
+ )
+
+ def first_available_host(self, first_ip, last_ip, exclude_intervals=()):
+ """
+ Return the first host in [first_ip, last_ip] neither present nor in an excluded interval (or None).
+ """
+ interval = next(self.available_intervals(first_ip, last_ip, exclude_intervals), None)
+ return interval[0] if interval else None
+
+
+class IPRangeQuerySet(RestrictedQuerySet):
+
+ def get_intervals(self, first_ip=None, last_ip=None):
+ """
+ Return ranges as merged (start, end) netaddr.IPAddress intervals, optionally clipped to the bounds.
+ """
+ intervals = []
+
+ # order_by() clears the default ordering; _merge_intervals() sorts anyway.
+ for start_address, end_address in self.order_by().values_list('start_address', 'end_address'):
+ start, end = start_address.ip, end_address.ip
+
+ if first_ip is not None:
+ if end < first_ip:
+ continue
+ start = max(start, first_ip)
+
+ if last_ip is not None:
+ if start > last_ip:
+ continue
+ end = min(end, last_ip)
+
+ intervals.append((start, end))
+
+ return _merge_intervals(intervals)
+
+
class PrefixQuerySet(RestrictedQuerySet):
def annotate_hierarchy(self):
diff --git a/netbox/ipam/tests/test_fields.py b/netbox/ipam/tests/test_fields.py
new file mode 100644
index 000000000..b7bcd3b28
--- /dev/null
+++ b/netbox/ipam/tests/test_fields.py
@@ -0,0 +1,29 @@
+from django.test import TestCase
+from netaddr import IPAddress
+
+from ipam.fields import IPAddressField, IPNetworkField
+
+
+class BaseIPFieldTestCase(TestCase):
+ """
+ Regression coverage for BaseIPField.get_prep_value() — zero addresses such as
+ 0.0.0.0 and :: are valid hosts and must not be treated as empty values.
+ """
+
+ def test_get_prep_value_accepts_ipv4_zero_address(self):
+ # Regression: 0.0.0.0 is a valid host, not an empty value.
+ self.assertEqual(IPAddressField().get_prep_value(IPAddress('0.0.0.0')), '0.0.0.0')
+
+ def test_get_prep_value_accepts_ipv6_zero_address(self):
+ # Regression: :: is a valid host, not an empty value.
+ self.assertEqual(IPAddressField().get_prep_value(IPAddress('::')), '::')
+
+ def test_get_prep_value_passes_through_empty(self):
+ self.assertIsNone(IPNetworkField().get_prep_value(None))
+ self.assertIsNone(IPAddressField().get_prep_value(''))
+
+ def test_get_prep_value_preserves_raw_zero_as_empty(self):
+ # Raw int 0 is preserved as the legacy "empty" sentinel; Django's ORM never
+ # passes it directly, but the previous `not value` check returned None for it.
+ self.assertIsNone(IPAddressField().get_prep_value(0))
+ self.assertIsNone(IPNetworkField().get_prep_value(0))
diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py
index 900436d62..4e396a9f8 100644
--- a/netbox/ipam/tests/test_filtersets.py
+++ b/netbox/ipam/tests/test_filtersets.py
@@ -4,7 +4,7 @@ from django.test import TestCase
from netaddr import IPNetwork
from circuits.models import Provider
-from dcim.choices import InterfaceTypeChoices
+from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
from ipam.choices import *
from ipam.filtersets import *
@@ -2206,11 +2206,50 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'interface_id': interface_id}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ # An interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths)
+ vlans = self.queryset.all()[:2]
+ interface = Interface.objects.create(
+ device=Device.objects.first(),
+ name='Interface X',
+ type=InterfaceTypeChoices.TYPE_1GE_FIXED,
+ mode=InterfaceModeChoices.MODE_TAGGED,
+ untagged_vlan=vlans[0],
+ )
+ interface.tagged_vlans.add(vlans[1])
+ params = {'interface_id': interface.pk}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication)
+ interface.tagged_vlans.add(vlans[0])
+ params = {'interface_id': interface.pk}
+ qs = self.filterset(params, self.queryset).qs
+ self.assertEqual(qs.count(), 2)
+ self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True))))
+
def test_vminterface(self):
vminterface_id = VMInterface.objects.first().pk
params = {'vminterface_id': vminterface_id}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ # A VM interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths)
+ vlans = self.queryset.all()[:2]
+ vminterface = VMInterface.objects.create(
+ virtual_machine=VirtualMachine.objects.first(),
+ name='VM Interface X',
+ mode=InterfaceModeChoices.MODE_TAGGED,
+ untagged_vlan=vlans[0],
+ )
+ vminterface.tagged_vlans.add(vlans[1])
+ params = {'vminterface_id': vminterface.pk}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication)
+ vminterface.tagged_vlans.add(vlans[0])
+ params = {'vminterface_id': vminterface.pk}
+ qs = self.filterset(params, self.queryset).qs
+ self.assertEqual(qs.count(), 2)
+ self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True))))
+
def test_qinq_role(self):
params = {'qinq_role': [VLANQinQRoleChoices.ROLE_SERVICE, VLANQinQRoleChoices.ROLE_CUSTOMER]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
diff --git a/netbox/ipam/tests/test_forms.py b/netbox/ipam/tests/test_forms.py
index 621afc6a8..33bdfb627 100644
--- a/netbox/ipam/tests/test_forms.py
+++ b/netbox/ipam/tests/test_forms.py
@@ -3,7 +3,7 @@ from django.test import TestCase
from dcim.constants import InterfaceTypeChoices
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Region, Site, SiteGroup
-from ipam.forms import PrefixForm
+from ipam.forms import PrefixForm, VLANIDBulkCreateForm
from ipam.forms.bulk_import import IPAddressImportForm
@@ -96,3 +96,31 @@ class IPAddressImportFormTestCase(TestCase):
self.device.refresh_from_db()
self.assertEqual(self.device.oob_ip, ip1, "OOB IP was incorrectly cleared by a row with is_oob=False")
+
+
+class VLANFormTestCase(TestCase):
+
+ def test_bulk_create_valid_patterns(self):
+ """Single values, ranges, and combinations expand to sorted, deduplicated VLAN IDs."""
+ cases = (
+ ('100', [100]),
+ ('5,10,20', [5, 10, 20]),
+ ('10-20', list(range(10, 21))),
+ ('1,10-20,300-305', [1, *range(10, 21), *range(300, 306)]),
+ (' 5 , 7 - 9 ', [5, 7, 8, 9]),
+ ('5,5,4-6', [4, 5, 6]),
+ )
+ for pattern, expected in cases:
+ with self.subTest(pattern=pattern):
+ form = VLANIDBulkCreateForm({'pattern': pattern})
+ self.assertTrue(form.is_valid(), form.errors)
+ self.assertEqual(form.cleaned_data['pattern'], expected)
+
+ def test_bulk_create_invalid_patterns(self):
+ """Malformed, descending, or out-of-range patterns are rejected with an error on the pattern field."""
+ cases = ('', 'abc', '10,abc', '20-10', '10-', '5,', '-5', '0', '4095')
+ for pattern in cases:
+ with self.subTest(pattern=pattern):
+ form = VLANIDBulkCreateForm({'pattern': pattern})
+ self.assertFalse(form.is_valid())
+ self.assertIn('pattern', form.errors)
diff --git a/netbox/ipam/tests/test_lookups.py b/netbox/ipam/tests/test_lookups.py
index c23c910cf..2280073ac 100644
--- a/netbox/ipam/tests/test_lookups.py
+++ b/netbox/ipam/tests/test_lookups.py
@@ -1,7 +1,9 @@
+import netaddr
from django.db.backends.postgresql.psycopg_any import NumericRange
from django.test import TestCase
+from netaddr import IPNetwork
-from ipam.models import VLANGroup
+from ipam.models import IPAddress, VLANGroup
class VLANGroupRangeContainsLookupTestCase(TestCase):
@@ -65,3 +67,134 @@ class VLANGroupRangeContainsLookupTestCase(TestCase):
specific condition.
"""
self.assertFalse(VLANGroup.objects.filter(pk=self.g_empty.pk, vid_ranges__range_contains=1).exists())
+
+
+class IPAddressHostBetweenLookupTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.0/24')),
+ IPAddress(address=IPNetwork('192.0.2.1/24')),
+ IPAddress(address=IPNetwork('192.0.2.5/32')),
+ IPAddress(address=IPNetwork('192.0.2.10/25')),
+ IPAddress(address=IPNetwork('192.0.2.11/24')),
+ IPAddress(address=IPNetwork('2001:db8::1/64')),
+ IPAddress(address=IPNetwork('2001:db8::5/128')),
+ IPAddress(address=IPNetwork('2001:db8::10/64')),
+ ))
+
+ def test_ipv4_boundaries_inclusive(self):
+ """
+ Tests that both bounds are included and hosts outside the window are excluded.
+ """
+ queryset = IPAddress.objects.filter(
+ address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'))
+ )
+ self.assertEqual(
+ sorted(str(ip.address) for ip in queryset),
+ ['192.0.2.1/24', '192.0.2.10/25', '192.0.2.5/32'],
+ )
+
+ def test_mask_insensitive(self):
+ """
+ Tests that hosts match regardless of their mask length.
+ """
+ queryset = IPAddress.objects.filter(
+ address__host_between=(netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('192.0.2.5'))
+ )
+ self.assertEqual(queryset.count(), 1)
+
+ def test_ipv6(self):
+ """
+ Tests that IPv6 hosts filter by host portion.
+ """
+ queryset = IPAddress.objects.filter(
+ address__host_between=(netaddr.IPAddress('2001:db8::1'), netaddr.IPAddress('2001:db8::5'))
+ )
+ self.assertEqual(queryset.count(), 2)
+
+ def test_bounds_mask_stripped(self):
+ """
+ Tests that bounds supplied with a mask compare by host portion only.
+ """
+ queryset = IPAddress.objects.filter(
+ address__host_between=(IPNetwork('192.0.2.1/24'), IPNetwork('192.0.2.10/24'))
+ )
+ self.assertEqual(queryset.count(), 3)
+
+ def test_invalid_bounds_raise(self):
+ """
+ Tests that a bounds value which is not a two-item pair raises ValueError.
+ """
+ with self.assertRaises(ValueError):
+ IPAddress.objects.filter(address__host_between=(netaddr.IPAddress('192.0.2.1'),))
+
+ def test_invalid_bound_value_raises(self):
+ """
+ Tests that a bound which is not a valid IP address raises ValueError.
+ """
+ with self.assertRaises(ValueError):
+ IPAddress.objects.filter(address__host_between=('invalid', netaddr.IPAddress('192.0.2.10')))
+
+ def test_mixed_family_bounds_raise(self):
+ """
+ Tests that bounds from different address families raise ValueError.
+ """
+ with self.assertRaises(ValueError):
+ IPAddress.objects.filter(
+ address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('2001:db8::1'))
+ )
+
+ def test_sql_uses_cast_host_expression(self):
+ """
+ Tests that the compiled SQL matches the ipam_ipaddress_host index expression.
+ """
+ queryset = IPAddress.objects.filter(
+ address__host_between=(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'))
+ )
+ self.assertIn('CAST(HOST(', str(queryset.query))
+
+
+class IPAddressNetLookupsTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ IPAddress.objects.bulk_create((
+ IPAddress(address='10.0.0.1/24'),
+ IPAddress(address='10.0.0.2/24'),
+ IPAddress(address='10.0.0.1/25'), # Same host as the first, different mask
+ IPAddress(address='2001:db8::1/64'),
+ ))
+
+ def test_net_host_matches_host_ignoring_mask(self):
+ """net_host matches every address whose host portion equals the value."""
+ qs = IPAddress.objects.filter(address__net_host='10.0.0.1')
+ self.assertEqual(qs.count(), 2)
+
+ def test_net_host_predicate_is_inet_typed(self):
+ """net_host casts the host expression to inet so the inet host index applies."""
+ sql = str(IPAddress.objects.filter(address__net_host='10.0.0.1').query)
+ self.assertIn('CAST(HOST(', sql)
+ self.assertIn('AS INET) =', sql)
+
+ def test_net_in_without_mask(self):
+ """net_in matches host values supplied without a mask."""
+ qs = IPAddress.objects.filter(address__net_in=['10.0.0.1', '10.0.0.2'])
+ self.assertEqual(qs.count(), 3)
+
+ def test_net_in_with_mask(self):
+ """net_in matches an exact address/mask value."""
+ qs = IPAddress.objects.filter(address__net_in=['10.0.0.1/25'])
+ self.assertEqual(qs.count(), 1)
+
+ def test_net_in_normalizes_ipv6(self):
+ """net_in matches an expanded IPv6 form against the canonical host value."""
+ qs = IPAddress.objects.filter(
+ address__net_in=['2001:0db8:0000:0000:0000:0000:0000:0001']
+ )
+ self.assertEqual(qs.count(), 1)
+
+ def test_net_in_predicate_is_inet_typed(self):
+ """net_in casts the host expression to inet so the inet host index applies."""
+ sql = str(IPAddress.objects.filter(address__net_in=['10.0.0.1']).query)
+ self.assertIn('CAST(HOST(', sql)
+ self.assertIn('AS INET) IN', sql)
diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py
index e5637433a..b76a7205b 100644
--- a/netbox/ipam/tests/test_models.py
+++ b/netbox/ipam/tests/test_models.py
@@ -1,3 +1,4 @@
+import netaddr
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db.backends.postgresql.psycopg_any import NumericRange
@@ -6,8 +7,11 @@ from netaddr import IPNetwork, IPSet
from dcim.models import Site, SiteGroup
from ipam.choices import *
+from ipam.constants import SERVICE_PORT_MAX, SERVICE_PORT_MIN
from ipam.models import *
+from ipam.utils import rebuild_prefixes
from utilities.data import string_to_ranges
+from virtualization.models import VirtualMachine
class AggregateTestCase(TestCase):
@@ -113,7 +117,7 @@ class IPRangeTestCase(TestCase):
self.assertEqual(iprange.size, 1)
self.assertEqual(str(iprange), '192.0.2.10-192.0.2.10/24')
- self.assertEqual(iprange.first_available_ip, '192.0.2.10/24')
+ self.assertEqual(iprange.get_first_available_ip(), '192.0.2.10/24')
def test_first_available_ip_consumed_single_address_range(self):
iprange = IPRange.objects.create(
@@ -123,7 +127,7 @@ class IPRangeTestCase(TestCase):
IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'))
# The sole address in the range is now assigned, so no IPs remain available.
- self.assertIsNone(iprange.first_available_ip)
+ self.assertIsNone(iprange.get_first_available_ip())
def test_single_address_range_ipv6(self):
# IPRange.name has IPv4/IPv6-specific formatting; exercise the IPv6 branch
@@ -138,7 +142,7 @@ class IPRangeTestCase(TestCase):
self.assertEqual(iprange.size, 1)
self.assertEqual(str(iprange), '2001:db8::10-2001:db8::10/64')
- self.assertEqual(iprange.first_available_ip, '2001:db8::10/64')
+ self.assertEqual(iprange.get_first_available_ip(), '2001:db8::10/64')
def test_reversed_range(self):
iprange = IPRange(
@@ -165,9 +169,207 @@ class IPRangeTestCase(TestCase):
with self.assertRaisesMessage(ValidationError, 'Defined addresses overlap'):
iprange.clean()
+ def test_get_child_ips_host_portion(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('10.0.0.2/24'),
+ end_address=IPNetwork('10.0.0.254/24'),
+ )
+
+ ip1 = IPAddress.objects.create(address=IPNetwork('10.0.0.2/32'))
+ ip2 = IPAddress.objects.create(address=IPNetwork('10.0.0.3/24'))
+
+ self.assertEqual(set(iprange.get_child_ips()), {ip1, ip2})
+
+ def test_get_available_ips(self):
+ """
+ Tests that occupied hosts are deduplicated and excluded from the available set.
+ """
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.13/24'),
+ )
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/32')),
+ ))
+
+ self.assertEqual(iprange.get_available_ips(), IPSet(['192.0.2.11/32', '192.0.2.12/31']))
+
+ def test_get_available_ips_mark_populated(self):
+ """
+ Tests that a populated range reports no available IPs.
+ """
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.13/24'),
+ mark_populated=True,
+ )
+
+ self.assertEqual(iprange.get_available_ips(), IPSet())
+
+ def test_get_available_ips_vrf(self):
+ """
+ Tests that IPs in other VRFs do not consume range space.
+ """
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.11/24'),
+ vrf=vrf1,
+ )
+ IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'), vrf=vrf2)
+
+ self.assertEqual(iprange.get_available_ips(), IPSet(['192.0.2.10/31']))
+
+ def test_iter_available_ips(self):
+ """
+ Tests that iter_available_ips() yields the same addresses as get_available_ips() in order.
+ """
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.13/24'),
+ )
+ IPAddress.objects.create(address=IPNetwork('192.0.2.11/24'))
+
+ self.assertEqual(list(iprange.iter_available_ips()), sorted(iprange.get_available_ips()))
+
+ def test_available_ip_count(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.12/24'))
+
+ self.assertEqual(iprange.get_available_ip_count(), 9)
+
+ def test_available_ip_count_distinct_hosts(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ )
+
+ # Two rows for .10 (different masks) must dedupe to a single occupied host.
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/32')),
+ IPAddress(address=IPNetwork('192.0.2.11/24')),
+ ))
+
+ self.assertEqual(iprange.get_available_ip_count(), 8)
+
+ def test_available_ip_count_vrf(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ vrf=vrf1,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.12/24'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.13/24'), vrf=vrf2)
+
+ # Only the VRF 1 IP should count.
+ self.assertEqual(iprange.get_available_ip_count(), 9)
+
+ def test_available_ip_count_populated(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_populated=True,
+ )
+
+ self.assertEqual(iprange.get_available_ip_count(), 0)
+
+ def test_first_available_ip_full(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.11/24'),
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'))
+ IPAddress.objects.create(address=IPNetwork('192.0.2.11/24'))
+
+ self.assertIsNone(iprange.get_first_available_ip())
+
+ def test_first_available_ip_populated(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_populated=True,
+ )
+
+ self.assertIsNone(iprange.get_first_available_ip())
+
+ def test_first_available_ip_ipv6(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('::/126'),
+ end_address=IPNetwork('::3/126'),
+ )
+
+ self.assertEqual(iprange.get_first_available_ip(), '::/126')
+
+ def test_utilization_distinct_hosts(self):
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/32')),
+ IPAddress(address=IPNetwork('192.0.2.11/24')),
+ ))
+
+ # Two distinct hosts in a 10-address range.
+ self.assertEqual(iprange.utilization, 2 / 10 * 100)
+
+ def test_utilization_vrf(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ vrf=vrf1,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.12/24'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.13/24'), vrf=vrf2)
+
+ # Only the VRF 1 IP counts toward utilization.
+ self.assertEqual(iprange.utilization, 1 / 10 * 100)
+
+ def test_utilization_duplicate_ips_vrf(self):
+ """
+ Tests that identical IPs in a non-unique VRF count once toward range utilization.
+ """
+ vrf = VRF.objects.create(name='VRF 1', enforce_unique=False)
+ iprange = IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ vrf=vrf,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.12/24'), vrf=vrf),
+ IPAddress(address=IPNetwork('192.0.2.12/24'), vrf=vrf),
+ ))
+
+ self.assertEqual(iprange.utilization, 1 / 10 * 100)
+
class PrefixTestCase(TestCase):
+ def assertAvailableIPCountMatchesIPSet(self, prefix):
+ """
+ Confirm that get_available_ip_count() matches get_available_ips().size for the supplied prefix.
+ """
+ self.assertEqual(prefix.get_available_ip_count(), prefix.get_available_ips().size)
+
def test_family_string(self):
# Test property when prefix is a string
prefix = Prefix(prefix='10.0.0.0/8')
@@ -251,6 +453,23 @@ class PrefixTestCase(TestCase):
self.assertEqual(child_ranges[0], ranges[2])
self.assertEqual(child_ranges[1], ranges[3])
+ def test_get_child_ranges_other_family(self):
+ """
+ Tests that ranges of a different address family are not returned.
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.16/28'))
+ IPRange.objects.bulk_create((
+ IPRange(
+ start_address=IPNetwork('192.168.0.18/28'), end_address=IPNetwork('192.168.0.20/28'), size=3
+ ),
+ IPRange(start_address=IPNetwork('::1/64'), end_address=IPNetwork('::2/64'), size=2),
+ ))
+
+ child_ranges = prefix.get_child_ranges()
+
+ self.assertEqual(len(child_ranges), 1)
+ self.assertEqual(child_ranges[0].start_address, IPNetwork('192.168.0.18/28'))
+
def test_get_child_ips(self):
vrfs = VRF.objects.bulk_create((
VRF(name='VRF 1'),
@@ -330,6 +549,319 @@ class PrefixTestCase(TestCase):
self.assertEqual(available_ips, missing_ips)
+ def test_iter_available_ips(self):
+ """
+ Tests that iter_available_ips() yields the same addresses as get_available_ips() in order.
+ """
+ parent_prefix = Prefix.objects.create(prefix=IPNetwork('10.0.0.0/28'))
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('10.0.0.1/28')),
+ IPAddress(address=IPNetwork('10.0.0.5/28')),
+ ))
+ IPRange.objects.create(
+ start_address=IPNetwork('10.0.0.8/28'),
+ end_address=IPNetwork('10.0.0.9/28'),
+ mark_populated=True,
+ )
+
+ available_ips = list(parent_prefix.iter_available_ips())
+
+ self.assertEqual(available_ips, sorted(parent_prefix.get_available_ips()))
+ self.assertEqual(available_ips[0], netaddr.IPAddress('10.0.0.2'))
+ self.assertEqual(available_ips[-1], netaddr.IPAddress('10.0.0.14'))
+
+ def test_get_available_ips_ipv6(self):
+ """
+ Tests that the subnet-router anycast address is excluded and the last address included.
+ """
+ parent_prefix = Prefix.objects.create(prefix=IPNetwork('2001:db8::/126'))
+ IPAddress.objects.create(address=IPNetwork('2001:db8::1/126'))
+
+ self.assertEqual(parent_prefix.get_available_ips(), IPSet(['2001:db8::2/127']))
+
+ def test_get_available_ips_pool(self):
+ """
+ Tests that pool prefixes include the network and broadcast addresses.
+ """
+ parent_prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), is_pool=True)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/30'))
+
+ self.assertEqual(parent_prefix.get_available_ips(), IPSet(['192.0.2.0/32', '192.0.2.2/31']))
+
+ def test_available_ip_count_distinct_hosts(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/29')),
+ IPAddress(address=IPNetwork('192.0.2.1/32')),
+ IPAddress(address=IPNetwork('192.0.2.3/29')),
+ ))
+
+ # Usable hosts in /29: 6. Two unique hosts occupy .1 and .3.
+ self.assertEqual(prefix.get_available_ip_count(), 4)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_populated_ranges(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/29')),
+ IPAddress(address=IPNetwork('192.0.2.3/29')), # Inside the populated range; not double-counted.
+ ))
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.3/29'),
+ end_address=IPNetwork('192.0.2.4/29'),
+ mark_populated=True,
+ )
+
+ # Usable 6, one IP outside the range at .1, populated range covers .3-.4.
+ # Available: .2, .5, .6.
+ self.assertEqual(prefix.get_available_ip_count(), 3)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv4_pool(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/30'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ is_pool=True,
+ )
+
+ self.assertEqual(prefix.get_available_ip_count(), 4)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv4_non_pool(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/30'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ is_pool=False,
+ )
+
+ self.assertEqual(prefix.get_available_ip_count(), 2)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv4_non_pool_ignores_unusable_ips(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/30'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # Network and broadcast addresses are unusable for non-pool IPv4 prefixes;
+ # an IP assigned to either must not reduce the available count.
+ IPAddress.objects.create(address=IPNetwork('192.0.2.0/30'))
+ IPAddress.objects.create(address=IPNetwork('192.0.2.3/30'))
+
+ self.assertEqual(prefix.get_available_ip_count(), 2)
+ self.assertEqual(prefix.get_first_available_ip(), '192.0.2.1/30')
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv6(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('2001:db8::/126'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # /126 has 4 addresses; normal IPv6 prefix excludes the first.
+ self.assertEqual(prefix.get_available_ip_count(), 3)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv6_ignores_subnet_router_anycast(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('2001:db8::/126'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # The subnet-router anycast (::) address is unusable for normal IPv6 prefixes;
+ # an IP assigned there must not reduce the available count.
+ IPAddress.objects.create(address=IPNetwork('2001:db8::/126'))
+
+ self.assertEqual(prefix.get_available_ip_count(), 3)
+ self.assertEqual(prefix.get_first_available_ip(), '2001:db8::1/126')
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv6_127(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('2001:db8::/127'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ self.assertEqual(prefix.get_available_ip_count(), 2)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_ipv6_populated_range(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('2001:db8::/126'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('2001:db8::1/126'),
+ end_address=IPNetwork('2001:db8::2/126'),
+ mark_populated=True,
+ )
+
+ # Usable IPv6 hosts in /126: ::1, ::2, ::3. Populated: ::1-::2.
+ self.assertEqual(prefix.get_available_ip_count(), 1)
+ self.assertEqual(prefix.get_first_available_ip(), '2001:db8::3/126')
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_overlapping_ranges(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.1/29'),
+ end_address=IPNetwork('192.0.2.3/29'),
+ mark_populated=True,
+ )
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.2/29'),
+ end_address=IPNetwork('192.0.2.4/29'),
+ mark_populated=True,
+ )
+
+ # Usable hosts: .1-.6 => 6. Populated union: .1-.4 => 4. Available: .5-.6 => 2.
+ self.assertEqual(prefix.get_available_ip_count(), 2)
+ self.assertEqual(prefix.get_first_available_ip(), '192.0.2.5/29')
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_vrf(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ vrf=vrf1,
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/29'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.2/29'), vrf=vrf2)
+
+ # Usable .1-.6 => 6. Only the VRF 1 IP should count.
+ self.assertEqual(prefix.get_available_ip_count(), 5)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_vrf_ranges(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ vrf=vrf1,
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # Covers every usable host, but in a different VRF.
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.1/29'),
+ end_address=IPNetwork('192.0.2.6/29'),
+ vrf=vrf2,
+ mark_populated=True,
+ )
+
+ self.assertEqual(prefix.get_available_ip_count(), 6)
+ self.assertEqual(prefix.get_first_available_ip(), '192.0.2.1/29')
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_fully_populated(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/30'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # Populated range covers every usable address (.1-.2 in a non-pool /30).
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.1/30'),
+ end_address=IPNetwork('192.0.2.2/30'),
+ mark_populated=True,
+ )
+
+ # Exercises the early-return paths that skip the child-IP count and
+ # the host-stream iterator entirely.
+ self.assertEqual(prefix.get_available_ip_count(), 0)
+ self.assertIsNone(prefix.get_first_available_ip())
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_query_count(self):
+ """
+ Tests that the count runs one interval query plus exactly one host scan.
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+
+ IPAddress.objects.bulk_create(
+ IPAddress(address=IPNetwork(f'192.0.2.{i}/24')) for i in range(1, 11)
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.20/24'),
+ end_address=IPNetwork('192.0.2.29/24'),
+ mark_populated=True,
+ )
+
+ with self.assertNumQueries(2):
+ prefix.get_available_ip_count()
+
+ def test_available_ip_count_container(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_CONTAINER,
+ )
+
+ # A child prefix exists but does not reduce the available IP count.
+ Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/26'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ self.assertEqual(prefix.get_available_ip_count(), 254)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_container_vrf_duplicate_hosts(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_CONTAINER,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf2)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.2/24'), vrf=vrf2)
+
+ # A global container counts child IPs from all VRFs; the duplicate host
+ # counts once. 254 usable - 2 distinct hosts.
+ self.assertEqual(prefix.get_available_ip_count(), 252)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ def test_available_ip_count_container_vrf_ip_in_populated_range(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_CONTAINER,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_populated=True,
+ )
+ IPAddress.objects.create(address=IPNetwork('192.0.2.15/24'), vrf=vrf1)
+
+ # The range covers 10 hosts; the VRF 1 IP inside it is not counted again.
+ self.assertEqual(prefix.get_available_ip_count(), 244)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
def test_get_first_available_prefix(self):
prefixes = Prefix.objects.bulk_create((
@@ -368,6 +900,42 @@ class PrefixTestCase(TestCase):
parent_prefix = Prefix.objects.create(prefix=IPNetwork('2001:db8:500:5::/127'))
self.assertEqual(parent_prefix.get_first_available_ip(), '2001:db8:500:5::/127')
+ def test_get_first_available_ip_ipv6_zero_address(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('::/126'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # Normal IPv6 prefixes exclude the subnet-router anycast address ::.
+ self.assertEqual(prefix.get_first_available_ip(), '::1/126')
+
+ def test_get_first_available_ip_populated_ranges(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/29'))
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.2/29'),
+ end_address=IPNetwork('192.0.2.3/29'),
+ mark_populated=True,
+ )
+
+ self.assertEqual(prefix.get_first_available_ip(), '192.0.2.4/29')
+
+ def test_get_first_available_ip_full(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/30'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/30'))
+ IPAddress.objects.create(address=IPNetwork('192.0.2.2/30'))
+
+ self.assertIsNone(prefix.get_first_available_ip())
+
def test_get_utilization_container(self):
prefixes = (
Prefix(prefix=IPNetwork('10.0.0.0/24'), status=PrefixStatusChoices.STATUS_CONTAINER),
@@ -397,6 +965,276 @@ class PrefixTestCase(TestCase):
)
self.assertEqual(prefix.get_utilization(), 64 / 254 * 100) # ~25% utilization
+ def test_get_utilization_distinct_hosts(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/32')),
+ IPAddress(address=IPNetwork('192.0.2.11/24')),
+ ))
+
+ # Two unique occupied hosts over 254 usable IPv4 addresses.
+ self.assertEqual(prefix.get_utilization(), 2 / 254 * 100)
+
+ @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
+ def test_get_utilization_duplicate_ips_global(self):
+ """
+ Tests that identical global IPs permitted by disabled uniqueness count as one host.
+ """
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'))
+ duplicate_ip = IPAddress(address=IPNetwork('192.0.2.10/24'))
+ self.assertIsNone(duplicate_ip.clean())
+ duplicate_ip.save()
+
+ self.assertEqual(prefix.get_utilization(), 1 / 254 * 100)
+
+ def test_get_utilization_duplicate_ips_vrf(self):
+ """
+ Tests that identical IPs in a non-unique VRF count as one host.
+ """
+ vrf = VRF.objects.create(name='VRF 1', enforce_unique=False)
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ vrf=vrf,
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.10/24'), vrf=vrf)
+ duplicate_ip = IPAddress(address=IPNetwork('192.0.2.10/24'), vrf=vrf)
+ self.assertIsNone(duplicate_ip.clean())
+ duplicate_ip.save()
+
+ self.assertEqual(prefix.get_utilization(), 1 / 254 * 100)
+
+ def test_available_ip_count_duplicate_ips_vrf(self):
+ """
+ Tests that identical IPs in a non-unique VRF reduce availability once.
+ """
+ vrf = VRF.objects.create(name='VRF 1', enforce_unique=False)
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/29'),
+ vrf=vrf,
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/29'), vrf=vrf),
+ IPAddress(address=IPNetwork('192.0.2.1/29'), vrf=vrf),
+ ))
+
+ # Usable hosts in /29: 6. The duplicate occupies a single host.
+ self.assertEqual(prefix.get_available_ip_count(), 5)
+ self.assertAvailableIPCountMatchesIPSet(prefix)
+
+ @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
+ def test_get_ip_usage_summary_duplicate_ips_global(self):
+ """
+ Tests that the usage summary deduplicates identical global IPs in both values.
+ """
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ ))
+
+ summary = prefix.get_ip_usage_summary()
+
+ self.assertEqual(summary['available_ip_count'], 253)
+ self.assertEqual(summary['utilization'], 1 / 254 * 100)
+
+ def test_get_utilization_utilized_ranges(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_utilized=True,
+ )
+
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/24')),
+ IPAddress(address=IPNetwork('192.0.2.10/24')),
+ IPAddress(address=IPNetwork('192.0.2.11/24')),
+ IPAddress(address=IPNetwork('192.0.2.20/24')),
+ ))
+
+ # Utilized range contributes 10 hosts; IPs inside the range are not double-counted.
+ # Outside IPs: .1 and .20 => 2 more.
+ self.assertEqual(prefix.get_utilization(), 12 / 254 * 100)
+
+ def test_get_utilization_overlapping_utilized_ranges(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_utilized=True,
+ )
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.15/24'),
+ end_address=IPNetwork('192.0.2.24/24'),
+ mark_utilized=True,
+ )
+
+ # Union is .10-.24 => 15 hosts, not 20.
+ self.assertEqual(prefix.get_utilization(), 15 / 254 * 100)
+
+ def test_get_utilization_fully_utilized_range(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ # Utilized range covers every usable host (.1-.254 in a non-pool /24).
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.1/24'),
+ end_address=IPNetwork('192.0.2.254/24'),
+ mark_utilized=True,
+ )
+
+ # Exercises the early-return path that skips the child-IP count entirely.
+ self.assertEqual(prefix.get_utilization(), 100)
+
+ def test_get_utilization_ipv6_utilized_range(self):
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('2001:db8::/126'),
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPRange.objects.create(
+ start_address=IPNetwork('2001:db8::1/126'),
+ end_address=IPNetwork('2001:db8::2/126'),
+ mark_utilized=True,
+ )
+
+ self.assertEqual(prefix.get_utilization(), 2 / 4 * 100)
+
+ def test_get_utilization_vrf(self):
+ vrf1 = VRF.objects.create(name='VRF 1')
+ vrf2 = VRF.objects.create(name='VRF 2')
+
+ prefix = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ vrf=vrf1,
+ status=PrefixStatusChoices.STATUS_ACTIVE,
+ )
+
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.15/24'), vrf=vrf1)
+ IPAddress.objects.create(address=IPNetwork('192.0.2.2/24'), vrf=vrf2)
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ vrf=vrf2,
+ mark_utilized=True,
+ )
+
+ # VRF 2 objects are ignored entirely; the VRF 1 IP at .15 still counts even
+ # though it falls inside the VRF 2 range's host span (exclusion intervals are
+ # built only from same-VRF ranges).
+ self.assertEqual(prefix.get_utilization(), 2 / 254 * 100)
+
+ def test_get_utilization_query_count(self):
+ """
+ Tests that utilization for a non-container prefix uses two queries.
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+
+ with self.assertNumQueries(2):
+ prefix.get_utilization()
+
+ def test_get_ip_usage_summary(self):
+ """
+ Tests that the combined summary matches the independent methods.
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/24')),
+ IPAddress(address=IPNetwork('192.0.2.2/24')),
+ ))
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.10/24'),
+ end_address=IPNetwork('192.0.2.19/24'),
+ mark_utilized=True,
+ )
+ IPRange.objects.create(
+ start_address=IPNetwork('192.0.2.30/24'),
+ end_address=IPNetwork('192.0.2.39/24'),
+ mark_populated=True,
+ )
+
+ summary = prefix.get_ip_usage_summary()
+
+ self.assertEqual(summary['available_ip_count'], prefix.get_available_ip_count())
+ self.assertEqual(summary['utilization'], prefix.get_utilization())
+
+ def test_get_ip_usage_summary_query_count(self):
+ """
+ Tests that the combined summary uses a single distinct-host scan (three queries).
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+
+ with self.assertNumQueries(3):
+ prefix.get_ip_usage_summary()
+
+ def test_get_ip_usage_summary_container(self):
+ """
+ Tests that the summary delegates to the independent methods for containers.
+ """
+ container = Prefix.objects.create(
+ prefix=IPNetwork('192.0.2.0/24'),
+ status=PrefixStatusChoices.STATUS_CONTAINER,
+ )
+
+ summary = container.get_ip_usage_summary()
+
+ self.assertEqual(summary['available_ip_count'], container.get_available_ip_count())
+ self.assertEqual(summary['utilization'], container.get_utilization())
+
+ def test_get_ip_usage_summary_mark_utilized(self):
+ """
+ Tests that a marked-utilized prefix reports 100% utilization in the summary.
+ """
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), mark_utilized=True)
+
+ summary = prefix.get_ip_usage_summary()
+
+ self.assertEqual(summary['utilization'], 100)
+ self.assertEqual(summary['available_ip_count'], prefix.get_available_ip_count())
+
+ def test_usable_size(self):
+ self.assertEqual(Prefix(prefix=IPNetwork('192.0.2.0/24')).usable_size, 254)
+ self.assertEqual(Prefix(prefix=IPNetwork('192.0.2.0/24'), is_pool=True).usable_size, 256)
+ self.assertEqual(Prefix(prefix=IPNetwork('2001:db8::/126')).usable_size, 3)
+
+ def test_usable_ip_bounds_string_prefix(self):
+ """
+ Tests that usable bounds are computed for a string-assigned prefix.
+ """
+ first_ip, last_ip = Prefix(prefix='192.0.2.0/24').usable_ip_bounds
+
+ self.assertEqual(first_ip, netaddr.IPAddress('192.0.2.1'))
+ self.assertEqual(last_ip, netaddr.IPAddress('192.0.2.254'))
+
#
# Uniqueness enforcement tests
#
@@ -622,6 +1460,35 @@ class PrefixHierarchyTestCase(TestCase):
self.assertEqual(prefixes[3]._depth, 2)
self.assertEqual(prefixes[3]._children, 0)
+ def test_rebuild_prefixes_accepts_vrf_identifier(self):
+ # None means "global table". Wipe the precomputed hierarchy so the rebuild is observable.
+ Prefix.objects.update(_depth=0, _children=0)
+
+ rebuild_prefixes(None)
+
+ top = Prefix.objects.get(prefix='10.0.0.0/8')
+ mid = Prefix.objects.get(prefix='10.0.0.0/16')
+ leaf = Prefix.objects.get(prefix='10.0.0.0/24')
+ self.assertEqual((top._depth, top._children), (0, 2))
+ self.assertEqual((mid._depth, mid._children), (1, 1))
+ self.assertEqual((leaf._depth, leaf._children), (2, 0))
+
+ def test_rebuild_prefixes_accepts_vrf_pk(self):
+ # A VRF pk filters to that VRF's prefixes.
+ vrf = VRF.objects.create(name='VRF 1')
+ Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vrf=vrf)
+ Prefix.objects.create(prefix=IPNetwork('192.0.2.0/25'), vrf=vrf)
+
+ # Reset depth/children so the rebuild has something to restore.
+ Prefix.objects.filter(vrf=vrf).update(_depth=0, _children=0)
+
+ rebuild_prefixes(vrf.pk)
+
+ parent = Prefix.objects.get(prefix='192.0.2.0/24', vrf=vrf)
+ child = Prefix.objects.get(prefix='192.0.2.0/25', vrf=vrf)
+ self.assertEqual((parent._depth, parent._children), (0, 1))
+ self.assertEqual((child._depth, child._children), (1, 0))
+
class IPAddressTestCase(TestCase):
@@ -717,6 +1584,20 @@ class IPAddressTestCase(TestCase):
with self.assertRaisesMessage(ValidationError, 'Cannot create IP address'):
ipaddress.clean()
+ def test_populated_range_blocks_ip_with_different_mask(self):
+ # The populated-range check compares by host portion, so a different mask
+ # must not let an IPAddress slip past validation.
+ IPRange.objects.create(
+ start_address=IPNetwork('10.0.0.2/24'),
+ end_address=IPNetwork('10.0.0.254/24'),
+ mark_populated=True,
+ )
+
+ ip = IPAddress(address=IPNetwork('10.0.0.2/32'))
+
+ with self.assertRaises(ValidationError):
+ ip.full_clean()
+
class VLANGroupTestCase(TestCase):
@@ -926,3 +1807,119 @@ class VLANTestCase(TestCase):
vlan.group = vlangroups[2]
with self.assertRaises(ValidationError):
vlan.full_clean()
+
+ def test_vlan_group_vid_validation_with_null_vid(self):
+ """A missing VID on a grouped VLAN raises a ValidationError, not a TypeError."""
+ group = VLANGroup.objects.create(name='VLAN Group 1', slug='vlan-group-1')
+ vlan = VLAN(name='VLAN X', vid=None, group=group)
+ with self.assertRaises(ValidationError):
+ vlan.full_clean()
+
+
+class PrefixGetChildIPsTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.prefix = Prefix.objects.create(prefix='10.0.0.0/24')
+ IPAddress.objects.bulk_create((
+ IPAddress(address='10.0.0.0/24'), # Network address (inside containment)
+ IPAddress(address='10.0.0.1/24'),
+ IPAddress(address='10.0.0.255/24'), # Broadcast address (inside containment)
+ IPAddress(address='10.0.1.1/24'), # Outside the prefix
+ ))
+
+ def test_get_child_ips_matches_net_host_contained(self):
+ """get_child_ips returns the same IPs as the net_host_contained containment lookup."""
+ expected = set(
+ IPAddress.objects.filter(
+ address__net_host_contained=str(self.prefix.prefix), vrf=None
+ ).values_list('pk', flat=True)
+ )
+ actual = set(self.prefix.get_child_ips().values_list('pk', flat=True))
+ self.assertEqual(actual, expected)
+ self.assertEqual(len(actual), 3)
+
+ def test_get_child_ips_sql_avoids_containment_recheck(self):
+ """get_child_ips filters on an inet host range, not the <<= containment operator."""
+ sql = str(self.prefix.get_child_ips().query)
+ self.assertNotIn('<<=', sql)
+
+ def test_get_child_ips_container_in_global_table_spans_vrfs(self):
+ """A container prefix in the global table returns child IPs from any VRF."""
+ vrf = VRF.objects.create(name='VRF 1')
+ container = Prefix.objects.create(
+ prefix='10.1.0.0/24', status=PrefixStatusChoices.STATUS_CONTAINER,
+ )
+ in_vrf = IPAddress.objects.create(address='10.1.0.5/24', vrf=vrf)
+ in_global = IPAddress.objects.create(address='10.1.0.6/24')
+ child_pks = set(container.get_child_ips().values_list('pk', flat=True))
+ self.assertEqual(child_pks, {in_vrf.pk, in_global.pk})
+
+
+class ServiceTemplateTestCase(TestCase):
+
+ def test_servicetemplate_lowest_port(self):
+ """
+ Test lowest port setting for servicetemplate
+ """
+ template = ServiceTemplate(
+ name='Template 1',
+ protocol=ServiceProtocolChoices.PROTOCOL_TCP,
+ ports=[80, 443, 22, 8080], # small test list
+ )
+ template.full_clean()
+ template.save()
+ self.assertEqual(template._ports_lowest, 22)
+
+ def test_servicetemplate_single_port(self):
+ """
+ Test with a single port
+ """
+ template = ServiceTemplate(
+ name='Template 2',
+ protocol=ServiceProtocolChoices.PROTOCOL_UDP,
+ ports=[53],
+ )
+ template.full_clean()
+ template.save()
+ self.assertEqual(template._ports_lowest, 53)
+
+ def test_servicetemplate_empty_ports(self):
+ """
+ Test with empty ports list
+ """
+ template = ServiceTemplate(
+ name='Template 3',
+ protocol=ServiceProtocolChoices.PROTOCOL_TCP,
+ ports=[],
+ )
+ self.assertRaises(ValidationError, template.full_clean)
+
+
+class ServiceTestCase(TestCase):
+
+ @classmethod
+ def setUpTestData(cls):
+ site = Site.objects.create(
+ name='Site 1',
+ slug='site-1',
+ )
+ VirtualMachine.objects.create(
+ name='virtual machine 1',
+ site=site,
+ )
+
+ def test_large_service(self):
+ """
+ Test creation of service with large number of ports.
+ Related to issue #22273
+ """
+ service = Service(
+ name='Service 1',
+ protocol=ServiceProtocolChoices.PROTOCOL_TCP,
+ ports=list(range(SERVICE_PORT_MIN, SERVICE_PORT_MAX)),
+ parent=VirtualMachine.objects.first(),
+ )
+ service.full_clean()
+ # Testing .save() is the important part, to check for database problems
+ service.save()
+ self.assertEqual(service._ports_lowest, SERVICE_PORT_MIN)
diff --git a/netbox/ipam/tests/test_querysets.py b/netbox/ipam/tests/test_querysets.py
new file mode 100644
index 000000000..feb8ff2cd
--- /dev/null
+++ b/netbox/ipam/tests/test_querysets.py
@@ -0,0 +1,344 @@
+import netaddr
+from django.test import TestCase
+from netaddr import IPNetwork
+
+from ipam.models import IPAddress, IPRange
+
+
+class IPAddressQuerySetTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.1/24')),
+ IPAddress(address=IPNetwork('192.0.2.1/32')),
+ IPAddress(address=IPNetwork('192.0.2.2/24')),
+ ))
+
+ def test_count_distinct_hosts(self):
+ """
+ Tests that duplicate hosts with different masks are counted once.
+ """
+ self.assertEqual(IPAddress.objects.count_distinct_hosts(), 2)
+
+ def test_count_distinct_hosts_empty(self):
+ """
+ Tests that an empty queryset counts zero hosts.
+ """
+ self.assertEqual(IPAddress.objects.none().count_distinct_hosts(), 0)
+
+ def test_count_distinct_hosts_exclude_intervals(self):
+ """
+ Tests that hosts covered by an excluded interval are not counted.
+ """
+ interval = (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.1'))
+ self.assertEqual(IPAddress.objects.count_distinct_hosts(exclude_intervals=[interval]), 1)
+
+ def test_count_distinct_hosts_pair(self):
+ """
+ Tests that the bounded and total distinct host counts are computed correctly.
+ """
+ counts = IPAddress.objects.count_distinct_hosts_pair(
+ bounds=(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.10')),
+ bounded_exclude=[(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.2'))],
+ total_exclude=[(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.1'))],
+ )
+ self.assertEqual(counts, {'bounded': 0, 'total': 1})
+
+ def test_count_distinct_hosts_pair_no_excludes(self):
+ """
+ Tests that both counts dedupe hosts and respect the bounds without excludes.
+ """
+ counts = IPAddress.objects.count_distinct_hosts_pair(
+ bounds=(netaddr.IPAddress('192.0.2.2'), netaddr.IPAddress('192.0.2.10')),
+ )
+ self.assertEqual(counts, {'bounded': 1, 'total': 2})
+
+ def test_first_available_host(self):
+ """
+ Tests that occupied hosts and excluded intervals are skipped, including hosts behind the sweep.
+ """
+ interval = (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.5'))
+ self.assertEqual(
+ IPAddress.objects.first_available_host(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval]
+ ),
+ netaddr.IPAddress('192.0.2.6'),
+ )
+
+ def test_first_available_host_inverted_bounds(self):
+ """
+ Tests that an inverted bounds pair yields None.
+ """
+ self.assertIsNone(
+ IPAddress.objects.first_available_host(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.5'))
+ )
+
+ def test_available_intervals(self):
+ """
+ Tests that gaps around occupied hosts and excluded intervals are yielded in order.
+ """
+ interval = (netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('192.0.2.6'))
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval]
+ )),
+ [
+ (netaddr.IPAddress('192.0.2.3'), netaddr.IPAddress('192.0.2.4')),
+ (netaddr.IPAddress('192.0.2.7'), netaddr.IPAddress('192.0.2.10')),
+ ],
+ )
+
+ def test_available_intervals_leading_gap(self):
+ """
+ Tests that the gap before the first occupied host is yielded.
+ """
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.2')
+ )),
+ [(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.0'))],
+ )
+
+ def test_available_intervals_empty_queryset(self):
+ """
+ Tests that an empty queryset yields the full span.
+ """
+ self.assertEqual(
+ list(IPAddress.objects.none().available_intervals(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.3')
+ )),
+ [(netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.3'))],
+ )
+
+ def test_available_intervals_inverted_bounds(self):
+ """
+ Tests that an inverted bounds pair yields nothing.
+ """
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.5')
+ )),
+ [],
+ )
+
+ def test_available_intervals_fully_excluded(self):
+ """
+ Tests that a span covered by an excluded interval yields nothing.
+ """
+ interval = (netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.20'))
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval]
+ )),
+ [],
+ )
+
+ def test_available_intervals_mixed_family_exclude(self):
+ """
+ Tests that an exclude interval spanning address families is ignored.
+ """
+ interval = (netaddr.IPAddress('192.0.2.5'), netaddr.IPAddress('2001:db8::5'))
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), exclude_intervals=[interval]
+ )),
+ [(netaddr.IPAddress('192.0.2.3'), netaddr.IPAddress('192.0.2.10'))],
+ )
+
+ def test_available_intervals_invalid_batch_size(self):
+ """
+ Tests that a non-positive batch size raises ValueError.
+ """
+ intervals = IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10'), batch_size=0
+ )
+ with self.assertRaises(ValueError):
+ next(intervals)
+
+ def test_available_intervals_first_interval_single_query(self):
+ """
+ Tests that consuming only the first interval issues a single batch query.
+ """
+ IPAddress.objects.bulk_create((
+ IPAddress(address=IPNetwork('192.0.2.12/24')),
+ IPAddress(address=IPNetwork('192.0.2.14/24')),
+ IPAddress(address=IPNetwork('192.0.2.16/24')),
+ ))
+
+ intervals = IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.20'), batch_size=1
+ )
+
+ with self.assertNumQueries(1):
+ self.assertEqual(
+ next(intervals),
+ (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.11')),
+ )
+
+ def test_available_intervals_unsorted_exclude_intervals(self):
+ """
+ Tests that unsorted, overlapping exclude intervals are normalized internally.
+ """
+ intervals = list(IPAddress.objects.none().available_intervals(
+ netaddr.IPAddress('192.0.2.1'),
+ netaddr.IPAddress('192.0.2.40'),
+ exclude_intervals=[
+ (netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.30')),
+ (netaddr.IPAddress('192.0.2.1'), netaddr.IPAddress('192.0.2.10')),
+ (netaddr.IPAddress('192.0.2.25'), netaddr.IPAddress('192.0.2.30')),
+ ],
+ ))
+
+ self.assertEqual(intervals, [
+ (netaddr.IPAddress('192.0.2.11'), netaddr.IPAddress('192.0.2.19')),
+ (netaddr.IPAddress('192.0.2.31'), netaddr.IPAddress('192.0.2.40')),
+ ])
+
+ def test_available_intervals_batching(self):
+ """
+ Tests that gaps spanning multiple fetch batches are yielded completely and in order.
+ """
+ IPAddress.objects.bulk_create(
+ IPAddress(address=IPNetwork(f'192.0.3.{i}/24')) for i in range(2, 82, 2)
+ )
+ expected = [
+ (netaddr.IPAddress(f'192.0.3.{i}'), netaddr.IPAddress(f'192.0.3.{i}'))
+ for i in range(1, 83, 2)
+ ]
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.81'), batch_size=8
+ )),
+ expected,
+ )
+
+ def test_iter_distinct_hosts_stops_at_upper_bound(self):
+ """
+ Tests that batch resumption stops once the last fetched host reaches the upper bound.
+ """
+ IPAddress.objects.bulk_create(
+ IPAddress(address=IPNetwork(f'192.0.4.{i}/24')) for i in (2, 4)
+ )
+ self.assertEqual(
+ list(IPAddress.objects.all()._iter_distinct_hosts(
+ netaddr.IPAddress('192.0.4.2'), netaddr.IPAddress('192.0.4.4'), batch_size=1
+ )),
+ [netaddr.IPAddress('192.0.4.2'), netaddr.IPAddress('192.0.4.4')],
+ )
+
+ def test_available_intervals_batch_size_one(self):
+ """
+ Tests that fetching one host per batch still terminates and yields every gap.
+ """
+ IPAddress.objects.bulk_create(
+ IPAddress(address=IPNetwork(f'192.0.3.{i}/24')) for i in (2, 3, 5)
+ )
+ self.assertEqual(
+ list(IPAddress.objects.available_intervals(
+ netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.6'), batch_size=1
+ )),
+ [
+ (netaddr.IPAddress('192.0.3.1'), netaddr.IPAddress('192.0.3.1')),
+ (netaddr.IPAddress('192.0.3.4'), netaddr.IPAddress('192.0.3.4')),
+ (netaddr.IPAddress('192.0.3.6'), netaddr.IPAddress('192.0.3.6')),
+ ],
+ )
+
+
+class IPRangeQuerySetTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ IPRange.objects.bulk_create((
+ IPRange(start_address=IPNetwork('192.0.2.10/24'), end_address=IPNetwork('192.0.2.19/24'), size=10),
+ IPRange(start_address=IPNetwork('192.0.2.15/24'), end_address=IPNetwork('192.0.2.24/24'), size=10),
+ IPRange(start_address=IPNetwork('192.0.2.40/24'), end_address=IPNetwork('192.0.2.49/24'), size=10),
+ ))
+
+ def test_get_intervals_merges_overlaps(self):
+ """
+ Tests that overlapping ranges merge and disjoint ranges stay separate.
+ """
+ self.assertEqual(
+ IPRange.objects.get_intervals(),
+ [
+ (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24')),
+ (netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49')),
+ ],
+ )
+
+ def test_get_intervals_clips_to_bounds(self):
+ """
+ Tests that ranges are clipped to the bounds and out-of-bounds ranges are dropped.
+ """
+ self.assertEqual(
+ IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.30')),
+ [(netaddr.IPAddress('192.0.2.20'), netaddr.IPAddress('192.0.2.24'))],
+ )
+
+ def test_get_intervals_drops_ranges_below_bounds(self):
+ """
+ Tests that ranges entirely below the lower bound are dropped.
+ """
+ self.assertEqual(
+ IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.30'), netaddr.IPAddress('192.0.2.60')),
+ [(netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49'))],
+ )
+
+ def test_get_intervals_drops_ranges_above_bounds(self):
+ """
+ Tests that ranges entirely above the upper bound are dropped.
+ """
+ self.assertEqual(
+ IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.30')),
+ [(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24'))],
+ )
+
+ def test_get_intervals_clips_to_upper_bound(self):
+ """
+ Tests that a range straddling the upper bound is clipped to it.
+ """
+ self.assertEqual(
+ IPRange.objects.get_intervals(netaddr.IPAddress('192.0.2.0'), netaddr.IPAddress('192.0.2.15')),
+ [(netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.15'))],
+ )
+
+ def test_get_intervals_mixed_families(self):
+ """
+ Tests that int-adjacent intervals of different address families are not merged.
+ """
+ IPRange.objects.bulk_create((
+ IPRange(
+ start_address=IPNetwork('255.255.255.254/32'),
+ end_address=IPNetwork('255.255.255.255/32'),
+ size=2,
+ ),
+ IPRange(start_address=IPNetwork('::1/128'), end_address=IPNetwork('::2/128'), size=2),
+ ))
+
+ self.assertEqual(
+ IPRange.objects.get_intervals(),
+ [
+ (netaddr.IPAddress('192.0.2.10'), netaddr.IPAddress('192.0.2.24')),
+ (netaddr.IPAddress('192.0.2.40'), netaddr.IPAddress('192.0.2.49')),
+ (netaddr.IPAddress('255.255.255.254'), netaddr.IPAddress('255.255.255.255')),
+ (netaddr.IPAddress('::1'), netaddr.IPAddress('::2')),
+ ],
+ )
+
+ def test_get_intervals_ipv6(self):
+ """
+ Tests that IPv6 ranges merge and clip by host address.
+ """
+ IPRange.objects.create(
+ start_address=IPNetwork('2001:db8::10/64'),
+ end_address=IPNetwork('2001:db8::1f/64'),
+ )
+ IPRange.objects.create(
+ start_address=IPNetwork('2001:db8::18/64'),
+ end_address=IPNetwork('2001:db8::2f/64'),
+ )
+
+ self.assertEqual(
+ IPRange.objects.get_intervals(netaddr.IPAddress('2001:db8::'), netaddr.IPAddress('2001:db8::ffff')),
+ [(netaddr.IPAddress('2001:db8::10'), netaddr.IPAddress('2001:db8::2f'))],
+ )
diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py
index 10932ce72..9bf73038f 100644
--- a/netbox/ipam/tests/test_views.py
+++ b/netbox/ipam/tests/test_views.py
@@ -1,6 +1,8 @@
import datetime
from django.contrib.contenttypes.models import ContentType
+from django.db.backends.postgresql.psycopg_any import NumericRange
+from django.test import RequestFactory
from django.urls import reverse
from netaddr import IPNetwork
@@ -8,8 +10,10 @@ from core.choices import ObjectChangeActionChoices
from core.models import ObjectChange, ObjectType
from dcim.constants import InterfaceTypeChoices
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
+from extras.models import SavedFilter
from ipam.choices import *
from ipam.models import *
+from ipam.views import AggregatePrefixesView
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
from tenancy.models import Tenant
from users.models import ObjectPermission
@@ -353,6 +357,101 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk})
self.assertHttpStatus(self.client.get(url), 200)
+ def test_aggregate_prefixes_filter_suppresses_available_prefixes(self):
+ self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix')
+
+ tenants = (
+ Tenant(name='Aggregate Tenant 1', slug='aggregate-tenant-1'),
+ Tenant(name='Aggregate Tenant 2', slug='aggregate-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ aggregate = Aggregate.objects.create(
+ prefix=IPNetwork('203.0.113.0/24'),
+ rir=RIR.objects.first()
+ )
+ prefixes = (
+ Prefix(prefix=IPNetwork('203.0.113.0/26'), tenant=tenants[0]),
+ Prefix(prefix=IPNetwork('203.0.113.64/26'), tenant=tenants[1]),
+ )
+ Prefix.objects.bulk_create(prefixes)
+
+ url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk})
+ response = self.client.get(url, {'tenant_id': tenants[0].pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '203.0.113.0/26')
+ self.assertNotContains(response, '203.0.113.64/26')
+
+ def test_aggregate_prefixes_saved_filter(self):
+ self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix')
+
+ tenants = (
+ Tenant(name='Aggregate Saved Tenant 1', slug='aggregate-saved-tenant-1'),
+ Tenant(name='Aggregate Saved Tenant 2', slug='aggregate-saved-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ aggregate = Aggregate.objects.create(
+ prefix=IPNetwork('203.0.114.0/24'),
+ rir=RIR.objects.first()
+ )
+ prefixes = (
+ Prefix(prefix=IPNetwork('203.0.114.0/26'), tenant=tenants[0]),
+ Prefix(prefix=IPNetwork('203.0.114.64/26'), tenant=tenants[1]),
+ )
+ Prefix.objects.bulk_create(prefixes)
+
+ saved_filter = SavedFilter.objects.create(
+ name='Aggregate Tenant 1 prefixes',
+ slug='aggregate-tenant-1-prefixes',
+ parameters={
+ 'tenant_id': [str(tenants[0].pk)],
+ },
+ )
+ saved_filter.object_types.add(ObjectType.objects.get_for_model(Prefix))
+
+ url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk})
+ response = self.client.get(url, {'filter_id': saved_filter.pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '203.0.114.0/26')
+ self.assertNotContains(response, '203.0.114.64/26')
+
+ def test_children_are_filtered_fallback(self):
+ """_children_are_filtered() rebuilds the queryset when prep_table_data() has not cached a result."""
+ self.add_permissions('ipam.view_aggregate', 'ipam.view_prefix')
+
+ aggregate = Aggregate.objects.create(
+ prefix=IPNetwork('203.0.115.0/24'),
+ rir=RIR.objects.first()
+ )
+ tenant = Tenant.objects.create(name='Aggregate Fallback Tenant', slug='aggregate-fallback-tenant')
+ Prefix.objects.create(prefix=IPNetwork('203.0.115.0/26'), tenant=tenant)
+ Prefix.objects.create(prefix=IPNetwork('203.0.115.64/26'))
+
+ # No cached value: the fallback path rebuilds the filtered queryset and detects the filter.
+ view = AggregatePrefixesView()
+ request = RequestFactory().get('/', {'tenant_id': tenant.pk})
+ request.user = self.user
+ self.assertFalse(hasattr(view, '_child_queryset_is_filtered'))
+ self.assertTrue(view._children_are_filtered(request, aggregate))
+
+ # No cached value and no filter: the fallback path reports no filtering.
+ view = AggregatePrefixesView()
+ request = RequestFactory().get('/')
+ request.user = self.user
+ self.assertFalse(view._children_are_filtered(request, aggregate))
+
+ # A cached value takes precedence over the actual request state.
+ view = AggregatePrefixesView()
+ view._set_children_filtered(False)
+ request = RequestFactory().get('/', {'tenant_id': tenant.pk})
+ request.user = self.user
+ self.assertFalse(view._children_are_filtered(request, aggregate))
+
class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
model = Role
@@ -588,6 +687,63 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
url = reverse('ipam:prefix_prefixes', kwargs={'pk': prefixes[0].pk})
self.assertHttpStatus(self.client.get(url), 200)
+ def test_prefix_prefixes_filter_suppresses_available_prefixes(self):
+ self.add_permissions('ipam.view_prefix')
+
+ tenants = (
+ Tenant(name='Prefix Tenant 1', slug='prefix-tenant-1'),
+ Tenant(name='Prefix Tenant 2', slug='prefix-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ parent = Prefix.objects.create(prefix=IPNetwork('198.51.100.0/24'))
+ prefixes = (
+ Prefix(prefix=IPNetwork('198.51.100.0/26'), tenant=tenants[0]),
+ Prefix(prefix=IPNetwork('198.51.100.64/26'), tenant=tenants[1]),
+ )
+ Prefix.objects.bulk_create(prefixes)
+
+ url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk})
+ response = self.client.get(url, {'tenant_id': tenants[0].pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '198.51.100.0/26')
+ self.assertNotContains(response, '198.51.100.64/26')
+
+ def test_prefix_prefixes_saved_filter_suppresses_available_prefixes(self):
+ self.add_permissions('ipam.view_prefix')
+
+ tenants = (
+ Tenant(name='Prefix Saved Tenant 1', slug='prefix-saved-tenant-1'),
+ Tenant(name='Prefix Saved Tenant 2', slug='prefix-saved-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ parent = Prefix.objects.create(prefix=IPNetwork('198.51.101.0/24'))
+ prefixes = (
+ Prefix(prefix=IPNetwork('198.51.101.0/26'), tenant=tenants[0]),
+ Prefix(prefix=IPNetwork('198.51.101.64/26'), tenant=tenants[1]),
+ )
+ Prefix.objects.bulk_create(prefixes)
+
+ saved_filter = SavedFilter.objects.create(
+ name='Prefix Tenant 1 prefixes',
+ slug='prefix-tenant-1-prefixes',
+ parameters={
+ 'tenant_id': [str(tenants[0].pk)],
+ },
+ )
+ saved_filter.object_types.add(ObjectType.objects.get_for_model(Prefix))
+
+ url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk})
+ response = self.client.get(url, {'filter_id': saved_filter.pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '198.51.101.0/26')
+ self.assertNotContains(response, '198.51.101.64/26')
+
def test_prefix_ipranges(self):
self.add_permissions('ipam.view_prefix', 'ipam.view_iprange')
prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16'))
@@ -616,6 +772,89 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
self.assertHttpStatus(self.client.get(url), 200)
+ def test_prefix_ipaddresses_filter(self):
+ self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange')
+
+ tenants = (
+ Tenant(name='IP Address Tenant 1', slug='ip-address-tenant-1'),
+ Tenant(name='IP Address Tenant 2', slug='ip-address-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+ ip_addresses = (
+ IPAddress(address=IPNetwork('192.0.2.1/24'), tenant=tenants[0]),
+ IPAddress(address=IPNetwork('192.0.2.2/24'), tenant=tenants[1]),
+ )
+ IPAddress.objects.bulk_create(ip_addresses)
+
+ url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
+ response = self.client.get(url, {'tenant_id': tenants[0].pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '192.0.2.1/24')
+ self.assertNotContains(response, '192.0.2.2/24')
+
+ def test_prefix_ipaddresses_saved_filter(self):
+ self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange')
+
+ tenants = (
+ Tenant(name='Saved Filter Tenant 1', slug='saved-filter-tenant-1'),
+ Tenant(name='Saved Filter Tenant 2', slug='saved-filter-tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+ ip_addresses = (
+ IPAddress(address=IPNetwork('192.0.2.1/24'), tenant=tenants[0]),
+ IPAddress(address=IPNetwork('192.0.2.2/24'), tenant=tenants[1]),
+ )
+ IPAddress.objects.bulk_create(ip_addresses)
+
+ saved_filter = SavedFilter.objects.create(
+ name='Tenant 1 IP addresses',
+ slug='tenant-1-ip-addresses',
+ parameters={
+ 'tenant_id': [str(tenants[0].pk)],
+ },
+ )
+ saved_filter.object_types.add(ObjectType.objects.get_for_model(IPAddress))
+
+ url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
+ response = self.client.get(url, {'filter_id': saved_filter.pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, '192.0.2.1/24')
+ self.assertNotContains(response, '192.0.2.2/24')
+
+ def test_prefix_ipaddresses_unfiltered_shows_available_space(self):
+ """An unfiltered IP Addresses tab injects synthetic available-space rows."""
+ self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange')
+
+ prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'))
+ IPAddress.objects.create(address=IPNetwork('192.0.2.1/29'))
+
+ url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
+ response = self.client.get(url)
+
+ self.assertHttpStatus(response, 200)
+ self.assertGreater(len(response.context['table'].data), 1)
+
+ def test_prefix_prefixes_unfiltered_shows_available_prefixes(self):
+ """An unfiltered Child Prefixes tab injects synthetic available-prefix rows."""
+ self.add_permissions('ipam.view_prefix')
+
+ parent = Prefix.objects.create(prefix=IPNetwork('198.51.102.0/24'))
+ Prefix.objects.create(prefix=IPNetwork('198.51.102.0/26'))
+
+ url = reverse('ipam:prefix_prefixes', kwargs={'pk': parent.pk})
+ response = self.client.get(url)
+
+ self.assertHttpStatus(response, 200)
+ self.assertGreater(len(response.context['table'].data), 1)
+
def test_prefix_ipaddresses_with_single_address_range(self):
self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress', 'ipam.view_iprange')
# The IP Addresses tab annotates child IP addresses alongside any
@@ -1130,6 +1369,70 @@ class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
'description': 'New description',
}
+ def test_vlans_filter_suppresses_available_vlans(self):
+ self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan')
+
+ group = VLANGroup.objects.create(
+ name='Filtered VLAN Group',
+ slug='filtered-vlan-group'
+ )
+ vlans = (
+ VLAN(group=group, vid=100, name='VLAN100'),
+ VLAN(group=group, vid=200, name='VLAN200'),
+ )
+ VLAN.objects.bulk_create(vlans)
+
+ url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk})
+ response = self.client.get(url, {'vid': 100})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, 'VLAN100')
+ self.assertNotContains(response, 'VLAN200')
+
+ def test_vlans_saved_filter_suppresses_available_vlans(self):
+ self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan')
+
+ group = VLANGroup.objects.create(
+ name='Saved Filter VLAN Group',
+ slug='saved-filter-vlan-group'
+ )
+ vlans = (
+ VLAN(group=group, vid=100, name='VLAN100'),
+ VLAN(group=group, vid=200, name='VLAN200'),
+ )
+ VLAN.objects.bulk_create(vlans)
+
+ saved_filter = SavedFilter.objects.create(
+ name='VLAN 100',
+ slug='vlan-100',
+ parameters={
+ 'vid': ['100'],
+ },
+ )
+ saved_filter.object_types.add(ObjectType.objects.get_for_model(VLAN))
+
+ url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk})
+ response = self.client.get(url, {'filter_id': saved_filter.pk})
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(len(response.context['table'].data), 1)
+ self.assertContains(response, 'VLAN100')
+ self.assertNotContains(response, 'VLAN200')
+
+ def test_vlans_unfiltered_shows_available_vlans(self):
+ """An unfiltered VLANs tab injects synthetic available-VLAN rows."""
+ self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan')
+
+ group = VLANGroup.objects.create(name='Unfiltered VLAN Group', slug='unfiltered-vlan-group')
+ VLAN.objects.create(group=group, vid=1, name='VLAN0001')
+
+ url = reverse('ipam:vlangroup_vlans', kwargs={'pk': group.pk})
+ response = self.client.get(url)
+
+ self.assertHttpStatus(response, 200)
+ self.assertGreater(len(response.context['table'].data), 1)
+
class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = VLAN
@@ -1199,6 +1502,178 @@ class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'description': 'New description',
}
+ def test_bulk_add_vlans(self):
+ self.add_permissions('ipam.add_vlan')
+
+ group = VLANGroup.objects.get(name='VLAN Group 1')
+ initial_count = VLAN.objects.count()
+ expected_vids = (110, 120, 121, 122)
+
+ form_data = {
+ 'pattern': '110,120-122',
+ 'group': group.pk,
+ 'name': 'Pool-{vid}',
+ 'status': VLANStatusChoices.STATUS_RESERVED,
+ }
+
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 302)
+ self.assertEqual(VLAN.objects.count(), initial_count + len(expected_vids))
+
+ for vid in expected_vids:
+ self.assertTrue(
+ VLAN.objects.filter(
+ group=group,
+ vid=vid,
+ name=f'Pool-{vid}'
+ ).exists()
+ )
+
+ def test_bulk_add_vlans_rolls_back_on_duplicate_name(self):
+ self.add_permissions('ipam.add_vlan')
+
+ group = VLANGroup.objects.get(name='VLAN Group 1')
+ initial_count = VLAN.objects.count()
+
+ form_data = {
+ 'pattern': '110-112',
+ 'group': group.pk,
+ 'name': 'Duplicate name',
+ 'status': VLANStatusChoices.STATUS_RESERVED,
+ }
+
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(VLAN.objects.count(), initial_count)
+ self.assertFalse(VLAN.objects.filter(group=group, vid=110).exists())
+
+ def test_bulk_add_vlans_rolls_back_when_any_id_outside_group_range(self):
+ self.add_permissions('ipam.add_vlan')
+
+ group = VLANGroup.objects.create(
+ name='Restricted VLAN Group',
+ slug='restricted-vlan-group',
+ vid_ranges=[NumericRange(200, 204)] # Valid VIDs: 200-203
+ )
+ initial_count = VLAN.objects.count()
+
+ form_data = {
+ 'pattern': '200-203,500',
+ 'group': group.pk,
+ 'name': 'Restricted-{vid}',
+ 'status': VLANStatusChoices.STATUS_RESERVED,
+ }
+
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(VLAN.objects.count(), initial_count)
+ self.assertFalse(VLAN.objects.filter(group=group, vid=200).exists())
+ self.assertFalse(VLAN.objects.filter(group=group, vid=203).exists())
+ self.assertFalse(VLAN.objects.filter(group=group, vid=500).exists())
+
+ def test_bulk_add_vlans_pattern_shapes(self):
+ """Single values, multiple values, ranges, and combinations create the expected VLANs."""
+ self.add_permissions('ipam.add_vlan')
+ # The combination runs against a second group: subTests share one transaction, and VIDs
+ # 10 & 20 would otherwise collide with the multiple-values case via the (group, vid) constraint.
+ cases = (
+ ('500', (500,), 'VLAN Group 1'),
+ ('5,10,20', (5, 10, 20), 'VLAN Group 1'),
+ ('600-605', tuple(range(600, 606)), 'VLAN Group 1'),
+ ('1,10-20,300-305', (1, *range(10, 21), *range(300, 306)), 'VLAN Group 2'),
+ )
+ for pattern, expected_vids, group_name in cases:
+ with self.subTest(pattern=pattern):
+ group = VLANGroup.objects.get(name=group_name)
+ initial_count = VLAN.objects.count()
+ form_data = {
+ 'pattern': pattern,
+ 'group': group.pk,
+ 'name': 'Pool-{vid}',
+ 'status': VLANStatusChoices.STATUS_ACTIVE,
+ }
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+ self.assertHttpStatus(response, 302)
+ self.assertEqual(VLAN.objects.count(), initial_count + len(expected_vids))
+ for vid in expected_vids:
+ self.assertTrue(VLAN.objects.filter(group=group, vid=vid, name=f'Pool-{vid}').exists())
+
+ def test_bulk_add_vlans_invalid_pattern(self):
+ """An invalid pattern re-renders the form with a pattern error and creates nothing."""
+ self.add_permissions('ipam.add_vlan')
+ initial_count = VLAN.objects.count()
+
+ for pattern in ('abc', '20-10', '0', '4095', '10-'):
+ with self.subTest(pattern=pattern):
+ form_data = {
+ 'pattern': pattern,
+ 'name': 'Pool-{vid}',
+ 'status': VLANStatusChoices.STATUS_ACTIVE,
+ }
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+ self.assertHttpStatus(response, 200)
+ self.assertIn('pattern', response.context['form'].errors)
+ self.assertEqual(VLAN.objects.count(), initial_count)
+
+ def test_bulk_add_vlans_static_name_without_group(self):
+ """A static name (no {vid} placeholder) is permitted across VLANs not assigned to a group."""
+ self.add_permissions('ipam.add_vlan')
+ initial_count = VLAN.objects.count()
+
+ form_data = {
+ 'pattern': '710-712',
+ 'name': 'Same name',
+ 'status': VLANStatusChoices.STATUS_ACTIVE,
+ }
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 302)
+ self.assertEqual(VLAN.objects.count(), initial_count + 3)
+ self.assertEqual(VLAN.objects.filter(name='Same name').count(), 3)
+
+ def test_bulk_add_vlans_rolls_back_on_constrained_permission(self):
+ """Bulk creation rolls back when a generated VLAN falls outside the user's add constraints."""
+ obj_perm = ObjectPermission(
+ name='Test permission',
+ actions=['add'],
+ constraints={'vid__lt': 120}
+ )
+ obj_perm.save()
+ obj_perm.users.add(self.user)
+ obj_perm.object_types.add(ObjectType.objects.get_for_model(VLAN))
+
+ initial_count = VLAN.objects.count()
+ form_data = {
+ 'pattern': '110,120-122',
+ 'name': 'Pool-{vid}',
+ 'status': VLANStatusChoices.STATUS_ACTIVE,
+ }
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(VLAN.objects.count(), initial_count)
+ self.assertTrue(response.context['form'].non_field_errors())
+
+ def test_bulk_add_vlans_propagates_field_errors(self):
+ """A per-object validation error on a non-pattern field is reported on the bulk-create form."""
+ self.add_permissions('ipam.add_vlan')
+ initial_count = VLAN.objects.count()
+
+ form_data = {
+ 'pattern': '800',
+ 'name': 'Pool-{vid}',
+ 'status': VLANStatusChoices.STATUS_ACTIVE,
+ 'qinq_role': VLANQinQRoleChoices.ROLE_CUSTOMER, # Requires an SVLAN
+ }
+ response = self.client.post(reverse('ipam:vlan_bulk_add'), form_data)
+
+ self.assertHttpStatus(response, 200)
+ self.assertEqual(VLAN.objects.count(), initial_count)
+ self.assertTrue(response.context['form'].non_field_errors())
+
class VLANTranslationPolicyTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = VLANTranslationPolicy
diff --git a/netbox/ipam/utils.py b/netbox/ipam/utils.py
index 390215873..5b29688fd 100644
--- a/netbox/ipam/utils.py
+++ b/netbox/ipam/utils.py
@@ -1,10 +1,10 @@
from dataclasses import dataclass
import netaddr
+from django.apps import apps
from django.utils.translation import gettext_lazy as _
from .constants import *
-from .models import VLAN, Prefix
__all__ = (
'AvailableIPSpace',
@@ -39,7 +39,7 @@ def add_requested_prefixes(parent, prefix_list, show_available=True, show_assign
requested, create fake Prefix objects for all unallocated space within a prefix.
:param parent: Parent Prefix instance
- :param prefix_list: Child prefixes list
+ :param prefix_list: Child prefixes list (or queryset)
:param show_available: Include available prefixes.
:param show_assigned: Show assigned prefixes.
"""
@@ -47,6 +47,7 @@ def add_requested_prefixes(parent, prefix_list, show_available=True, show_assign
# Add available prefixes to the table if requested
if prefix_list and show_available:
+ Prefix = apps.get_model('ipam', 'Prefix')
# Find all unallocated space, add fake Prefix objects to child_prefixes.
# IMPORTANT: These are unsaved Prefix instances (pk=None). If this is ever changed to use
@@ -78,22 +79,7 @@ def annotate_ip_space(prefix):
records = sorted(records, key=lambda x: x[0])
# Determine the first & last valid IP addresses in the prefix
- if (
- prefix.is_pool
- or (prefix.family == 4 and prefix.mask_length >= 31)
- or (prefix.family == 6 and prefix.mask_length >= 127)
- ):
- # Pool, IPv4 /31-/32 or IPv6 /127-/128 sets are fully usable
- first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first)
- last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last)
- elif prefix.family == 4:
- # Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31
- first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first + 1)
- last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last - 1)
- else:
- # For IPv6 prefixes, omit the Subnet-Router anycast address (RFC 4291)
- first_ip_in_prefix = netaddr.IPAddress(prefix.prefix.first + 1)
- last_ip_in_prefix = netaddr.IPAddress(prefix.prefix.last)
+ first_ip_in_prefix, last_ip_in_prefix = prefix.usable_ip_bounds
if not records:
return [
@@ -195,7 +181,7 @@ def add_available_vlans(vlans, vlan_group):
new_vlans.extend(available_vlans_from_range(vlans, vlan_group, vid_range))
vlans = list(vlans) + new_vlans
- vlans.sort(key=lambda v: v.vid if type(v) is VLAN else v['vid'])
+ vlans.sort(key=lambda v: v['vid'] if isinstance(v, dict) else v.vid)
return vlans
@@ -204,6 +190,9 @@ def rebuild_prefixes(vrf):
"""
Rebuild the prefix hierarchy for all prefixes in the specified VRF (or global table).
"""
+ Prefix = apps.get_model('ipam', 'Prefix')
+ prefix_queryset = Prefix.objects.filter(vrf=vrf)
+
def contains(parent, child):
return child in parent and child != parent
@@ -219,10 +208,10 @@ def rebuild_prefixes(vrf):
stack = []
update_queue = []
- prefixes = Prefix.objects.filter(vrf=vrf).values('pk', 'prefix')
+ prefixes = prefix_queryset.order_by('prefix', 'pk').values('pk', 'prefix')
- # Iterate through all Prefixes in the VRF, growing and shrinking the stack as we go
- for i, p in enumerate(prefixes):
+ # Iterate through all Prefixes in the table, growing and shrinking the stack as we go
+ for p in prefixes:
# Grow the stack if this is a child of the most recent prefix
if not stack or contains(stack[-1]['prefix'], p['prefix']):
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index 41f5a1832..3c57b8598 100644
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -547,8 +547,64 @@ class AggregateView(generic.ObjectView):
)
+class ChildAvailabilityMixin:
+ """
+ Mixin for ObjectChildrenView subclasses that render synthetic "available" rows
+ (available IP space, prefixes, or VLANs) and must suppress them when the child
+ queryset has been narrowed by a direct or saved filter.
+ """
+
+ @staticmethod
+ def _where_signature(queryset):
+ # query.where is Django-internal, but it is the closest signal for "narrowed by a filter".
+ return str(queryset.query.where)
+
+ def _set_children_filtered(self, is_filtered):
+ self._child_queryset_is_filtered = is_filtered
+ return is_filtered
+
+ def _queryset_is_filtered(self, request, queryset, parent):
+ """
+ Return True if the filtered child queryset differs from the unfiltered one.
+
+ Compares WHERE clauses rather than testing queryset.query.where for truthiness,
+ because child querysets are already scoped to their parent object and carry WHERE
+ clauses before any user filter is applied. The result is cached on the view instance
+ so get_extra_context() can reuse it without rebuilding the queryset.
+ """
+ if self.filterset is None:
+ return self._set_children_filtered(False)
+
+ unfiltered = self.get_children(request, parent)
+
+ return self._set_children_filtered(
+ self._where_signature(queryset) != self._where_signature(unfiltered)
+ )
+
+ def _children_are_filtered(self, request, parent):
+ """
+ Return whether child objects are filtered.
+
+ In the normal ObjectChildrenView flow prep_table_data() runs first and caches the
+ result, so this returns the cached value. Fall back to rebuilding the queryset for
+ direct calls where prep_table_data() has not run.
+ """
+ if hasattr(self, '_child_queryset_is_filtered'):
+ return self._child_queryset_is_filtered
+
+ if self.filterset is None:
+ return self._set_children_filtered(False)
+
+ unfiltered = self.get_children(request, parent)
+ filtered = self.filterset(request.GET, unfiltered, request=request).qs
+
+ return self._set_children_filtered(
+ self._where_signature(filtered) != self._where_signature(unfiltered)
+ )
+
+
@register_model_view(Aggregate, 'prefixes')
-class AggregatePrefixesView(generic.ObjectChildrenView):
+class AggregatePrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
queryset = Aggregate.objects.all()
child_model = Prefix
table = tables.PrefixTable
@@ -572,13 +628,21 @@ class AggregatePrefixesView(generic.ObjectChildrenView):
show_available = bool(request.GET.get('show_available', 'true') == 'true')
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
+ if self._queryset_is_filtered(request, queryset, parent):
+ show_available = False
+
return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
def get_extra_context(self, request, instance):
+ show_available = (
+ bool(request.GET.get('show_available', 'true') == 'true') and
+ not self._children_are_filtered(request, instance)
+ )
+
return {
'bulk_querystring': f'within={instance.prefix}',
'first_available_prefix': instance.get_first_available_prefix(),
- 'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
+ 'show_available': show_available,
'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
}
@@ -770,7 +834,7 @@ class PrefixView(generic.ObjectView):
@register_model_view(Prefix, 'prefixes')
-class PrefixPrefixesView(generic.ObjectChildrenView):
+class PrefixPrefixesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
queryset = Prefix.objects.all()
child_model = Prefix
table = tables.PrefixTable
@@ -794,13 +858,21 @@ class PrefixPrefixesView(generic.ObjectChildrenView):
show_available = bool(request.GET.get('show_available', 'true') == 'true')
show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')
+ if self._queryset_is_filtered(request, queryset, parent):
+ show_available = False
+
return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)
def get_extra_context(self, request, instance):
+ show_available = (
+ bool(request.GET.get('show_available', 'true') == 'true') and
+ not self._children_are_filtered(request, instance)
+ )
+
return {
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}",
'first_available_prefix': instance.get_first_available_prefix(),
- 'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
+ 'show_available': show_available,
'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
}
@@ -833,7 +905,7 @@ class PrefixIPRangesView(generic.ObjectChildrenView):
@register_model_view(Prefix, 'ipaddresses', path='ip-addresses')
-class PrefixIPAddressesView(generic.ObjectChildrenView):
+class PrefixIPAddressesView(ChildAvailabilityMixin, generic.ObjectChildrenView):
queryset = Prefix.objects.all()
child_model = IPAddress
table = tables.AnnotatedIPAddressTable
@@ -851,9 +923,10 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
def prep_table_data(self, request, queryset, parent):
- if not request.GET.get('q') and not get_table_ordering(request, self.table):
+ if not self._queryset_is_filtered(request, queryset, parent) and not get_table_ordering(request, self.table):
return annotate_ip_space(parent)
- return queryset
+
+ return super().prep_table_data(request, queryset, parent)
def get_extra_context(self, request, instance):
return {
@@ -1292,7 +1365,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
@register_model_view(VLANGroup, 'vlans')
-class VLANGroupVLANsView(generic.ObjectChildrenView):
+class VLANGroupVLANsView(ChildAvailabilityMixin, generic.ObjectChildrenView):
queryset = VLANGroup.objects.all()
child_model = VLAN
table = tables.VLANTable
@@ -1312,9 +1385,11 @@ class VLANGroupVLANsView(generic.ObjectChildrenView):
)
def prep_table_data(self, request, queryset, parent):
- if not get_table_ordering(request, self.table):
+ # Skip synthetic available rows under active filters: filtered-out VLANs would otherwise look available.
+ if not self._queryset_is_filtered(request, queryset, parent) and not get_table_ordering(request, self.table):
return add_available_vlans(queryset, parent)
- return queryset
+
+ return super().prep_table_data(request, queryset, parent)
#
@@ -1718,6 +1793,16 @@ class VLANDeleteView(generic.ObjectDeleteView):
queryset = VLAN.objects.all()
+@register_model_view(VLAN, 'bulk_add', path='bulk-add', detail=False)
+class VLANBulkCreateView(generic.BulkCreateView):
+ queryset = VLAN.objects.all()
+ form = forms.VLANIDBulkCreateForm
+ model_form = forms.VLANBulkAddForm
+ pattern_target = 'vid'
+ pattern_template_fields = ('name',)
+ template_name = 'ipam/vlan_bulk_add.html'
+
+
@register_model_view(VLAN, 'bulk_import', path='import', detail=False)
class VLANBulkImportView(generic.BulkImportView):
queryset = VLAN.objects.all()
diff --git a/netbox/netbox/api/serializers/bulk.py b/netbox/netbox/api/serializers/bulk.py
index c9fd28534..fba4961b0 100644
--- a/netbox/netbox/api/serializers/bulk.py
+++ b/netbox/netbox/api/serializers/bulk.py
@@ -1,11 +1,79 @@
+import copy
+import functools
+
from rest_framework import serializers
from .features import ChangeLogMessageSerializer
__all__ = (
'BulkOperationSerializer',
+ 'BulkPartialUpdateSchemaMixin',
+ 'BulkUpdateSchemaMixin',
+ 'get_bulk_update_serializer_class'
)
class BulkOperationSerializer(ChangeLogMessageSerializer):
id = serializers.IntegerField()
+
+
+class BulkUpdateSchemaMixin:
+ def get_fields(self):
+ fields = super().get_fields()
+ # Reuse the runtime bulk-operation ID field so the schema stays in sync
+ # with the validator that consumes `id` before model serialization.
+ _id = copy.deepcopy(BulkOperationSerializer().fields['id'])
+ _id.required = True
+ fields['id'] = _id
+
+ return fields
+
+
+class BulkPartialUpdateSchemaMixin(BulkUpdateSchemaMixin):
+ def get_fields(self):
+ fields = super().get_fields()
+
+ for name, field in fields.items():
+ if name != 'id':
+ field.required = False
+
+ return fields
+
+
+@functools.cache
+def get_bulk_update_serializer_class(serializer_class, *, partial=False):
+ """
+ Return a schema-only serializer for bulk PUT/PATCH requests.
+
+ Bulk update requests to a list endpoint require each object to include
+ the target object's numeric ID, even though `id` is read-only on the
+ normal model serializer. The runtime code consumes `id` before invoking
+ the model serializer for each object.
+ """
+
+ meta = getattr(serializer_class, 'Meta')
+
+ if meta.fields == '__all__':
+ fields = '__all__'
+ else:
+ fields = ('id', *[f for f in meta.fields if f != 'id'])
+
+ class Meta(meta):
+ pass
+
+ # intentional; this is different than setting fields = fields within class Meta above
+ Meta.fields = fields
+
+ bases = (
+ (BulkPartialUpdateSchemaMixin, serializer_class)
+ if partial
+ else (BulkUpdateSchemaMixin, serializer_class)
+ )
+
+ attrs = {
+ 'Meta': Meta,
+ '__module__': serializer_class.__module__,
+ }
+
+ prefix = 'PatchedBulk' if partial else 'Bulk'
+ return type(f'{prefix}{serializer_class.__name__}', bases, attrs)
diff --git a/netbox/netbox/api/viewsets/mixins.py b/netbox/netbox/api/viewsets/mixins.py
index d05c6abc2..f5e6c18b5 100644
--- a/netbox/netbox/api/viewsets/mixins.py
+++ b/netbox/netbox/api/viewsets/mixins.py
@@ -10,6 +10,7 @@ from rest_framework.reverse import reverse
from core.models import ObjectType
from extras.models import ExportTemplate
from netbox.api.serializers import BulkOperationSerializer
+from netbox.api.serializers.bulk import get_bulk_update_serializer_class
from netbox.jobs import AsyncAPIJob
from utilities.exceptions import RQWorkerNotRunningException
from utilities.rqworker import any_workers_for_queue
@@ -240,6 +241,20 @@ class BulkUpdateModelMixin:
return updated_pks
+ def get_bulk_update_serializer_class(self, *, partial=False):
+ return get_bulk_update_serializer_class(
+ self.get_serializer_class(),
+ partial=partial,
+ )
+
+ def get_bulk_update_request_serializer(self, *, partial=False):
+ serializer_class = self.get_bulk_update_serializer_class(partial=partial)
+
+ # Important: do NOT pass partial=True here. The partial schema class already
+ # makes non-id fields optional, and passing partial=True would also make id
+ # appear optional in OpenAPI.
+ return serializer_class(many=True)
+
def bulk_partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.bulk_update(request, *args, **kwargs)
diff --git a/netbox/netbox/graphql/filter_lookups.py b/netbox/netbox/graphql/filter_lookups.py
index 752e0e966..e56c3048b 100644
--- a/netbox/netbox/graphql/filter_lookups.py
+++ b/netbox/netbox/graphql/filter_lookups.py
@@ -1,3 +1,4 @@
+import re
from enum import Enum
from typing import Generic, TypeVar
@@ -11,17 +12,48 @@ from strawberry.directive import DirectiveValue
from strawberry.types import Info
from strawberry_django import (
ComparisonFilterLookup,
- DateFilterLookup,
- DatetimeFilterLookup,
FilterLookup,
RangeLookup,
- StrFilterLookup,
- TimeFilterLookup,
process_filters,
)
from netbox.graphql.scalars import BigInt
+# ------------------------------------------------------------------
+# JSON path validation (VM-323)
+# ------------------------------------------------------------------
+
+# Each segment of a JSON path may only contain alphanumerics, underscores, and
+# hyphens. Hyphens are included because JSON keys commonly use them; leading
+# underscores are permitted (e.g. _foo is a valid key name).
+_JSON_PATH_SEGMENT_RE = re.compile(r'^[A-Za-z0-9_][A-Za-z0-9_-]*$')
+
+
+def _validate_json_path(path: str) -> str:
+ """Validate a JSON traversal path for use in ORM lookups.
+
+ Each ``__``-separated segment must match ``[A-Za-z0-9_][A-Za-z0-9_-]*``.
+ Raises ``ValueError`` on an empty path, empty segment, or segment with
+ disallowed characters.
+
+ ORM operator names (``date``, ``regex``, etc.) are intentionally *not*
+ blocked here: ``JSONFilter.filter()`` always appends ``__`` to the path
+ before handing it to ``process_filters``, so a segment named ``regex``
+ becomes another level of JSON key traversal (``data__key__regex__exact``),
+ not the ORM regex transform (``data__key__regex=…``).
+ """
+ if not path:
+ raise ValueError("JSON path cannot be empty")
+
+ for segment in path.split('__'):
+ if not segment:
+ raise ValueError("JSON path contains consecutive or trailing '__'")
+ if not _JSON_PATH_SEGMENT_RE.match(segment):
+ raise ValueError(f"Invalid JSON path segment: {segment!r}")
+
+ return path
+
+
__all__ = (
'ArrayLookup',
'BigIntegerLookup',
@@ -31,6 +63,8 @@ __all__ = (
'IntegerLookup',
'IntegerRangeArrayLookup',
'JSONFilter',
+ 'JSONLookup',
+ 'JSONStringLookup',
'StringArrayLookup',
'TreeNodeFilter',
)
@@ -39,16 +73,82 @@ T = TypeVar('T')
SKIP_MSG = 'Filter will be skipped on `null` value'
+# These JSON lookup types intentionally mirror the legacy DateFilterLookup[str],
+# TimeFilterLookup[str], and DatetimeFilterLookup[str] schema. JSON values are
+# string-backed, so the concrete strawberry-django date/time lookup classes
+# (which now ignore type parameters and warn) are deliberately not used here.
+@strawberry.input(name='StrDateFilterLookup')
+class JSONDateFilterLookup(ComparisonFilterLookup[str]):
+ year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ month: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ week: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
+@strawberry.input(name='StrTimeFilterLookup')
+class JSONTimeFilterLookup(ComparisonFilterLookup[str]):
+ hour: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ minute: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ second: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ date: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ time: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
+@strawberry.input(name='StrDatetimeFilterLookup')
+class JSONDatetimeFilterLookup(ComparisonFilterLookup[str]):
+ year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ month: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ week: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ hour: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ minute: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ second: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ date: ComparisonFilterLookup[int] | None = strawberry.UNSET
+ time: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
+@strawberry.input(description='String lookups for JSON field values.')
+class JSONStringLookup:
+ """
+ String-filter type for use inside JSONLookup.
+
+ Equivalent to ``StrFilterLookup`` but defined explicitly so that the type
+ name remains stable and any future per-field restrictions are easy to add.
+ ``regex`` / ``i_regex`` are included: they provide no additional oracle
+ power beyond ``starts_with``, which is also present.
+ """
+ exact: str | None = strawberry_django.filter_field()
+ i_exact: str | None = strawberry_django.filter_field()
+ contains: str | None = strawberry_django.filter_field()
+ i_contains: str | None = strawberry_django.filter_field()
+ starts_with: str | None = strawberry_django.filter_field()
+ i_starts_with: str | None = strawberry_django.filter_field()
+ ends_with: str | None = strawberry_django.filter_field()
+ i_ends_with: str | None = strawberry_django.filter_field()
+ in_: list[str] | None = strawberry_django.filter_field()
+ isnull: bool | None = strawberry_django.filter_field()
+ regex: str | None = strawberry_django.filter_field()
+ i_regex: str | None = strawberry_django.filter_field()
+
+
@strawberry.input(one_of=True, description='Lookup for JSON field. Only one of the lookup fields can be set.')
class JSONLookup:
- string_lookup: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ string_lookup: JSONStringLookup | None = strawberry_django.filter_field()
int_range_lookup: RangeLookup[int] | None = strawberry_django.filter_field()
int_comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
float_range_lookup: RangeLookup[float] | None = strawberry_django.filter_field()
float_comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field()
- date_lookup: DateFilterLookup[str] | None = strawberry_django.filter_field()
- datetime_lookup: DatetimeFilterLookup[str] | None = strawberry_django.filter_field()
- time_lookup: TimeFilterLookup[str] | None = strawberry_django.filter_field()
+ date_lookup: JSONDateFilterLookup | None = strawberry_django.filter_field()
+ datetime_lookup: JSONDatetimeFilterLookup | None = strawberry_django.filter_field()
+ time_lookup: JSONTimeFilterLookup | None = strawberry_django.filter_field()
boolean_lookup: FilterLookup[bool] | None = strawberry_django.filter_field()
def get_filter(self):
@@ -119,7 +219,12 @@ class JSONFilter:
if not filters:
return queryset, Q()
- json_path = f'{prefix}{self.path}__'
+ try:
+ safe_path = _validate_json_path(self.path)
+ except ValueError:
+ return queryset, Q()
+
+ json_path = f'{prefix}{safe_path}__'
return process_filters(filters=filters, queryset=queryset, info=info, prefix=json_path)
diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py
index 94b836f06..c33e3e8b9 100644
--- a/netbox/netbox/graphql/filter_mixins.py
+++ b/netbox/netbox/graphql/filter_mixins.py
@@ -1,5 +1,4 @@
from dataclasses import dataclass
-from datetime import datetime
from typing import TYPE_CHECKING, Annotated, TypeVar
import strawberry
@@ -48,9 +47,9 @@ class SyncedDataFilterMixin:
strawberry_django.filter_field()
)
data_file_id: FilterLookup[int] | None = strawberry_django.filter_field()
- data_path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ data_path: StrFilterLookup | None = strawberry_django.filter_field()
auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
- data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+ data_synced: DatetimeFilterLookup | None = strawberry_django.filter_field()
@dataclass
diff --git a/netbox/netbox/graphql/filters.py b/netbox/netbox/graphql/filters.py
index a372d7bc0..c6241e226 100644
--- a/netbox/netbox/graphql/filters.py
+++ b/netbox/netbox/graphql/filters.py
@@ -42,21 +42,21 @@ class NetBoxModelFilter(
@dataclass
class NestedGroupModelFilter(NetBoxModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
parent_id: ID | None = strawberry_django.filter_field()
@dataclass
class OrganizationalModelFilter(NetBoxModelFilter):
- name: StrFilterLookup[str] | None = strawberry_django.filter_field()
- slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup | None = strawberry_django.filter_field()
+ slug: StrFilterLookup | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
@dataclass
class PrimaryModelFilter(NetBoxModelFilter):
- description: StrFilterLookup[str] | None = strawberry_django.filter_field()
- comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ description: StrFilterLookup | None = strawberry_django.filter_field()
+ comments: StrFilterLookup | None = strawberry_django.filter_field()
diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py
index 1cce4a7dc..6b432050f 100644
--- a/netbox/netbox/graphql/schema.py
+++ b/netbox/netbox/graphql/schema.py
@@ -1,3 +1,5 @@
+from collections.abc import Callable
+
import strawberry
from django.conf import settings
from strawberry.extensions import MaxAliasesLimiter, QueryDepthLimiter, SchemaExtension
@@ -18,6 +20,8 @@ from wireless.graphql.schema import WirelessQuery
from .scalars import BigInt, BigIntScalar
+SchemaExtensionFactory = type[SchemaExtension] | Callable[[], SchemaExtension]
+
@strawberry.type
class Query(
@@ -36,14 +40,16 @@ class Query(
pass
-def get_schema_extensions() -> list[SchemaExtension]:
- extensions: list[SchemaExtension] = [
- DjangoOptimizerExtension(prefetch_custom_queryset=True),
- MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
- ]
+def get_schema_extensions() -> list[SchemaExtensionFactory]:
+ max_aliases = settings.GRAPHQL_MAX_ALIASES
max_depth = settings.GRAPHQL_MAX_QUERY_DEPTH
+
+ extensions: list[SchemaExtensionFactory] = [
+ lambda: DjangoOptimizerExtension(prefetch_custom_queryset=True),
+ lambda: MaxAliasesLimiter(max_alias_count=max_aliases),
+ ]
if max_depth and max_depth > 0:
- extensions.append(QueryDepthLimiter(max_depth=max_depth))
+ extensions.append(lambda: QueryDepthLimiter(max_depth=max_depth))
return extensions
diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py
index 4a36c2d98..a9ee93fc6 100644
--- a/netbox/netbox/middleware.py
+++ b/netbox/netbox/middleware.py
@@ -10,7 +10,9 @@ from django.db import ProgrammingError, connection
from django.db.utils import InternalError
from django.http import Http404, HttpResponseRedirect
from django.middleware.common import CommonMiddleware as DjangoCommonMiddleware
+from django.utils.translation import gettext_lazy as _
from django_prometheus import middleware
+from social_django.middleware import SocialAuthExceptionMiddleware as SocialAuthExceptionMiddleware_
from netbox.config import clear_config, get_config
from netbox.metrics import Metrics
@@ -26,6 +28,7 @@ __all__ = (
'PrometheusAfterMiddleware',
'PrometheusBeforeMiddleware',
'RemoteUserMiddleware',
+ 'SocialAuthExceptionMiddleware',
)
@@ -286,3 +289,13 @@ class MaintenanceModeMiddleware:
messages.error(request, error_message)
return HttpResponseRedirect(request.path_info)
return None
+
+
+class SocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware_):
+ """
+ Subclass of python-social-auth's exception middleware which surfaces a generic, user-friendly
+ message rather than exposing the raw social_core exception text to (typically unauthenticated)
+ users when an SSO/SAML login fails.
+ """
+ def get_message(self, request, exception):
+ return _("Single sign-on failed. Please try again or contact your administrator.")
diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py
index e106fbc10..86ac2bfd7 100644
--- a/netbox/netbox/models/deletion.py
+++ b/netbox/netbox/models/deletion.py
@@ -3,6 +3,7 @@ import logging
from django.contrib.contenttypes.fields import GenericRelation
from django.db import router
from django.db.models.deletion import CASCADE, Collector
+from django.utils.translation import gettext as _
logger = logging.getLogger("netbox.models.deletion")
@@ -45,7 +46,7 @@ class CustomCollector(Collector):
# Add GenericRelations to the dependency graph
processed_relations = set()
- for _, instances in list(self.data.items()):
+ for _model, instances in list(self.data.items()):
for instance in instances:
# Get all GenericRelations for this model
for field in instance._meta.private_fields:
@@ -70,10 +71,13 @@ class DeleteMixin:
Override delete to use our custom collector.
"""
using = using or router.db_for_write(self.__class__, instance=self)
- assert self._get_pk_val() is not None, (
- f"{self._meta.object_name} object can't be deleted because its "
- f"{self._meta.pk.attname} attribute is set to None."
- )
+ if self._get_pk_val() is None:
+ raise ValueError(
+ _("{object_name} object can't be deleted because its {pk_attname} attribute is set to None.").format(
+ object_name=self._meta.object_name,
+ pk_attname=self._meta.pk.attname,
+ )
+ )
collector = CustomCollector(using=using)
collector.collect([self], keep_parents=keep_parents)
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 092ac44db..0ae705127 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -514,6 +514,7 @@ MIDDLEWARE = [
'netbox.middleware.RemoteUserMiddleware',
'netbox.middleware.CoreMiddleware',
'netbox.middleware.MaintenanceModeMiddleware',
+ 'netbox.middleware.SocialAuthExceptionMiddleware',
]
if DEBUG:
@@ -713,6 +714,13 @@ SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.user.user_details',
)
+# Redirect users back to the login page (surfacing the error via the messages framework) when an
+# SSO/SAML authentication failure occurs, rather than raising an HTTP 500. Full exceptions are still
+# raised when DEBUG is enabled. LOGIN_URL is an absolute path which respects BASE_PATH; the social
+# auth middleware passes this value directly to an HttpResponseRedirect without reversing it.
+SOCIAL_AUTH_LOGIN_ERROR_URL = LOGIN_URL
+SOCIAL_AUTH_RAISE_EXCEPTIONS = DEBUG
+
# Load all SOCIAL_AUTH_* settings from the user configuration
for param in dir(configuration):
if param.startswith('SOCIAL_AUTH_'):
diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py
index 55298cb2f..5221fe4b8 100644
--- a/netbox/netbox/tests/test_authentication.py
+++ b/netbox/netbox/tests/test_authentication.py
@@ -1,13 +1,16 @@
import datetime
from django.conf import settings
-from django.test import Client
+from django.contrib.messages.storage.fallback import FallbackStorage
+from django.test import Client, RequestFactory, SimpleTestCase
from django.test.utils import override_settings
from django.urls import reverse
from rest_framework.test import APIClient
+from social_core.exceptions import AuthFailed
from core.models import ObjectType
from dcim.models import Rack, Site
+from netbox.middleware import SocialAuthExceptionMiddleware
from users.constants import TOKEN_PREFIX
from users.models import Group, ObjectPermission, Token, User
from utilities.testing import TestCase
@@ -697,3 +700,56 @@ class ObjectPermissionAPIViewTestCase(TestCase):
url = reverse('dcim-api:rack-detail', kwargs={'pk': self.racks[0].pk})
response = self.client.delete(url, format='json', **self.header)
self.assertEqual(response.status_code, 204)
+
+
+class SocialAuthExceptionMiddlewareTestCase(SimpleTestCase):
+ """
+ Verify that SSO/SAML authentication failures are surfaced as a login-page message rather than
+ bubbling up as an HTTP 500 (see #22346).
+ """
+ GENERIC_MESSAGE = "Single sign-on failed. Please try again or contact your administrator."
+
+ class FakeStrategy:
+ # Mirror social_core's DjangoStrategy.setting(), which reads SOCIAL_AUTH_