mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into feature
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.5.7
|
||||
placeholder: v3.5.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.5.7
|
||||
placeholder: v3.5.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
561
contrib/generated_schema.json
Normal file
561
contrib/generated_schema.json
Normal file
@ -0,0 +1,561 @@
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"airflow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"front-to-rear",
|
||||
"rear-to-front",
|
||||
"left-to-right",
|
||||
"right-to-left",
|
||||
"side-to-rear",
|
||||
"passive",
|
||||
"mixed"
|
||||
]
|
||||
},
|
||||
"weight-unit": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"kg",
|
||||
"g",
|
||||
"lb",
|
||||
"oz"
|
||||
]
|
||||
},
|
||||
"subdevice-role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"parent",
|
||||
"child"
|
||||
]
|
||||
},
|
||||
"console-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"console-server-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"power-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iec-60320-c6",
|
||||
"iec-60320-c8",
|
||||
"iec-60320-c14",
|
||||
"iec-60320-c16",
|
||||
"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-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",
|
||||
"dc-terminal",
|
||||
"saf-d-grid",
|
||||
"neutrik-powercon-20",
|
||||
"neutrik-powercon-32",
|
||||
"neutrik-powercon-true1",
|
||||
"neutrik-powercon-true1-top",
|
||||
"ubiquiti-smartpower",
|
||||
"hardwired",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"power-outlet": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iec-60320-c5",
|
||||
"iec-60320-c7",
|
||||
"iec-60320-c13",
|
||||
"iec-60320-c15",
|
||||
"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-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",
|
||||
"dc-terminal",
|
||||
"hdot-cx",
|
||||
"saf-d-grid",
|
||||
"neutrik-powercon-20a",
|
||||
"neutrik-powercon-32a",
|
||||
"neutrik-powercon-true1",
|
||||
"neutrik-powercon-true1-top",
|
||||
"ubiquiti-smartpower",
|
||||
"hardwired",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"feed-leg": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"A",
|
||||
"B",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"virtual",
|
||||
"bridge",
|
||||
"lag",
|
||||
"100base-fx",
|
||||
"100base-lfx",
|
||||
"100base-tx",
|
||||
"100base-t1",
|
||||
"1000base-t",
|
||||
"2.5gbase-t",
|
||||
"5gbase-t",
|
||||
"10gbase-t",
|
||||
"10gbase-cx4",
|
||||
"1000base-x-gbic",
|
||||
"1000base-x-sfp",
|
||||
"10gbase-x-sfpp",
|
||||
"10gbase-x-xfp",
|
||||
"10gbase-x-xenpak",
|
||||
"10gbase-x-x2",
|
||||
"25gbase-x-sfp28",
|
||||
"50gbase-x-sfp56",
|
||||
"40gbase-x-qsfpp",
|
||||
"50gbase-x-sfp28",
|
||||
"100gbase-x-cfp",
|
||||
"100gbase-x-cfp2",
|
||||
"200gbase-x-cfp2",
|
||||
"100gbase-x-cfp4",
|
||||
"100gbase-x-cxp",
|
||||
"100gbase-x-cpak",
|
||||
"100gbase-x-dsfp",
|
||||
"100gbase-x-sfpdd",
|
||||
"100gbase-x-qsfp28",
|
||||
"100gbase-x-qsfpdd",
|
||||
"200gbase-x-qsfp56",
|
||||
"200gbase-x-qsfpdd",
|
||||
"400gbase-x-qsfpdd",
|
||||
"400gbase-x-osfp",
|
||||
"400gbase-x-cdfp",
|
||||
"400gbase-x-cfp8",
|
||||
"800gbase-x-qsfpdd",
|
||||
"800gbase-x-osfp",
|
||||
"1000base-kx",
|
||||
"10gbase-kr",
|
||||
"10gbase-kx4",
|
||||
"25gbase-kr",
|
||||
"40gbase-kr4",
|
||||
"50gbase-kr",
|
||||
"100gbase-kp4",
|
||||
"100gbase-kr2",
|
||||
"100gbase-kr4",
|
||||
"ieee802.11a",
|
||||
"ieee802.11g",
|
||||
"ieee802.11n",
|
||||
"ieee802.11ac",
|
||||
"ieee802.11ad",
|
||||
"ieee802.11ax",
|
||||
"ieee802.11ay",
|
||||
"ieee802.15.1",
|
||||
"other-wireless",
|
||||
"gsm",
|
||||
"cdma",
|
||||
"lte",
|
||||
"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",
|
||||
"64gfc-qsfpp",
|
||||
"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",
|
||||
"gpon",
|
||||
"xg-pon",
|
||||
"xgs-pon",
|
||||
"ng-pon2",
|
||||
"epon",
|
||||
"10g-epon",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"poe_mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pd",
|
||||
"pse"
|
||||
]
|
||||
},
|
||||
"poe_type": {
|
||||
"type": "string",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"front-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"8p8c",
|
||||
"8p6c",
|
||||
"8p4c",
|
||||
"8p2c",
|
||||
"6p6c",
|
||||
"6p4c",
|
||||
"6p2c",
|
||||
"4p4c",
|
||||
"4p2c",
|
||||
"gg45",
|
||||
"tera-4p",
|
||||
"tera-2p",
|
||||
"tera-1p",
|
||||
"110-punch",
|
||||
"bnc",
|
||||
"f",
|
||||
"n",
|
||||
"mrj21",
|
||||
"fc",
|
||||
"lc",
|
||||
"lc-pc",
|
||||
"lc-upc",
|
||||
"lc-apc",
|
||||
"lsh",
|
||||
"lsh-pc",
|
||||
"lsh-upc",
|
||||
"lsh-apc",
|
||||
"lx5",
|
||||
"lx5-pc",
|
||||
"lx5-upc",
|
||||
"lx5-apc",
|
||||
"mpo",
|
||||
"mtrj",
|
||||
"sc",
|
||||
"sc-pc",
|
||||
"sc-upc",
|
||||
"sc-apc",
|
||||
"st",
|
||||
"cs",
|
||||
"sn",
|
||||
"sma-905",
|
||||
"sma-906",
|
||||
"urm-p2",
|
||||
"urm-p4",
|
||||
"urm-p8",
|
||||
"splice",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rear-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"8p8c",
|
||||
"8p6c",
|
||||
"8p4c",
|
||||
"8p2c",
|
||||
"6p6c",
|
||||
"6p4c",
|
||||
"6p2c",
|
||||
"4p4c",
|
||||
"4p2c",
|
||||
"gg45",
|
||||
"tera-4p",
|
||||
"tera-2p",
|
||||
"tera-1p",
|
||||
"110-punch",
|
||||
"bnc",
|
||||
"f",
|
||||
"n",
|
||||
"mrj21",
|
||||
"fc",
|
||||
"lc",
|
||||
"lc-pc",
|
||||
"lc-upc",
|
||||
"lc-apc",
|
||||
"lsh",
|
||||
"lsh-pc",
|
||||
"lsh-upc",
|
||||
"lsh-apc",
|
||||
"lx5",
|
||||
"lx5-pc",
|
||||
"lx5-upc",
|
||||
"lx5-apc",
|
||||
"mpo",
|
||||
"mtrj",
|
||||
"sc",
|
||||
"sc-pc",
|
||||
"sc-upc",
|
||||
"sc-apc",
|
||||
"st",
|
||||
"cs",
|
||||
"sn",
|
||||
"sma-905",
|
||||
"sma-906",
|
||||
"urm-p2",
|
||||
"urm-p4",
|
||||
"urm-p8",
|
||||
"splice",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -70,6 +70,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec
|
||||
|
||||
In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above).
|
||||
|
||||
### Rebuild the Device Type Definition Schema
|
||||
|
||||
Run the following command to update the device type definition validation schema:
|
||||
|
||||
```nohighlight
|
||||
./manage.py buildschema --write
|
||||
```
|
||||
|
||||
This will automatically update the schema file at `contrib/generated_schema.json`.
|
||||
|
||||
### Update Version and Changelog
|
||||
|
||||
* Update the `VERSION` constant in `settings.py` to the new release version.
|
||||
|
@ -1,23 +1,33 @@
|
||||
# NetBox v3.5
|
||||
|
||||
## v3.5.8 (FUTURE)
|
||||
## v3.5.9 (FUTURE)
|
||||
|
||||
---
|
||||
|
||||
## v3.5.8 (2023-08-15)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#10030](https://github.com/netbox-community/netbox/issues/10030) - Ship a validation schema for the device type library with each release
|
||||
* [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import
|
||||
* [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI
|
||||
* [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type
|
||||
* [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table
|
||||
* [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses
|
||||
* [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page
|
||||
* [#13442](https://github.com/netbox-community/netbox/issues/13442) - Add 200 and 400 Gbps speeds to dropdown choices on interface form
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#11578](https://github.com/netbox-community/netbox/issues/11578) - Fix schema definition for available IP & VLAN REST API endpoints
|
||||
* [#12639](https://github.com/netbox-community/netbox/issues/12639) - Raise validation error for invalid alphanumeric ranges when creating objects
|
||||
* [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links
|
||||
* [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted
|
||||
* [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view
|
||||
* [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports
|
||||
* [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms
|
||||
* [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox
|
||||
* [#13451](https://github.com/netbox-community/netbox/issues/13451) - Disable table ordering for custom link columns
|
||||
|
||||
---
|
||||
|
||||
|
@ -1143,6 +1143,8 @@ class InterfaceSpeedChoices(ChoiceSet):
|
||||
(25000000, '25 Gbps'),
|
||||
(40000000, '40 Gbps'),
|
||||
(100000000, '100 Gbps'),
|
||||
(200000000, '200 Gbps'),
|
||||
(400000000, '400 Gbps'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -55,7 +55,10 @@ class ComponentCreateForm(forms.Form):
|
||||
super().clean()
|
||||
|
||||
# Validate that all replication fields generate an equal number of values
|
||||
pattern_count = len(self.cleaned_data[self.replication_fields[0]])
|
||||
if not (patterns := self.cleaned_data.get(self.replication_fields[0])):
|
||||
return
|
||||
|
||||
pattern_count = len(patterns)
|
||||
for field_name in self.replication_fields:
|
||||
value_count = len(self.cleaned_data[field_name])
|
||||
if self.cleaned_data[field_name] and value_count != pattern_count:
|
||||
|
62
netbox/dcim/management/commands/buildschema.py
Normal file
62
netbox/dcim/management/commands/buildschema.py
Normal file
@ -0,0 +1,62 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from jinja2 import FileSystemLoader, Environment
|
||||
|
||||
from dcim.choices import *
|
||||
|
||||
TEMPLATE_FILENAME = 'devicetype_schema.jinja2'
|
||||
OUTPUT_FILENAME = 'contrib/generated_schema.json'
|
||||
|
||||
CHOICES_MAP = {
|
||||
'airflow_choices': DeviceAirflowChoices,
|
||||
'weight_unit_choices': WeightUnitChoices,
|
||||
'subdevice_role_choices': SubdeviceRoleChoices,
|
||||
'console_port_type_choices': ConsolePortTypeChoices,
|
||||
'console_server_port_type_choices': ConsolePortTypeChoices,
|
||||
'power_port_type_choices': PowerPortTypeChoices,
|
||||
'power_outlet_type_choices': PowerOutletTypeChoices,
|
||||
'power_outlet_feedleg_choices': PowerOutletFeedLegChoices,
|
||||
'interface_type_choices': InterfaceTypeChoices,
|
||||
'interface_poe_mode_choices': InterfacePoEModeChoices,
|
||||
'interface_poe_type_choices': InterfacePoETypeChoices,
|
||||
'front_port_type_choices': PortTypeChoices,
|
||||
'rear_port_type_choices': PortTypeChoices,
|
||||
}
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Generate JSON schema for validating NetBox device type definitions"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--write',
|
||||
action='store_true',
|
||||
help="Write the generated schema to file"
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
# Initialize template
|
||||
template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/schema/')
|
||||
template_env = Environment(loader=template_loader)
|
||||
template = template_env.get_template(TEMPLATE_FILENAME)
|
||||
|
||||
# Render template
|
||||
context = {
|
||||
key: json.dumps(choices.values())
|
||||
for key, choices in CHOICES_MAP.items()
|
||||
}
|
||||
rendered = template.render(**context)
|
||||
|
||||
if kwargs['write']:
|
||||
# $root/contrib/generated_schema.json
|
||||
filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME)
|
||||
with open(filename, mode='w', encoding='UTF-8') as f:
|
||||
f.write(json.dumps(json.loads(rendered), indent=4))
|
||||
f.write('\n')
|
||||
f.close()
|
||||
self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}."))
|
||||
else:
|
||||
self.stdout.write(rendered)
|
@ -1,7 +1,5 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from django.db.models.functions import Round
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django_pglocks import advisory_lock
|
||||
from drf_spectacular.utils import extend_schema
|
||||
@ -16,6 +14,7 @@ from circuits.models import Provider
|
||||
from dcim.models import Site
|
||||
from ipam import filtersets
|
||||
from ipam.models import *
|
||||
from ipam.models import L2VPN, L2VPNTermination
|
||||
from ipam.utils import get_next_available_prefix
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
from netbox.api.viewsets.mixins import ObjectValidationMixin
|
||||
@ -24,7 +23,6 @@ from netbox.constants import ADVISORY_LOCK_KEYS
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.utils import count_related
|
||||
from . import serializers
|
||||
from ipam.models import L2VPN, L2VPNTermination
|
||||
|
||||
|
||||
class IPAMRootView(APIRootView):
|
||||
@ -346,7 +344,11 @@ class AvailableASNsView(AvailableObjectsView):
|
||||
def get(self, request, pk):
|
||||
return super().get(request, pk)
|
||||
|
||||
@extend_schema(methods=["post"], responses={201: serializers.AvailableASNSerializer(many=True)})
|
||||
@extend_schema(
|
||||
methods=["post"],
|
||||
responses={201: serializers.ASNSerializer(many=True)},
|
||||
request=serializers.ASNSerializer(many=True),
|
||||
)
|
||||
def post(self, request, pk):
|
||||
return super().post(request, pk)
|
||||
|
||||
@ -395,7 +397,11 @@ class AvailablePrefixesView(AvailableObjectsView):
|
||||
def get(self, request, pk):
|
||||
return super().get(request, pk)
|
||||
|
||||
@extend_schema(methods=["post"], responses={201: serializers.PrefixSerializer(many=True)})
|
||||
@extend_schema(
|
||||
methods=["post"],
|
||||
responses={201: serializers.PrefixSerializer(many=True)},
|
||||
request=serializers.PrefixSerializer(many=True),
|
||||
)
|
||||
def post(self, request, pk):
|
||||
return super().post(request, pk)
|
||||
|
||||
@ -435,7 +441,11 @@ class AvailableIPAddressesView(AvailableObjectsView):
|
||||
def get(self, request, pk):
|
||||
return super().get(request, pk)
|
||||
|
||||
@extend_schema(methods=["post"], responses={201: serializers.IPAddressSerializer(many=True)})
|
||||
@extend_schema(
|
||||
methods=["post"],
|
||||
responses={201: serializers.IPAddressSerializer(many=True)},
|
||||
request=serializers.IPAddressSerializer(many=True),
|
||||
)
|
||||
def post(self, request, pk):
|
||||
return super().post(request, pk)
|
||||
|
||||
@ -482,6 +492,10 @@ class AvailableVLANsView(AvailableObjectsView):
|
||||
def get(self, request, pk):
|
||||
return super().get(request, pk)
|
||||
|
||||
@extend_schema(methods=["post"], responses={201: serializers.VLANSerializer(many=True)})
|
||||
@extend_schema(
|
||||
methods=["post"],
|
||||
responses={201: serializers.VLANSerializer(many=True)},
|
||||
request=serializers.VLANSerializer(many=True),
|
||||
)
|
||||
def post(self, request, pk):
|
||||
return super().post(request, pk)
|
||||
|
@ -256,7 +256,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPRange
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attriubtes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
||||
(_('Attributes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
family = forms.ChoiceField(
|
||||
|
@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.6-beta1'
|
||||
VERSION = '3.6-beta2'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
@ -511,9 +511,9 @@ class CustomLinkColumn(tables.Column):
|
||||
"""
|
||||
def __init__(self, customlink, *args, **kwargs):
|
||||
self.customlink = customlink
|
||||
kwargs['accessor'] = Accessor('pk')
|
||||
if 'verbose_name' not in kwargs:
|
||||
kwargs['verbose_name'] = customlink.name
|
||||
kwargs.setdefault('accessor', Accessor('pk'))
|
||||
kwargs.setdefault('orderable', False)
|
||||
kwargs.setdefault('verbose_name', customlink.name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
18
netbox/project-static/dist/netbox.js
vendored
18
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
2
netbox/project-static/dist/netbox.js.map
vendored
2
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
import { getElement, getElements, findFirstAdjacent } from '../util';
|
||||
import { getElements, findFirstAdjacent } from '../util';
|
||||
|
||||
/**
|
||||
* If any PK checkbox is checked, uncheck the select all table checkbox and the select all
|
||||
@ -63,29 +63,6 @@ function handleSelectAllToggle(event: Event): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize the select all confirmation checkbox state with the select all confirmation button
|
||||
* disabled state. If the select all confirmation checkbox is checked, the buttons should be
|
||||
* enabled. If not, the buttons should be disabled.
|
||||
*
|
||||
* @param event Change Event
|
||||
*/
|
||||
function handleSelectAll(event: Event): void {
|
||||
const target = event.currentTarget as HTMLInputElement;
|
||||
const selectAllBox = getElement<HTMLDivElement>('select-all-box');
|
||||
if (selectAllBox !== null) {
|
||||
for (const button of selectAllBox.querySelectorAll<HTMLButtonElement>(
|
||||
'button[type="submit"]',
|
||||
)) {
|
||||
if (target.checked) {
|
||||
button.disabled = false;
|
||||
} else {
|
||||
button.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize table select all elements.
|
||||
*/
|
||||
@ -98,9 +75,4 @@ export function initSelectAll(): void {
|
||||
for (const element of getElements<HTMLInputElement>('input[type="checkbox"][name="pk"]')) {
|
||||
element.addEventListener('change', handlePkCheck);
|
||||
}
|
||||
const selectAll = getElement<HTMLInputElement>('select-all');
|
||||
|
||||
if (selectAll !== null) {
|
||||
selectAll.addEventListener('change', handleSelectAll);
|
||||
}
|
||||
}
|
||||
|
4
netbox/templates/extras/imageattachment.html
Normal file
4
netbox/templates/extras/imageattachment.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
|
||||
{% block tabs %}
|
||||
{% endblock %}
|
93
netbox/templates/extras/schema/devicetype_schema.jinja2
Normal file
93
netbox/templates/extras/schema/devicetype_schema.jinja2
Normal file
@ -0,0 +1,93 @@
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"airflow": {
|
||||
"type": "string",
|
||||
"enum": {{ airflow_choices }}
|
||||
},
|
||||
"weight-unit": {
|
||||
"type": "string",
|
||||
"enum": {{ weight_unit_choices }}
|
||||
},
|
||||
"subdevice-role": {
|
||||
"type": "string",
|
||||
"enum": {{ subdevice_role_choices }}
|
||||
},
|
||||
"console-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ console_port_type_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"console-server-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ console_server_port_type_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"power-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ power_port_type_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"power-outlet": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ power_outlet_type_choices }}
|
||||
},
|
||||
"feed-leg": {
|
||||
"type": "string",
|
||||
"enum": {{ power_outlet_feedleg_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ interface_type_choices }}
|
||||
},
|
||||
"poe_mode": {
|
||||
"type": "string",
|
||||
"enum": {{ interface_poe_mode_choices }}
|
||||
},
|
||||
"poe_type": {
|
||||
"type": "string",
|
||||
"enum": {{ interface_poe_type_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"front-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ front_port_type_choices }}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rear-port": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": {{ rear_port_type_choices}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,9 @@ def parse_alphanumeric_range(string):
|
||||
except ValueError:
|
||||
begin, end = dash_range, dash_range
|
||||
if begin.isdigit() and end.isdigit():
|
||||
if int(begin) >= int(end):
|
||||
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||
|
||||
for n in list(range(int(begin), int(end) + 1)):
|
||||
values.append(n)
|
||||
else:
|
||||
@ -71,6 +74,10 @@ def parse_alphanumeric_range(string):
|
||||
# Not a valid range (more than a single character)
|
||||
if not len(begin) == len(end) == 1:
|
||||
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||
|
||||
if ord(begin) >= ord(end):
|
||||
raise forms.ValidationError(f'Range "{dash_range}" is invalid.')
|
||||
|
||||
for n in list(range(ord(begin), ord(end) + 1)):
|
||||
values.append(chr(n))
|
||||
return values
|
||||
|
@ -264,8 +264,9 @@ class ExpandAlphanumeric(TestCase):
|
||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), [])
|
||||
|
||||
def test_invalid_range_bounds(self):
|
||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), [])
|
||||
self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), [])
|
||||
with self.assertRaises(forms.ValidationError):
|
||||
sorted(expand_alphanumeric_pattern('r[9-8]a'))
|
||||
sorted(expand_alphanumeric_pattern('r[b-a]a'))
|
||||
|
||||
def test_invalid_range_len(self):
|
||||
with self.assertRaises(forms.ValidationError):
|
||||
|
@ -1,6 +1,7 @@
|
||||
bleach==6.0.0
|
||||
Django==4.2.4
|
||||
django-cors-headers==4.2.0
|
||||
django-debug-toolbar==4.1.0
|
||||
django-debug-toolbar==4.2.0
|
||||
django-filter==23.2
|
||||
django-graphiql-debug-toolbar==0.2.0
|
||||
django-mptt==0.14
|
||||
@ -14,7 +15,7 @@ django-taggit==4.0.0
|
||||
django-timezone-field==5.1
|
||||
djangorestframework==3.14.0
|
||||
drf-spectacular==0.26.4
|
||||
drf-spectacular-sidecar==2023.7.1
|
||||
drf-spectacular-sidecar==2023.8.1
|
||||
feedparser==6.0.10
|
||||
graphene-django==3.0.0
|
||||
gunicorn==21.2.0
|
||||
@ -24,9 +25,9 @@ mkdocs-material==9.1.21
|
||||
mkdocstrings[python-legacy]==0.22.0
|
||||
netaddr==0.8.0
|
||||
Pillow==10.0.0
|
||||
psycopg[binary,pool]==3.1.9
|
||||
psycopg[binary,pool]==3.1.10
|
||||
PyYAML==6.0.1
|
||||
sentry-sdk==1.28.1
|
||||
sentry-sdk==1.29.2
|
||||
social-auth-app-django==5.2.0
|
||||
social-auth-core[openidconnect]==4.4.2
|
||||
svgwrite==1.4.3
|
||||
|
Reference in New Issue
Block a user