mirror of
https://github.com/netbox-community/netbox.git
synced 2026-03-06 14:30:07 +01:00
Compare commits
26 Commits
20378-del-
...
20766-fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e13d89baa | ||
|
|
4961b0d334 | ||
|
|
ab06edd9f5 | ||
|
|
e787a71c1d | ||
|
|
cd8878df30 | ||
|
|
b5a9cb1762 | ||
|
|
9723a2f0ad | ||
|
|
327d08f4c2 | ||
|
|
4be476eb49 | ||
|
|
8005b56ab4 | ||
|
|
3f1654c9ba | ||
|
|
95f8fe788d | ||
|
|
5b3ff3c0e9 | ||
|
|
730d73042d | ||
|
|
6c2a6d0e90 | ||
|
|
e6a6ff7aec | ||
|
|
87ff83ef1f | ||
|
|
8522c03b71 | ||
|
|
20af97ce24 | ||
|
|
264b40a269 | ||
|
|
90712fa865 | ||
|
|
fbe76ac98a | ||
|
|
1245a9f99d | ||
|
|
78223cea03 | ||
|
|
8452222761 | ||
|
|
8a59fc733c |
@@ -15,7 +15,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.4.5
|
||||
placeholder: v4.4.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@@ -27,7 +27,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.4.5
|
||||
placeholder: v4.4.6
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "NetBox REST API",
|
||||
"version": "4.4.5",
|
||||
"version": "4.4.6",
|
||||
"license": {
|
||||
"name": "Apache v2 License"
|
||||
}
|
||||
@@ -19688,6 +19688,32 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "object_type_id",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"explode": true,
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "object_type_id__n",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"explode": true,
|
||||
"style": "form"
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"required": false,
|
||||
@@ -23626,7 +23652,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23647,7 +23673,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23661,7 +23687,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23675,7 +23701,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23689,7 +23715,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23703,7 +23729,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23717,7 +23743,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23731,7 +23757,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23745,7 +23771,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23759,7 +23785,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23773,7 +23799,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -23787,7 +23813,7 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-spec-enum-id": "c731f2793fceac04",
|
||||
"x-spec-enum-id": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -205023,6 +205049,15 @@
|
||||
"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",
|
||||
@@ -205039,8 +205074,8 @@
|
||||
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* `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": "c731f2793fceac04",
|
||||
"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": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
},
|
||||
"a_terminations": {
|
||||
@@ -205193,6 +205228,15 @@
|
||||
"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",
|
||||
@@ -205209,8 +205253,8 @@
|
||||
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* `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": "c731f2793fceac04",
|
||||
"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": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
},
|
||||
"a_terminations": {
|
||||
@@ -219171,6 +219215,10 @@
|
||||
"format": "int64",
|
||||
"nullable": true
|
||||
},
|
||||
"object": {
|
||||
"nullable": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
@@ -219264,6 +219312,7 @@
|
||||
"id",
|
||||
"job_id",
|
||||
"name",
|
||||
"object",
|
||||
"object_type",
|
||||
"status",
|
||||
"url",
|
||||
@@ -229509,6 +229558,15 @@
|
||||
"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",
|
||||
@@ -229525,8 +229583,8 @@
|
||||
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* `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": "c731f2793fceac04",
|
||||
"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": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
},
|
||||
"a_terminations": {
|
||||
@@ -249743,6 +249801,15 @@
|
||||
"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",
|
||||
@@ -249759,8 +249826,8 @@
|
||||
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* `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": "c731f2793fceac04",
|
||||
"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": "8d6d8ba53d82f066",
|
||||
"nullable": true
|
||||
},
|
||||
"a_terminations": {
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# NetBox v4.4
|
||||
|
||||
## v4.4.6 (2025-11-11)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#14171](https://github.com/netbox-community/netbox/issues/14171) - Support VLAN assignment for device & VM interfaces being bulk imported
|
||||
* [#20297](https://github.com/netbox-community/netbox/issues/20297) - Introduce additional coaxial cable types
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#20378](https://github.com/netbox-community/netbox/issues/20378) - Prevent exception when attempting to delete a data source utilized by a custom script
|
||||
* [#20645](https://github.com/netbox-community/netbox/issues/20645) - CSVChoiceField should defer to model field's default value when CSV field is empty
|
||||
* [#20647](https://github.com/netbox-community/netbox/issues/20647) - Improve handling of empty strings during bulk imports
|
||||
* [#20653](https://github.com/netbox-community/netbox/issues/20653) - Fix filtering of jobs by object type ID
|
||||
* [#20660](https://github.com/netbox-community/netbox/issues/20660) - Optimize loading of custom script modules from remote storage
|
||||
* [#20670](https://github.com/netbox-community/netbox/issues/20670) - Improve validation of related objects during bulk import
|
||||
* [#20688](https://github.com/netbox-community/netbox/issues/20688) - Suppress non-harmful "No active configuration revision found" warning message
|
||||
* [#20697](https://github.com/netbox-community/netbox/issues/20697) - Prevent duplication of signals which increment/decrement related object counts
|
||||
* [#20699](https://github.com/netbox-community/netbox/issues/20699) - Ensure proper ordering of changelog entries resulting from cascading deletions
|
||||
* [#20713](https://github.com/netbox-community/netbox/issues/20713) - Ensure a pre-change snapshot is recorded on virtual chassis members being added/removed
|
||||
* [#20721](https://github.com/netbox-community/netbox/issues/20721) - Fix breadcrumb navigation links in UI for background tasks
|
||||
* [#20738](https://github.com/netbox-community/netbox/issues/20738) - Deleting a virtual chassis should nullify the `vc_position` of all former members
|
||||
* [#20750](https://github.com/netbox-community/netbox/issues/20750) - Fix cloning of permissions when only one action is enabled
|
||||
* [#20755](https://github.com/netbox-community/netbox/issues/20755) - Prevent duplicate results under certain conditions when filtering providers
|
||||
* [#20771](https://github.com/netbox-community/netbox/issues/20771) - Comments are required when creating a new journal entry
|
||||
* [#20774](https://github.com/netbox-community/netbox/issues/20774) - Bulk action button labels should be translated
|
||||
|
||||
---
|
||||
|
||||
## v4.4.5 (2025-10-28)
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -89,8 +89,6 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(description__icontains=value) |
|
||||
Q(accounts__account__icontains=value) |
|
||||
Q(accounts__name__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.choices import *
|
||||
from core.models import Job
|
||||
from netbox.api.exceptions import SerializerNotFound
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||
from netbox.api.serializers import BaseModelSerializer
|
||||
from users.api.serializers_.users import UserSerializer
|
||||
from utilities.api import get_serializer_for_model
|
||||
|
||||
__all__ = (
|
||||
'JobSerializer',
|
||||
@@ -18,11 +23,28 @@ class JobSerializer(BaseModelSerializer):
|
||||
object_type = ContentTypeField(
|
||||
read_only=True
|
||||
)
|
||||
object = serializers.SerializerMethodField(
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled',
|
||||
'interval', 'started', 'completed', 'user', 'data', 'error', 'job_id', 'log_entries',
|
||||
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'object', 'name', 'status', 'created',
|
||||
'scheduled', 'interval', 'started', 'completed', 'user', 'data', 'error', 'job_id', 'log_entries',
|
||||
]
|
||||
brief_fields = ('url', 'created', 'completed', 'user', 'status')
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
def get_object(self, obj):
|
||||
"""
|
||||
Serialize a nested representation of the object.
|
||||
"""
|
||||
if obj.object is None:
|
||||
return None
|
||||
try:
|
||||
serializer = get_serializer_for_model(obj.object)
|
||||
except SerializerNotFound:
|
||||
return obj.object_repr
|
||||
context = {'request': self.context['request']}
|
||||
return serializer(obj.object, nested=True, context=context).data
|
||||
|
||||
@@ -80,6 +80,10 @@ class JobFilterSet(BaseFilterSet):
|
||||
method='search',
|
||||
label=_('Search'),
|
||||
)
|
||||
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ObjectType.objects.with_feature('jobs'),
|
||||
field_name='object_type_id',
|
||||
)
|
||||
object_type = ContentTypeFilter()
|
||||
created = django_filters.DateTimeFilter()
|
||||
created__before = django_filters.DateTimeFilter(
|
||||
@@ -124,7 +128,7 @@ class JobFilterSet(BaseFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
fields = ('id', 'object_type', 'object_id', 'name', 'interval', 'status', 'user', 'job_id')
|
||||
fields = ('id', 'object_type', 'object_type_id', 'object_id', 'name', 'interval', 'status', 'user', 'job_id')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
||||
@@ -70,13 +70,13 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
|
||||
model = Job
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('object_type', 'status', name=_('Attributes')),
|
||||
FieldSet('object_type_id', 'status', name=_('Attributes')),
|
||||
FieldSet(
|
||||
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
|
||||
'started__after', 'completed__before', 'completed__after', 'user', name=_('Creation')
|
||||
),
|
||||
)
|
||||
object_type = ContentTypeChoiceField(
|
||||
object_type_id = ContentTypeChoiceField(
|
||||
label=_('Object Type'),
|
||||
queryset=ObjectType.objects.with_feature('jobs'),
|
||||
required=False,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.object_actions import ObjectAction
|
||||
|
||||
|
||||
@@ -1154,7 +1154,6 @@ class VirtualChassis(PrimaryModel):
|
||||
})
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
||||
# Check for LAG interfaces split across member chassis
|
||||
interfaces = Interface.objects.filter(
|
||||
device__in=self.members.all(),
|
||||
@@ -1168,6 +1167,13 @@ class VirtualChassis(PrimaryModel):
|
||||
"interfaces."
|
||||
).format(self=self, interfaces=InterfaceSpeedChoices))
|
||||
|
||||
# Clear vc_position and vc_priority on member devices BEFORE calling super().delete()
|
||||
# This must be done here because on_delete=SET_NULL executes before pre_delete signal
|
||||
for device in self.members.all():
|
||||
device.vc_position = None
|
||||
device.vc_priority = None
|
||||
device.save()
|
||||
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.object_actions import ObjectAction
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
from django.db.models.signals import post_save, post_delete, pre_delete
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from dcim.choices import CableEndChoices, LinkStatusChoices
|
||||
@@ -85,18 +85,6 @@ def assign_virtualchassis_master(instance, created, **kwargs):
|
||||
master.save()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=VirtualChassis)
|
||||
def clear_virtualchassis_members(instance, **kwargs):
|
||||
"""
|
||||
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
||||
"""
|
||||
devices = Device.objects.filter(virtual_chassis=instance.pk)
|
||||
for device in devices:
|
||||
device.vc_position = None
|
||||
device.vc_priority = None
|
||||
device.save()
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
@@ -1031,3 +1031,92 @@ class VirtualDeviceContextTestCase(TestCase):
|
||||
vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
|
||||
with self.assertRaises(ValidationError):
|
||||
vdc2.full_clean()
|
||||
|
||||
|
||||
class VirtualChassisTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||
devicetype = DeviceType.objects.create(
|
||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
||||
)
|
||||
role = DeviceRole.objects.create(
|
||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||
)
|
||||
Device.objects.create(
|
||||
device_type=devicetype, role=role, name='TestDevice1', site=site
|
||||
)
|
||||
Device.objects.create(
|
||||
device_type=devicetype, role=role, name='TestDevice2', site=site
|
||||
)
|
||||
|
||||
def test_virtualchassis_deletion_clears_vc_position(self):
|
||||
"""
|
||||
Test that when a VirtualChassis is deleted, member devices have their
|
||||
vc_position and vc_priority fields set to None.
|
||||
"""
|
||||
devices = Device.objects.all()
|
||||
device1 = devices[0]
|
||||
device2 = devices[1]
|
||||
|
||||
# Create a VirtualChassis with two member devices
|
||||
vc = VirtualChassis.objects.create(name='Test VC', master=device1)
|
||||
|
||||
device1.virtual_chassis = vc
|
||||
device1.vc_position = 1
|
||||
device1.vc_priority = 10
|
||||
device1.save()
|
||||
|
||||
device2.virtual_chassis = vc
|
||||
device2.vc_position = 2
|
||||
device2.vc_priority = 20
|
||||
device2.save()
|
||||
|
||||
# Verify devices are members of the VC with positions set
|
||||
device1.refresh_from_db()
|
||||
device2.refresh_from_db()
|
||||
self.assertEqual(device1.virtual_chassis, vc)
|
||||
self.assertEqual(device1.vc_position, 1)
|
||||
self.assertEqual(device1.vc_priority, 10)
|
||||
self.assertEqual(device2.virtual_chassis, vc)
|
||||
self.assertEqual(device2.vc_position, 2)
|
||||
self.assertEqual(device2.vc_priority, 20)
|
||||
|
||||
# Delete the VirtualChassis
|
||||
vc.delete()
|
||||
|
||||
# Verify devices have vc_position and vc_priority set to None
|
||||
device1.refresh_from_db()
|
||||
device2.refresh_from_db()
|
||||
self.assertIsNone(device1.virtual_chassis)
|
||||
self.assertIsNone(device1.vc_position)
|
||||
self.assertIsNone(device1.vc_priority)
|
||||
self.assertIsNone(device2.virtual_chassis)
|
||||
self.assertIsNone(device2.vc_position)
|
||||
self.assertIsNone(device2.vc_priority)
|
||||
|
||||
def test_virtualchassis_duplicate_vc_position(self):
|
||||
"""
|
||||
Test that two devices cannot be assigned to the same vc_position
|
||||
within the same VirtualChassis.
|
||||
"""
|
||||
devices = Device.objects.all()
|
||||
device1 = devices[0]
|
||||
device2 = devices[1]
|
||||
|
||||
# Create a VirtualChassis
|
||||
vc = VirtualChassis.objects.create(name='Test VC')
|
||||
|
||||
# Assign first device to vc_position 1
|
||||
device1.virtual_chassis = vc
|
||||
device1.vc_position = 1
|
||||
device1.full_clean()
|
||||
device1.save()
|
||||
|
||||
# Try to assign second device to the same vc_position
|
||||
device2.virtual_chassis = vc
|
||||
device2.vc_position = 1
|
||||
with self.assertRaises(ValidationError):
|
||||
device2.full_clean()
|
||||
|
||||
@@ -986,6 +986,131 @@ inventory-items:
|
||||
ii1 = InventoryItemTemplate.objects.first()
|
||||
self.assertEqual(ii1.name, 'Inventory Item 1')
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_import_error_numbering(self):
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
'dcim.view_devicetype',
|
||||
'dcim.add_devicetype',
|
||||
'dcim.add_consoleporttemplate',
|
||||
'dcim.add_consoleserverporttemplate',
|
||||
'dcim.add_powerporttemplate',
|
||||
'dcim.add_poweroutlettemplate',
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
'dcim.add_inventoryitemtemplate',
|
||||
)
|
||||
|
||||
import_data = '''
|
||||
---
|
||||
manufacturer: Manufacturer 1
|
||||
model: TEST-2001
|
||||
slug: test-2001
|
||||
u_height: 1
|
||||
module-bays:
|
||||
- name: Module Bay 1-1
|
||||
- name: Module Bay 1-2
|
||||
---
|
||||
- manufacturer: Manufacturer 1
|
||||
model: TEST-2002
|
||||
slug: test-2002
|
||||
u_height: 1
|
||||
module-bays:
|
||||
- name: Module Bay 2-1
|
||||
- name: Module Bay 2-2
|
||||
- not_name: Module Bay 2-3
|
||||
- manufacturer: Manufacturer 1
|
||||
model: TEST-2003
|
||||
slug: test-2003
|
||||
u_height: 1
|
||||
module-bays:
|
||||
- name: Module Bay 3-1
|
||||
'''
|
||||
form_data = {
|
||||
'data': import_data,
|
||||
'format': 'yaml'
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_import_nolist(self):
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
'dcim.view_devicetype',
|
||||
'dcim.add_devicetype',
|
||||
'dcim.add_consoleporttemplate',
|
||||
'dcim.add_consoleserverporttemplate',
|
||||
'dcim.add_powerporttemplate',
|
||||
'dcim.add_poweroutlettemplate',
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
'dcim.add_inventoryitemtemplate',
|
||||
)
|
||||
|
||||
for value in ('', 'null', '3', '"My console port"', '{name: "My other console port"}'):
|
||||
with self.subTest(value=value):
|
||||
import_data = f'''
|
||||
manufacturer: Manufacturer 1
|
||||
model: TEST-3000
|
||||
slug: test-3000
|
||||
u_height: 1
|
||||
console-ports: {value}
|
||||
'''
|
||||
form_data = {
|
||||
'data': import_data,
|
||||
'format': 'yaml'
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
self.assertContains(response, "Record 1 console-ports: Must be a list.")
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_import_nodict(self):
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
'dcim.view_devicetype',
|
||||
'dcim.add_devicetype',
|
||||
'dcim.add_consoleporttemplate',
|
||||
'dcim.add_consoleserverporttemplate',
|
||||
'dcim.add_powerporttemplate',
|
||||
'dcim.add_poweroutlettemplate',
|
||||
'dcim.add_interfacetemplate',
|
||||
'dcim.add_frontporttemplate',
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
'dcim.add_inventoryitemtemplate',
|
||||
)
|
||||
|
||||
for value in ('', 'null', '3', '"My console port"', '["My other console port"]'):
|
||||
with self.subTest(value=value):
|
||||
import_data = f'''
|
||||
manufacturer: Manufacturer 1
|
||||
model: TEST-4000
|
||||
slug: test-4000
|
||||
u_height: 1
|
||||
console-ports:
|
||||
- {value}
|
||||
'''
|
||||
form_data = {
|
||||
'data': import_data,
|
||||
'format': 'yaml'
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
|
||||
self.assertHttpStatus(response, 200)
|
||||
self.assertContains(response, "Record 1 console-ports[1]: Must be a dictionary.")
|
||||
|
||||
def test_export_objects(self):
|
||||
url = reverse('dcim:devicetype_list')
|
||||
self.add_permissions('dcim.view_devicetype')
|
||||
|
||||
@@ -272,6 +272,10 @@ class JournalEntryImportForm(NetBoxModelImportForm):
|
||||
choices=JournalEntryKindChoices,
|
||||
help_text=_('The classification of entry')
|
||||
)
|
||||
comments = forms.CharField(
|
||||
label=_('Comments'),
|
||||
required=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = JournalEntry
|
||||
|
||||
@@ -793,7 +793,7 @@ class JournalEntryForm(NetBoxModelForm):
|
||||
label=_('Kind'),
|
||||
choices=JournalEntryKindChoices
|
||||
)
|
||||
comments = CommentField()
|
||||
comments = CommentField(required=True)
|
||||
|
||||
class Meta:
|
||||
model = JournalEntry
|
||||
|
||||
@@ -30,8 +30,7 @@ class CustomStoragesLoader(importlib.abc.Loader):
|
||||
return None # Use default module creation
|
||||
|
||||
def exec_module(self, module):
|
||||
storage = storages.create_storage(storages.backends["scripts"])
|
||||
with storage.open(self.filename, 'rb') as f:
|
||||
with storages["scripts"].open(self.filename, 'rb') as f:
|
||||
code = f.read()
|
||||
exec(code, module.__dict__)
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
|
||||
ordered.extend(script_objects.values())
|
||||
return ordered
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def module_scripts(self):
|
||||
|
||||
def _get_name(cls):
|
||||
|
||||
@@ -82,7 +82,7 @@ class Config:
|
||||
revision = ConfigRevision.objects.get(active=True)
|
||||
logger.debug(f"Loaded active configuration revision #{revision.pk}")
|
||||
except (ConfigRevision.DoesNotExist, ConfigRevision.MultipleObjectsReturned):
|
||||
logger.warning("No active configuration revision found - falling back to most recent")
|
||||
logger.debug("No active configuration revision found - falling back to most recent")
|
||||
revision = ConfigRevision.objects.order_by('-created').first()
|
||||
if revision is None:
|
||||
logger.debug("No previous configuration found in database; proceeding with default values")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.template import loader
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.models import ExportTemplate
|
||||
|
||||
@@ -323,7 +323,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
|
||||
class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
"""
|
||||
Import objects in bulk (CSV format).
|
||||
Import objects in bulk (CSV/JSON/YAML format).
|
||||
|
||||
Attributes:
|
||||
model_form: The form used to create each imported object
|
||||
@@ -368,7 +368,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
error_messages.append(f"Record {index} {prefix}{field_name}: {err}")
|
||||
return error_messages
|
||||
|
||||
def _save_object(self, model_form, request):
|
||||
def _save_object(self, model_form, request, parent_idx):
|
||||
_action = 'Updated' if model_form.instance.pk else 'Created'
|
||||
|
||||
# Save the primary object
|
||||
@@ -381,8 +381,25 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
# Iterate through the related object forms (if any), validating and saving each instance.
|
||||
for field_name, related_object_form in self.related_object_forms.items():
|
||||
|
||||
related_objects = model_form.data.get(field_name, list())
|
||||
if not isinstance(related_objects, list):
|
||||
raise ValidationError(
|
||||
self._compile_form_errors(
|
||||
{field_name: [_("Must be a list.")]},
|
||||
index=parent_idx
|
||||
)
|
||||
)
|
||||
|
||||
related_obj_pks = []
|
||||
for i, rel_obj_data in enumerate(model_form.data.get(field_name, list())):
|
||||
for i, rel_obj_data in enumerate(related_objects, start=1):
|
||||
if not isinstance(rel_obj_data, dict):
|
||||
raise ValidationError(
|
||||
self._compile_form_errors(
|
||||
{f'{field_name}[{i}]': [_("Must be a dictionary.")]},
|
||||
index=parent_idx,
|
||||
)
|
||||
)
|
||||
|
||||
rel_obj_data = self.prep_related_object_data(obj, rel_obj_data)
|
||||
f = related_object_form(rel_obj_data)
|
||||
|
||||
@@ -396,7 +413,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
else:
|
||||
# Replicate errors on the related object form to the import form for display and abort
|
||||
raise ValidationError(
|
||||
self._compile_form_errors(f.errors, index=i, prefix=f'{field_name}[{i}]')
|
||||
self._compile_form_errors(f.errors, index=parent_idx, prefix=f'{field_name}[{i}]')
|
||||
)
|
||||
|
||||
# Enforce object-level permissions on related objects
|
||||
@@ -439,8 +456,12 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
try:
|
||||
instance = prefetched_objects[object_id]
|
||||
except KeyError:
|
||||
form.add_error('data', _("Row {i}: Object with ID {id} does not exist").format(i=i, id=object_id))
|
||||
raise ValidationError('')
|
||||
raise ValidationError(
|
||||
self._compile_form_errors(
|
||||
{'id': [_("Object with ID {id} does not exist").format(id=object_id)]},
|
||||
index=i
|
||||
)
|
||||
)
|
||||
|
||||
# Take a snapshot for change logging
|
||||
if instance.pk and hasattr(instance, 'snapshot'):
|
||||
@@ -481,7 +502,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
restrict_form_fields(model_form, request.user)
|
||||
|
||||
if model_form.is_valid():
|
||||
obj = self._save_object(model_form, request)
|
||||
obj = self._save_object(model_form, request, i)
|
||||
saved_objects.append(obj)
|
||||
else:
|
||||
# Raise model form errors
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"gridstack": "12.3.3",
|
||||
"htmx.org": "2.0.8",
|
||||
"query-string": "9.3.1",
|
||||
"sass": "1.93.2",
|
||||
"sass": "1.94.0",
|
||||
"tom-select": "2.4.3",
|
||||
"typeface-inter": "3.18.1",
|
||||
"typeface-roboto-mono": "1.1.13"
|
||||
|
||||
@@ -3190,10 +3190,10 @@ safe-regex-test@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
is-regex "^1.2.1"
|
||||
|
||||
sass@1.93.2:
|
||||
version "1.93.2"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.93.2.tgz#e97d225d60f59a3b3dbb6d2ae3c1b955fd1f2cd1"
|
||||
integrity sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==
|
||||
sass@1.94.0:
|
||||
version "1.94.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.94.0.tgz#a04198d8940358ca6ad537d2074051edbbe7c1a7"
|
||||
integrity sha512-Dqh7SiYcaFtdv5Wvku6QgS5IGPm281L+ZtVD1U2FJa7Q0EFRlq8Z3sjYtz6gYObsYThUOz9ArwFqPZx+1azILQ==
|
||||
dependencies:
|
||||
chokidar "^4.0.0"
|
||||
immutable "^5.0.2"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version: "4.4.5"
|
||||
version: "4.4.6"
|
||||
edition: "Community"
|
||||
published: "2025-10-28"
|
||||
published: "2025-11-11"
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<p>
|
||||
<i class="mdi mdi-alert"></i>
|
||||
<strong>{% trans "Missing required packages" %}.</strong>
|
||||
{% blocktrans trimmed %}
|
||||
{% blocktrans trimmed with req_file="requirements.txt" local_req_file="local_requirements.txt" pip_cmd="pip freeze" %}
|
||||
This installation of NetBox might be missing one or more required Python packages. These packages are listed in
|
||||
<code>requirements.txt</code> and <code>local_requirements.txt</code>, and are normally installed as part of the
|
||||
installation or upgrade process. To verify installed packages, run <code>pip freeze</code> from the console and
|
||||
<code>{{ req_file }}</code> and <code>{{ local_req_file }}</code>, and are normally installed as part of the
|
||||
installation or upgrade process. To verify installed packages, run <code>{{ pip_cmd }}</code> from the console and
|
||||
compare the output to the list of required packages.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
<p>
|
||||
<i class="mdi mdi-alert"></i>
|
||||
<strong>{% trans "Database migrations missing" %}.</strong>
|
||||
{% blocktrans trimmed %}
|
||||
{% blocktrans trimmed with command="python3 manage.py migrate" %}
|
||||
When upgrading to a new NetBox release, the upgrade script must be run to apply any new database migrations. You
|
||||
can run migrations manually by executing <code>python3 manage.py migrate</code> from the command line.
|
||||
can run migrations manually by executing <code>{{ command }}</code> from the command line.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<i class="mdi mdi-alert"></i>
|
||||
<strong>{% trans "Unsupported PostgreSQL version" %}.</strong>
|
||||
{% blocktrans trimmed %}
|
||||
{% blocktrans trimmed with sql_query="SELECT VERSION()" %}
|
||||
Ensure that PostgreSQL version 14 or later is in use. You can check this by connecting to the database using
|
||||
NetBox's credentials and issuing a query for <code>SELECT VERSION()</code>.
|
||||
NetBox's credentials and issuing a query for <code>{{ sql_query }}</code>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock message %}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<p>{% trans "Check the following" %}:</p>
|
||||
<ul>
|
||||
<li class="tip">
|
||||
{% blocktrans trimmed %}
|
||||
<code>manage.py collectstatic</code> was run during the most recent upgrade. This installs the most
|
||||
{% blocktrans trimmed with command="manage.py collectstatic" %}
|
||||
<code>{{ command }}</code> was run during the most recent upgrade. This installs the most
|
||||
recent iteration of each static file into the static root path.
|
||||
{% endblocktrans %}
|
||||
</li>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-11-01 05:01+0000\n"
|
||||
"POT-Creation-Date: 2025-11-11 05:01+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -84,9 +84,9 @@ msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20
|
||||
#: netbox/dcim/choices.py:102 netbox/dcim/choices.py:204
|
||||
#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1836
|
||||
#: netbox/dcim/choices.py:1894 netbox/dcim/choices.py:1961
|
||||
#: netbox/dcim/choices.py:1983 netbox/virtualization/choices.py:20
|
||||
#: netbox/dcim/choices.py:257 netbox/dcim/choices.py:1854
|
||||
#: netbox/dcim/choices.py:1912 netbox/dcim/choices.py:1979
|
||||
#: netbox/dcim/choices.py:2001 netbox/virtualization/choices.py:20
|
||||
#: netbox/virtualization/choices.py:46 netbox/vpn/choices.py:18
|
||||
#: netbox/vpn/choices.py:281
|
||||
msgid "Planned"
|
||||
@@ -100,8 +100,8 @@ msgstr ""
|
||||
#: netbox/core/tables/tasks.py:23 netbox/dcim/choices.py:22
|
||||
#: netbox/dcim/choices.py:103 netbox/dcim/choices.py:155
|
||||
#: netbox/dcim/choices.py:203 netbox/dcim/choices.py:256
|
||||
#: netbox/dcim/choices.py:1893 netbox/dcim/choices.py:1960
|
||||
#: netbox/dcim/choices.py:1982 netbox/extras/tables/tables.py:598
|
||||
#: netbox/dcim/choices.py:1911 netbox/dcim/choices.py:1978
|
||||
#: netbox/dcim/choices.py:2000 netbox/extras/tables/tables.py:598
|
||||
#: netbox/ipam/choices.py:31 netbox/ipam/choices.py:49
|
||||
#: netbox/ipam/choices.py:69 netbox/ipam/choices.py:154
|
||||
#: netbox/templates/extras/configcontext.html:29
|
||||
@@ -113,8 +113,8 @@ msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:24 netbox/dcim/choices.py:202
|
||||
#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:1892
|
||||
#: netbox/dcim/choices.py:1962 netbox/dcim/choices.py:1981
|
||||
#: netbox/dcim/choices.py:255 netbox/dcim/choices.py:1910
|
||||
#: netbox/dcim/choices.py:1980 netbox/dcim/choices.py:1999
|
||||
#: netbox/virtualization/choices.py:24 netbox/virtualization/choices.py:44
|
||||
msgid "Offline"
|
||||
msgstr ""
|
||||
@@ -127,7 +127,7 @@ msgstr ""
|
||||
msgid "Decommissioned"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1905
|
||||
#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1923
|
||||
#: netbox/dcim/tables/devices.py:1178 netbox/templates/dcim/interface.html:135
|
||||
#: netbox/templates/virtualization/vminterface.html:83
|
||||
#: netbox/tenancy/choices.py:17
|
||||
@@ -160,8 +160,8 @@ msgstr ""
|
||||
msgid "Spoke"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:37 netbox/circuits/filtersets.py:204
|
||||
#: netbox/circuits/filtersets.py:284 netbox/dcim/base_filtersets.py:22
|
||||
#: netbox/circuits/filtersets.py:37 netbox/circuits/filtersets.py:202
|
||||
#: netbox/circuits/filtersets.py:282 netbox/dcim/base_filtersets.py:22
|
||||
#: netbox/dcim/filtersets.py:101 netbox/dcim/filtersets.py:155
|
||||
#: netbox/dcim/filtersets.py:215 netbox/dcim/filtersets.py:336
|
||||
#: netbox/dcim/filtersets.py:467 netbox/dcim/filtersets.py:1108
|
||||
@@ -172,8 +172,8 @@ msgstr ""
|
||||
msgid "Region (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:44 netbox/circuits/filtersets.py:211
|
||||
#: netbox/circuits/filtersets.py:291 netbox/dcim/base_filtersets.py:29
|
||||
#: netbox/circuits/filtersets.py:44 netbox/circuits/filtersets.py:209
|
||||
#: netbox/circuits/filtersets.py:289 netbox/dcim/base_filtersets.py:29
|
||||
#: netbox/dcim/filtersets.py:108 netbox/dcim/filtersets.py:161
|
||||
#: netbox/dcim/filtersets.py:222 netbox/dcim/filtersets.py:343
|
||||
#: netbox/dcim/filtersets.py:474 netbox/dcim/filtersets.py:1115
|
||||
@@ -185,8 +185,8 @@ msgstr ""
|
||||
msgid "Region (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:50 netbox/circuits/filtersets.py:217
|
||||
#: netbox/circuits/filtersets.py:297 netbox/dcim/base_filtersets.py:35
|
||||
#: netbox/circuits/filtersets.py:50 netbox/circuits/filtersets.py:215
|
||||
#: netbox/circuits/filtersets.py:295 netbox/dcim/base_filtersets.py:35
|
||||
#: netbox/dcim/filtersets.py:131 netbox/dcim/filtersets.py:228
|
||||
#: netbox/dcim/filtersets.py:349 netbox/dcim/filtersets.py:480
|
||||
#: netbox/dcim/filtersets.py:1121 netbox/dcim/filtersets.py:1442
|
||||
@@ -197,8 +197,8 @@ msgstr ""
|
||||
msgid "Site group (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:57 netbox/circuits/filtersets.py:224
|
||||
#: netbox/circuits/filtersets.py:304 netbox/dcim/base_filtersets.py:42
|
||||
#: netbox/circuits/filtersets.py:57 netbox/circuits/filtersets.py:222
|
||||
#: netbox/circuits/filtersets.py:302 netbox/dcim/base_filtersets.py:42
|
||||
#: netbox/dcim/filtersets.py:138 netbox/dcim/filtersets.py:235
|
||||
#: netbox/dcim/filtersets.py:356 netbox/dcim/filtersets.py:487
|
||||
#: netbox/dcim/filtersets.py:1128 netbox/dcim/filtersets.py:1449
|
||||
@@ -258,8 +258,8 @@ msgstr ""
|
||||
msgid "Site"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:68 netbox/circuits/filtersets.py:235
|
||||
#: netbox/circuits/filtersets.py:315 netbox/dcim/base_filtersets.py:53
|
||||
#: netbox/circuits/filtersets.py:68 netbox/circuits/filtersets.py:233
|
||||
#: netbox/circuits/filtersets.py:313 netbox/dcim/base_filtersets.py:53
|
||||
#: netbox/dcim/filtersets.py:245 netbox/dcim/filtersets.py:366
|
||||
#: netbox/dcim/filtersets.py:461 netbox/extras/filtersets.py:668
|
||||
#: netbox/ipam/filtersets.py:257 netbox/ipam/filtersets.py:972
|
||||
@@ -278,44 +278,44 @@ msgstr ""
|
||||
msgid "ASN"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:101 netbox/circuits/filtersets.py:128
|
||||
#: netbox/circuits/filtersets.py:162 netbox/circuits/filtersets.py:338
|
||||
#: netbox/circuits/filtersets.py:406 netbox/circuits/filtersets.py:482
|
||||
#: netbox/circuits/filtersets.py:550 netbox/ipam/filtersets.py:262
|
||||
#: netbox/circuits/filtersets.py:99 netbox/circuits/filtersets.py:126
|
||||
#: netbox/circuits/filtersets.py:160 netbox/circuits/filtersets.py:336
|
||||
#: netbox/circuits/filtersets.py:404 netbox/circuits/filtersets.py:480
|
||||
#: netbox/circuits/filtersets.py:548 netbox/ipam/filtersets.py:262
|
||||
msgid "Provider (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:107 netbox/circuits/filtersets.py:134
|
||||
#: netbox/circuits/filtersets.py:168 netbox/circuits/filtersets.py:344
|
||||
#: netbox/circuits/filtersets.py:488 netbox/circuits/filtersets.py:556
|
||||
#: netbox/circuits/filtersets.py:105 netbox/circuits/filtersets.py:132
|
||||
#: netbox/circuits/filtersets.py:166 netbox/circuits/filtersets.py:342
|
||||
#: netbox/circuits/filtersets.py:486 netbox/circuits/filtersets.py:554
|
||||
#: netbox/ipam/filtersets.py:268
|
||||
msgid "Provider (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:173 netbox/circuits/filtersets.py:493
|
||||
#: netbox/circuits/filtersets.py:561
|
||||
#: netbox/circuits/filtersets.py:171 netbox/circuits/filtersets.py:491
|
||||
#: netbox/circuits/filtersets.py:559
|
||||
msgid "Provider account (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:179 netbox/circuits/filtersets.py:499
|
||||
#: netbox/circuits/filtersets.py:567
|
||||
#: netbox/circuits/filtersets.py:177 netbox/circuits/filtersets.py:497
|
||||
#: netbox/circuits/filtersets.py:565
|
||||
msgid "Provider account (account)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:184 netbox/circuits/filtersets.py:503
|
||||
#: netbox/circuits/filtersets.py:572
|
||||
#: netbox/circuits/filtersets.py:182 netbox/circuits/filtersets.py:501
|
||||
#: netbox/circuits/filtersets.py:570
|
||||
msgid "Provider network (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:188
|
||||
#: netbox/circuits/filtersets.py:186
|
||||
msgid "Circuit type (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:194
|
||||
#: netbox/circuits/filtersets.py:192
|
||||
msgid "Circuit type (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:229 netbox/circuits/filtersets.py:309
|
||||
#: netbox/circuits/filtersets.py:227 netbox/circuits/filtersets.py:307
|
||||
#: netbox/dcim/base_filtersets.py:47 netbox/dcim/filtersets.py:239
|
||||
#: netbox/dcim/filtersets.py:360 netbox/dcim/filtersets.py:455
|
||||
#: netbox/dcim/filtersets.py:1132 netbox/dcim/filtersets.py:1454
|
||||
@@ -326,7 +326,7 @@ msgstr ""
|
||||
msgid "Site (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:239 netbox/circuits/filtersets.py:321
|
||||
#: netbox/circuits/filtersets.py:237 netbox/circuits/filtersets.py:319
|
||||
#: netbox/dcim/base_filtersets.py:59 netbox/dcim/filtersets.py:261
|
||||
#: netbox/dcim/filtersets.py:372 netbox/dcim/filtersets.py:493
|
||||
#: netbox/dcim/filtersets.py:1144 netbox/dcim/filtersets.py:1465
|
||||
@@ -334,14 +334,14 @@ msgstr ""
|
||||
msgid "Location (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:244 netbox/circuits/filtersets.py:248
|
||||
#: netbox/circuits/filtersets.py:242 netbox/circuits/filtersets.py:246
|
||||
msgid "Termination A (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:273 netbox/circuits/filtersets.py:375
|
||||
#: netbox/circuits/filtersets.py:537 netbox/core/filtersets.py:81
|
||||
#: netbox/core/filtersets.py:141 netbox/core/filtersets.py:166
|
||||
#: netbox/core/filtersets.py:205 netbox/dcim/filtersets.py:787
|
||||
#: netbox/circuits/filtersets.py:271 netbox/circuits/filtersets.py:373
|
||||
#: netbox/circuits/filtersets.py:535 netbox/core/filtersets.py:81
|
||||
#: netbox/core/filtersets.py:145 netbox/core/filtersets.py:170
|
||||
#: netbox/core/filtersets.py:209 netbox/dcim/filtersets.py:787
|
||||
#: netbox/dcim/filtersets.py:1521 netbox/dcim/filtersets.py:2626
|
||||
#: netbox/extras/filtersets.py:45 netbox/extras/filtersets.py:67
|
||||
#: netbox/extras/filtersets.py:96 netbox/extras/filtersets.py:136
|
||||
@@ -364,7 +364,7 @@ msgstr ""
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:277 netbox/circuits/forms/bulk_edit.py:195
|
||||
#: netbox/circuits/filtersets.py:275 netbox/circuits/forms/bulk_edit.py:195
|
||||
#: netbox/circuits/forms/bulk_edit.py:284
|
||||
#: netbox/circuits/forms/bulk_import.py:128
|
||||
#: netbox/circuits/forms/filtersets.py:224
|
||||
@@ -383,7 +383,7 @@ msgstr ""
|
||||
msgid "Circuit"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:328 netbox/dcim/base_filtersets.py:66
|
||||
#: netbox/circuits/filtersets.py:326 netbox/dcim/base_filtersets.py:66
|
||||
#: netbox/dcim/filtersets.py:268 netbox/dcim/filtersets.py:379
|
||||
#: netbox/dcim/filtersets.py:500 netbox/dcim/filtersets.py:1151
|
||||
#: netbox/dcim/filtersets.py:1471 netbox/dcim/filtersets.py:1569
|
||||
@@ -391,47 +391,47 @@ msgstr ""
|
||||
msgid "Location (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:333
|
||||
#: netbox/circuits/filtersets.py:331
|
||||
msgid "ProviderNetwork (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:381
|
||||
#: netbox/circuits/filtersets.py:379
|
||||
msgid "Circuit (CID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:386
|
||||
#: netbox/circuits/filtersets.py:384
|
||||
msgid "Circuit (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:391
|
||||
#: netbox/circuits/filtersets.py:389
|
||||
msgid "Virtual circuit (CID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:396 netbox/dcim/filtersets.py:2056
|
||||
#: netbox/circuits/filtersets.py:394 netbox/dcim/filtersets.py:2056
|
||||
msgid "Virtual circuit (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:401
|
||||
#: netbox/circuits/filtersets.py:399
|
||||
msgid "Provider (name)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:410
|
||||
#: netbox/circuits/filtersets.py:408
|
||||
msgid "Circuit group (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:416
|
||||
#: netbox/circuits/filtersets.py:414
|
||||
msgid "Circuit group (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:507
|
||||
#: netbox/circuits/filtersets.py:505
|
||||
msgid "Virtual circuit type (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:513
|
||||
#: netbox/circuits/filtersets.py:511
|
||||
msgid "Virtual circuit type (slug)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:541 netbox/circuits/forms/bulk_edit.py:355
|
||||
#: netbox/circuits/filtersets.py:539 netbox/circuits/forms/bulk_edit.py:355
|
||||
#: netbox/circuits/forms/bulk_import.py:249
|
||||
#: netbox/circuits/forms/filtersets.py:373
|
||||
#: netbox/circuits/forms/filtersets.py:379
|
||||
@@ -443,7 +443,7 @@ msgstr ""
|
||||
msgid "Virtual circuit"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/filtersets.py:577 netbox/dcim/filtersets.py:1361
|
||||
#: netbox/circuits/filtersets.py:575 netbox/dcim/filtersets.py:1361
|
||||
#: netbox/dcim/filtersets.py:1796 netbox/ipam/filtersets.py:628
|
||||
#: netbox/vpn/filtersets.py:102 netbox/vpn/filtersets.py:404
|
||||
msgid "Interface (ID)"
|
||||
@@ -1469,7 +1469,7 @@ msgstr ""
|
||||
#: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:52
|
||||
#: netbox/dcim/models/device_components.py:488
|
||||
#: netbox/dcim/models/device_components.py:1319
|
||||
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1188
|
||||
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1194
|
||||
#: netbox/dcim/models/modules.py:210 netbox/dcim/models/power.py:94
|
||||
#: netbox/dcim/models/racks.py:294 netbox/dcim/models/racks.py:677
|
||||
#: netbox/dcim/models/sites.py:154 netbox/dcim/models/sites.py:270
|
||||
@@ -1603,7 +1603,7 @@ msgstr ""
|
||||
#: netbox/core/models/jobs.py:56
|
||||
#: netbox/dcim/models/device_component_templates.py:44
|
||||
#: netbox/dcim/models/device_components.py:53 netbox/dcim/models/devices.py:524
|
||||
#: netbox/dcim/models/devices.py:1120 netbox/dcim/models/devices.py:1183
|
||||
#: netbox/dcim/models/devices.py:1120 netbox/dcim/models/devices.py:1189
|
||||
#: netbox/dcim/models/modules.py:32 netbox/dcim/models/power.py:38
|
||||
#: netbox/dcim/models/power.py:89 netbox/dcim/models/racks.py:263
|
||||
#: netbox/dcim/models/sites.py:142 netbox/extras/models/configs.py:36
|
||||
@@ -1880,13 +1880,13 @@ msgstr ""
|
||||
#: netbox/dcim/tables/racks.py:148 netbox/dcim/tables/racks.py:236
|
||||
#: netbox/dcim/tables/sites.py:40 netbox/dcim/tables/sites.py:74
|
||||
#: netbox/dcim/tables/sites.py:121 netbox/dcim/tables/sites.py:179
|
||||
#: netbox/extras/tables/tables.py:702 netbox/ipam/tables/asn.py:69
|
||||
#: netbox/ipam/tables/fhrp.py:34 netbox/ipam/tables/ip.py:83
|
||||
#: netbox/ipam/tables/ip.py:227 netbox/ipam/tables/ip.py:286
|
||||
#: netbox/ipam/tables/ip.py:355 netbox/ipam/tables/services.py:25
|
||||
#: netbox/ipam/tables/services.py:55 netbox/ipam/tables/vlans.py:124
|
||||
#: netbox/ipam/tables/vrfs.py:47 netbox/ipam/tables/vrfs.py:72
|
||||
#: netbox/templates/dcim/htmx/cable_edit.html:90
|
||||
#: netbox/extras/forms/bulk_import.py:276 netbox/extras/tables/tables.py:702
|
||||
#: netbox/ipam/tables/asn.py:69 netbox/ipam/tables/fhrp.py:34
|
||||
#: netbox/ipam/tables/ip.py:83 netbox/ipam/tables/ip.py:227
|
||||
#: netbox/ipam/tables/ip.py:286 netbox/ipam/tables/ip.py:355
|
||||
#: netbox/ipam/tables/services.py:25 netbox/ipam/tables/services.py:55
|
||||
#: netbox/ipam/tables/vlans.py:124 netbox/ipam/tables/vrfs.py:47
|
||||
#: netbox/ipam/tables/vrfs.py:72 netbox/templates/dcim/htmx/cable_edit.html:90
|
||||
#: netbox/templates/generic/bulk_edit.html:86
|
||||
#: netbox/templates/inc/panels/comments.html:5
|
||||
#: netbox/tenancy/tables/contacts.py:35 netbox/tenancy/tables/contacts.py:76
|
||||
@@ -2084,7 +2084,7 @@ msgstr ""
|
||||
#: netbox/core/choices.py:22 netbox/core/choices.py:59
|
||||
#: netbox/core/constants.py:21 netbox/core/tables/tasks.py:35
|
||||
#: netbox/dcim/choices.py:206 netbox/dcim/choices.py:259
|
||||
#: netbox/dcim/choices.py:1895 netbox/dcim/choices.py:1985
|
||||
#: netbox/dcim/choices.py:1913 netbox/dcim/choices.py:2003
|
||||
#: netbox/virtualization/choices.py:48
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
@@ -2243,19 +2243,19 @@ msgstr ""
|
||||
msgid "Data source (name)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/filtersets.py:176 netbox/dcim/filtersets.py:508
|
||||
#: netbox/core/filtersets.py:180 netbox/dcim/filtersets.py:508
|
||||
#: netbox/extras/filtersets.py:292 netbox/extras/filtersets.py:344
|
||||
#: netbox/extras/filtersets.py:389 netbox/extras/filtersets.py:411
|
||||
#: netbox/extras/filtersets.py:475 netbox/users/filtersets.py:28
|
||||
msgid "User (ID)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/filtersets.py:182
|
||||
#: netbox/core/filtersets.py:186
|
||||
msgid "User name"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/bulk_edit.py:26 netbox/core/forms/filtersets.py:43
|
||||
#: netbox/core/tables/data.py:27 netbox/dcim/choices.py:1943
|
||||
#: netbox/core/tables/data.py:27 netbox/dcim/choices.py:1961
|
||||
#: netbox/dcim/forms/bulk_edit.py:1211 netbox/dcim/forms/bulk_edit.py:1492
|
||||
#: netbox/dcim/forms/filtersets.py:1458 netbox/dcim/tables/devices.py:596
|
||||
#: netbox/dcim/tables/devicetypes.py:231 netbox/extras/forms/bulk_edit.py:127
|
||||
@@ -2444,7 +2444,7 @@ msgstr ""
|
||||
msgid "Rack Elevations"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1814
|
||||
#: netbox/core/forms/model_forms.py:160 netbox/dcim/choices.py:1832
|
||||
#: netbox/dcim/forms/bulk_edit.py:1054 netbox/dcim/forms/bulk_edit.py:1446
|
||||
#: netbox/dcim/forms/bulk_edit.py:1467 netbox/dcim/tables/racks.py:161
|
||||
#: netbox/netbox/navigation/menu.py:313 netbox/netbox/navigation/menu.py:317
|
||||
@@ -2550,7 +2550,7 @@ msgid "Change logging is not supported for this object type ({type})."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/config.py:21 netbox/core/models/data.py:269
|
||||
#: netbox/core/models/files.py:30 netbox/core/models/jobs.py:60
|
||||
#: netbox/core/models/files.py:29 netbox/core/models/jobs.py:60
|
||||
#: netbox/extras/models/models.py:839 netbox/extras/models/notifications.py:39
|
||||
#: netbox/extras/models/notifications.py:195
|
||||
#: netbox/netbox/models/features.py:61 netbox/users/models/tokens.py:32
|
||||
@@ -2665,7 +2665,7 @@ msgid ""
|
||||
"installed: "
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/data.py:273 netbox/core/models/files.py:34
|
||||
#: netbox/core/models/data.py:273 netbox/core/models/files.py:33
|
||||
#: netbox/netbox/models/features.py:67
|
||||
msgid "last updated"
|
||||
msgstr ""
|
||||
@@ -2710,27 +2710,27 @@ msgstr ""
|
||||
msgid "auto sync records"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:40
|
||||
#: netbox/core/models/files.py:39
|
||||
msgid "file root"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:45
|
||||
#: netbox/core/models/files.py:44
|
||||
msgid "file path"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:47
|
||||
#: netbox/core/models/files.py:46
|
||||
msgid "File path relative to the designated root path"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:61
|
||||
#: netbox/core/models/files.py:60
|
||||
msgid "managed file"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:62
|
||||
#: netbox/core/models/files.py:61
|
||||
msgid "managed files"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/models/files.py:112
|
||||
#: netbox/core/models/files.py:108
|
||||
#, python-brace-format
|
||||
msgid "A {model} with this file path already exists ({path})."
|
||||
msgstr ""
|
||||
@@ -3082,8 +3082,8 @@ msgid "Staging"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:23 netbox/dcim/choices.py:208
|
||||
#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1837
|
||||
#: netbox/dcim/choices.py:1986 netbox/virtualization/choices.py:23
|
||||
#: netbox/dcim/choices.py:260 netbox/dcim/choices.py:1855
|
||||
#: netbox/dcim/choices.py:2004 netbox/virtualization/choices.py:23
|
||||
#: netbox/virtualization/choices.py:49 netbox/vpn/choices.py:282
|
||||
msgid "Decommissioning"
|
||||
msgstr ""
|
||||
@@ -3148,7 +3148,7 @@ msgstr ""
|
||||
msgid "Millimeters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1859
|
||||
#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1877
|
||||
msgid "Inches"
|
||||
msgstr ""
|
||||
|
||||
@@ -3232,7 +3232,7 @@ msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:205 netbox/dcim/choices.py:258
|
||||
#: netbox/dcim/choices.py:1984 netbox/virtualization/choices.py:47
|
||||
#: netbox/dcim/choices.py:2002 netbox/virtualization/choices.py:47
|
||||
msgid "Staged"
|
||||
msgstr ""
|
||||
|
||||
@@ -3469,80 +3469,80 @@ msgstr ""
|
||||
msgid "Fiber Optic"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1695 netbox/dcim/choices.py:1820
|
||||
#: netbox/dcim/choices.py:1695 netbox/dcim/choices.py:1838
|
||||
msgid "USB"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1764
|
||||
#: netbox/dcim/choices.py:1773
|
||||
msgid "Copper - Twisted Pair (UTP/STP)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1778
|
||||
#: netbox/dcim/choices.py:1787
|
||||
msgid "Copper - Twinax (DAC)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1785
|
||||
#: netbox/dcim/choices.py:1794
|
||||
msgid "Copper - Coaxial"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1791
|
||||
#: netbox/dcim/choices.py:1809
|
||||
msgid "Fiber - Multimode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1802
|
||||
#: netbox/dcim/choices.py:1820
|
||||
msgid "Fiber - Single-mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1810
|
||||
#: netbox/dcim/choices.py:1828
|
||||
msgid "Fiber - Other"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1835 netbox/dcim/forms/filtersets.py:1305
|
||||
#: netbox/dcim/choices.py:1853 netbox/dcim/forms/filtersets.py:1305
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1854 netbox/netbox/choices.py:177
|
||||
#: netbox/dcim/choices.py:1872 netbox/netbox/choices.py:177
|
||||
msgid "Kilometers"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1855 netbox/netbox/choices.py:178
|
||||
#: netbox/dcim/choices.py:1873 netbox/netbox/choices.py:178
|
||||
#: netbox/templates/dcim/cable_trace.html:65
|
||||
msgid "Meters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1856
|
||||
#: netbox/dcim/choices.py:1874
|
||||
msgid "Centimeters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1857 netbox/netbox/choices.py:179
|
||||
#: netbox/dcim/choices.py:1875 netbox/netbox/choices.py:179
|
||||
msgid "Miles"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1858 netbox/netbox/choices.py:180
|
||||
#: netbox/dcim/choices.py:1876 netbox/netbox/choices.py:180
|
||||
#: netbox/templates/dcim/cable_trace.html:66
|
||||
msgid "Feet"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1906
|
||||
#: netbox/dcim/choices.py:1924
|
||||
msgid "Redundant"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1927
|
||||
#: netbox/dcim/choices.py:1945
|
||||
msgid "Single phase"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1928
|
||||
#: netbox/dcim/choices.py:1946
|
||||
msgid "Three-phase"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1944 netbox/extras/choices.py:53
|
||||
#: netbox/dcim/choices.py:1962 netbox/extras/choices.py:53
|
||||
#: netbox/netbox/preferences.py:32 netbox/netbox/preferences.py:55
|
||||
#: netbox/netbox/preferences.py:80 netbox/templates/extras/customfield.html:78
|
||||
#: netbox/vpn/choices.py:20 netbox/wireless/choices.py:27
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1945
|
||||
#: netbox/dcim/choices.py:1963
|
||||
msgid "Faulty"
|
||||
msgstr ""
|
||||
|
||||
@@ -3815,8 +3815,8 @@ msgstr ""
|
||||
|
||||
#: netbox/dcim/filtersets.py:1197 netbox/dcim/forms/filtersets.py:848
|
||||
#: netbox/dcim/forms/filtersets.py:1473 netbox/dcim/forms/filtersets.py:1688
|
||||
#: netbox/dcim/forms/model_forms.py:1899 netbox/dcim/models/devices.py:1284
|
||||
#: netbox/dcim/models/devices.py:1304 netbox/virtualization/filtersets.py:201
|
||||
#: netbox/dcim/forms/model_forms.py:1899 netbox/dcim/models/devices.py:1290
|
||||
#: netbox/dcim/models/devices.py:1310 netbox/virtualization/filtersets.py:201
|
||||
#: netbox/virtualization/filtersets.py:273
|
||||
#: netbox/virtualization/forms/filtersets.py:178
|
||||
#: netbox/virtualization/forms/filtersets.py:231
|
||||
@@ -6857,12 +6857,12 @@ msgstr ""
|
||||
msgid "rack face"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1204
|
||||
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1210
|
||||
#: netbox/virtualization/models/virtualmachines.py:94
|
||||
msgid "primary IPv4"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1212
|
||||
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1218
|
||||
#: netbox/virtualization/models/virtualmachines.py:102
|
||||
msgid "primary IPv6"
|
||||
msgstr ""
|
||||
@@ -7023,55 +7023,55 @@ msgstr ""
|
||||
msgid "The selected master ({master}) is not assigned to this virtual chassis."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1167
|
||||
#: netbox/dcim/models/devices.py:1166
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Unable to delete virtual chassis {self}. There are member interfaces which "
|
||||
"form a cross-chassis LAG interfaces."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1193 netbox/vpn/models/l2vpn.py:42
|
||||
#: netbox/dcim/models/devices.py:1199 netbox/vpn/models/l2vpn.py:42
|
||||
msgid "identifier"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1194
|
||||
#: netbox/dcim/models/devices.py:1200
|
||||
msgid "Numeric identifier unique to the parent device"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1222 netbox/extras/models/customfields.py:231
|
||||
#: netbox/dcim/models/devices.py:1228 netbox/extras/models/customfields.py:231
|
||||
#: netbox/extras/models/models.py:111 netbox/extras/models/models.py:800
|
||||
#: netbox/netbox/models/__init__.py:120 netbox/netbox/models/__init__.py:155
|
||||
msgid "comments"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1238
|
||||
#: netbox/dcim/models/devices.py:1244
|
||||
msgid "virtual device context"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1239
|
||||
#: netbox/dcim/models/devices.py:1245
|
||||
msgid "virtual device contexts"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1268
|
||||
#: netbox/dcim/models/devices.py:1274
|
||||
#, python-brace-format
|
||||
msgid "{ip} is not an IPv{family} address."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1274
|
||||
#: netbox/dcim/models/devices.py:1280
|
||||
msgid "Primary IP address must belong to an interface on the assigned device."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1305
|
||||
#: netbox/dcim/models/devices.py:1311
|
||||
msgid "MAC addresses"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1337
|
||||
#: netbox/dcim/models/devices.py:1343
|
||||
msgid ""
|
||||
"Cannot unassign MAC Address while it is designated as the primary MAC for an "
|
||||
"object"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/models/devices.py:1341
|
||||
#: netbox/dcim/models/devices.py:1347
|
||||
msgid ""
|
||||
"Cannot reassign MAC Address while it is designated as the primary MAC for an "
|
||||
"object"
|
||||
@@ -8454,7 +8454,7 @@ msgstr ""
|
||||
|
||||
#: netbox/extras/forms/bulk_edit.py:158 netbox/extras/forms/bulk_edit.py:383
|
||||
#: netbox/extras/forms/filtersets.py:193 netbox/extras/forms/filtersets.py:498
|
||||
#: netbox/extras/models/mixins.py:101
|
||||
#: netbox/extras/models/mixins.py:100
|
||||
msgid "MIME type"
|
||||
msgstr ""
|
||||
|
||||
@@ -8615,7 +8615,7 @@ msgstr ""
|
||||
msgid "The classification of entry"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/forms/bulk_import.py:285
|
||||
#: netbox/extras/forms/bulk_import.py:289
|
||||
#: netbox/extras/forms/model_forms.py:400 netbox/netbox/navigation/menu.py:414
|
||||
#: netbox/templates/extras/notificationgroup.html:41
|
||||
#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:247
|
||||
@@ -8624,11 +8624,11 @@ msgstr ""
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/forms/bulk_import.py:289
|
||||
#: netbox/extras/forms/bulk_import.py:293
|
||||
msgid "User names separated by commas, encased with double quotes"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/forms/bulk_import.py:292
|
||||
#: netbox/extras/forms/bulk_import.py:296
|
||||
#: netbox/extras/forms/model_forms.py:395 netbox/netbox/navigation/menu.py:295
|
||||
#: netbox/netbox/navigation/menu.py:434
|
||||
#: netbox/templates/extras/notificationgroup.html:31
|
||||
@@ -8641,7 +8641,7 @@ msgstr ""
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/forms/bulk_import.py:296
|
||||
#: netbox/extras/forms/bulk_import.py:300
|
||||
msgid "Group names separated by commas, encased with double quotes"
|
||||
msgstr ""
|
||||
|
||||
@@ -9360,51 +9360,51 @@ msgstr ""
|
||||
msgid "dashboards"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:86
|
||||
#: netbox/extras/models/mixins.py:85
|
||||
msgid "template code"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:87
|
||||
#: netbox/extras/models/mixins.py:86
|
||||
msgid "Jinja template code."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:90
|
||||
#: netbox/extras/models/mixins.py:89
|
||||
msgid "environment parameters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:95
|
||||
#: netbox/extras/models/mixins.py:94
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Any <a href=\"{url}\">additional parameters</a> to pass when constructing "
|
||||
"the Jinja environment"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:102
|
||||
#: netbox/extras/models/mixins.py:101
|
||||
#, python-brace-format
|
||||
msgid "Defaults to <code>{default}</code>"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:107
|
||||
#: netbox/extras/models/mixins.py:106
|
||||
msgid "Filename to give to the rendered export file"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:110
|
||||
#: netbox/extras/models/mixins.py:109
|
||||
msgid "file extension"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:113
|
||||
#: netbox/extras/models/mixins.py:112
|
||||
msgid "Extension to append to the rendered filename"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:116
|
||||
#: netbox/extras/models/mixins.py:115
|
||||
msgid "as attachment"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:118
|
||||
#: netbox/extras/models/mixins.py:117
|
||||
msgid "Download file as attachment"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/mixins.py:125
|
||||
#: netbox/extras/models/mixins.py:124
|
||||
#, python-brace-format
|
||||
msgid "{class_name} must implement a get_context() method."
|
||||
msgstr ""
|
||||
@@ -12539,54 +12539,62 @@ msgid ""
|
||||
"{error}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:442
|
||||
#, python-brace-format
|
||||
msgid "Row {i}: Object with ID {id} does not exist"
|
||||
#: netbox/netbox/views/generic/bulk_views.py:388
|
||||
msgid "Must be a list."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:525
|
||||
#: netbox/netbox/views/generic/bulk_views.py:398
|
||||
msgid "Must be a dictionary."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:461
|
||||
#, python-brace-format
|
||||
msgid "Object with ID {id} does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:546
|
||||
#, python-brace-format
|
||||
msgid "Bulk import {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:541
|
||||
#: netbox/netbox/views/generic/bulk_views.py:562
|
||||
#, python-brace-format
|
||||
msgid "Imported {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:731
|
||||
#: netbox/netbox/views/generic/bulk_views.py:752
|
||||
#, python-brace-format
|
||||
msgid "Bulk edit {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:747
|
||||
#: netbox/netbox/views/generic/bulk_views.py:768
|
||||
#, python-brace-format
|
||||
msgid "Updated {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:780
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1015
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1063
|
||||
#: netbox/netbox/views/generic/bulk_views.py:801
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1036
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1084
|
||||
#, python-brace-format
|
||||
msgid "No {object_type} were selected."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:873
|
||||
#: netbox/netbox/views/generic/bulk_views.py:894
|
||||
#, python-brace-format
|
||||
msgid "Renamed {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:943
|
||||
#: netbox/netbox/views/generic/bulk_views.py:964
|
||||
#, python-brace-format
|
||||
msgid "Bulk delete {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:970
|
||||
#: netbox/netbox/views/generic/bulk_views.py:991
|
||||
#, python-brace-format
|
||||
msgid "Deleted {count} {object_type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/views/generic/bulk_views.py:987
|
||||
#: netbox/netbox/views/generic/bulk_views.py:1008
|
||||
msgid "Deletion failed due to the presence of one or more dependent objects."
|
||||
msgstr ""
|
||||
|
||||
@@ -15817,11 +15825,11 @@ msgstr ""
|
||||
msgid "Objects"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/users/forms/model_forms.py:400
|
||||
#: netbox/users/forms/model_forms.py:403
|
||||
msgid "At least one action must be selected."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/users/forms/model_forms.py:418
|
||||
#: netbox/users/forms/model_forms.py:421
|
||||
#, python-brace-format
|
||||
msgid "Invalid filter for {model}: {error}"
|
||||
msgstr ""
|
||||
@@ -16049,33 +16057,33 @@ msgid ""
|
||||
"Invalid ranges ({value}). Must be a range of integers in ascending order."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:44
|
||||
#: netbox/utilities/forms/fields/csv.py:59
|
||||
#, python-brace-format
|
||||
msgid "Invalid value for a multiple choice field: {value}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:57
|
||||
#: netbox/utilities/forms/fields/csv.py:78
|
||||
#: netbox/utilities/forms/fields/csv.py:77
|
||||
#: netbox/utilities/forms/fields/csv.py:98
|
||||
#, python-format
|
||||
msgid "Object not found: %(value)s"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:65
|
||||
#: netbox/utilities/forms/fields/csv.py:85
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"\"{value}\" is not a unique value for this field; multiple objects were found"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:69
|
||||
#: netbox/utilities/forms/fields/csv.py:89
|
||||
#, python-brace-format
|
||||
msgid "\"{field_name}\" is an invalid accessor field name."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:102
|
||||
#: netbox/utilities/forms/fields/csv.py:122
|
||||
msgid "Object type must be specified as \"<app>.<model>\""
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:106
|
||||
#: netbox/utilities/forms/fields/csv.py:126
|
||||
msgid "Invalid object type"
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -372,6 +372,9 @@ class ObjectPermissionForm(forms.ModelForm):
|
||||
elif self.initial:
|
||||
# Handle cloned objects - actions come from initial data (URL parameters)
|
||||
if 'actions' in self.initial:
|
||||
# Normalize actions to a list of strings
|
||||
if isinstance(self.initial['actions'], str):
|
||||
self.initial['actions'] = [self.initial['actions']]
|
||||
if cloned_actions := self.initial['actions']:
|
||||
for action in ['view', 'add', 'change', 'delete']:
|
||||
if action in cloned_actions:
|
||||
|
||||
@@ -18,6 +18,20 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CSVSelectWidget(forms.Select):
|
||||
"""
|
||||
Custom Select widget for CSV imports that treats blank values as omitted.
|
||||
This allows model defaults to be applied when a CSV field is present but empty.
|
||||
"""
|
||||
def value_omitted_from_data(self, data, files, name):
|
||||
# Check if value is omitted using parent behavior
|
||||
if super().value_omitted_from_data(data, files, name):
|
||||
return True
|
||||
# Treat blank/empty strings as omitted to allow model defaults
|
||||
value = data.get(name)
|
||||
return value == '' or value is None
|
||||
|
||||
|
||||
class CSVChoicesMixin:
|
||||
STATIC_CHOICES = True
|
||||
|
||||
@@ -29,8 +43,9 @@ class CSVChoicesMixin:
|
||||
class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):
|
||||
"""
|
||||
A CSV field which accepts a single selection value.
|
||||
Treats blank CSV values as omitted to allow model defaults.
|
||||
"""
|
||||
pass
|
||||
widget = CSVSelectWidget
|
||||
|
||||
|
||||
class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
|
||||
@@ -46,7 +61,12 @@ class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
|
||||
|
||||
|
||||
class CSVTypedChoiceField(forms.TypedChoiceField):
|
||||
"""
|
||||
A CSV field for typed choice values.
|
||||
Treats blank CSV values as omitted to allow model defaults.
|
||||
"""
|
||||
STATIC_CHOICES = True
|
||||
widget = CSVSelectWidget
|
||||
|
||||
|
||||
class CSVModelChoiceField(forms.ModelChoiceField):
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.test import TestCase
|
||||
from dcim.models import Site
|
||||
from netbox.choices import ImportFormatChoices
|
||||
from utilities.forms.bulk_import import BulkImportForm
|
||||
from utilities.forms.fields.csv import CSVSelectWidget
|
||||
from utilities.forms.forms import BulkRenameForm
|
||||
from utilities.forms.utils import get_field_value, expand_alphanumeric_pattern, expand_ipaddress_pattern
|
||||
|
||||
@@ -448,3 +449,35 @@ class GetFieldValueTest(TestCase):
|
||||
get_field_value(form, 'site'),
|
||||
None
|
||||
)
|
||||
|
||||
|
||||
class CSVSelectWidgetTest(TestCase):
|
||||
"""
|
||||
Validate that CSVSelectWidget treats blank values as omitted.
|
||||
This allows model defaults to be applied when CSV fields are present but empty.
|
||||
Related to issue #20645.
|
||||
"""
|
||||
|
||||
def test_blank_value_treated_as_omitted(self):
|
||||
"""Test that blank string values are treated as omitted"""
|
||||
widget = CSVSelectWidget()
|
||||
data = {'test_field': ''}
|
||||
self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
|
||||
|
||||
def test_none_value_treated_as_omitted(self):
|
||||
"""Test that None values are treated as omitted"""
|
||||
widget = CSVSelectWidget()
|
||||
data = {'test_field': None}
|
||||
self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
|
||||
|
||||
def test_missing_field_treated_as_omitted(self):
|
||||
"""Test that missing fields are treated as omitted"""
|
||||
widget = CSVSelectWidget()
|
||||
data = {}
|
||||
self.assertTrue(widget.value_omitted_from_data(data, {}, 'test_field'))
|
||||
|
||||
def test_valid_value_not_omitted(self):
|
||||
"""Test that valid values are not treated as omitted"""
|
||||
widget = CSVSelectWidget()
|
||||
data = {'test_field': 'valid_value'}
|
||||
self.assertFalse(widget.value_omitted_from_data(data, {}, 'test_field'))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.object_actions import ObjectAction
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[project]
|
||||
name = "netbox"
|
||||
version = "4.4.5"
|
||||
version = "4.4.6"
|
||||
requires-python = ">=3.10"
|
||||
description = "The premier source of truth powering network automation."
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
colorama==0.4.6
|
||||
Django==5.2.7
|
||||
Django==5.2.8
|
||||
django-cors-headers==4.9.0
|
||||
django-debug-toolbar==6.0.0
|
||||
django-debug-toolbar==6.1.0
|
||||
django-filter==25.2
|
||||
django-graphiql-debug-toolbar==0.2.0
|
||||
django-htmx==1.26.0
|
||||
@@ -16,18 +16,18 @@ django-tables2==2.7.5
|
||||
django-taggit==6.1.0
|
||||
django-timezone-field==7.1
|
||||
djangorestframework==3.16.1
|
||||
drf-spectacular==0.28.0
|
||||
drf-spectacular==0.29.0
|
||||
drf-spectacular-sidecar==2025.10.1
|
||||
feedparser==6.0.12
|
||||
gunicorn==23.0.0
|
||||
Jinja2==3.1.6
|
||||
jsonschema==4.25.1
|
||||
Markdown==3.9
|
||||
Markdown==3.10
|
||||
mkdocs-material==9.6.22
|
||||
mkdocstrings==0.30.1
|
||||
mkdocstrings-python==1.18.2
|
||||
mkdocstrings-python==1.19.0
|
||||
netaddr==1.3.0
|
||||
nh3==0.3.1
|
||||
nh3==0.3.2
|
||||
Pillow==12.0.0
|
||||
psycopg[c,pool]==3.2.12
|
||||
PyYAML==6.0.3
|
||||
@@ -36,7 +36,7 @@ rq==2.6.0
|
||||
social-auth-app-django==5.6.0
|
||||
social-auth-core==4.8.1
|
||||
sorl-thumbnail==12.11.0
|
||||
strawberry-graphql==0.284.1
|
||||
strawberry-graphql==0.285.0
|
||||
strawberry-graphql-django==0.67.0
|
||||
svgwrite==1.4.3
|
||||
tablib==3.9.0
|
||||
|
||||
Reference in New Issue
Block a user