mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-10 13:52:17 -06:00
Compare commits
69 Commits
v3.6-beta1
...
v3.5.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dbfbf6941 | ||
|
|
d515530277 | ||
|
|
4343e0566b | ||
|
|
8555269f7e | ||
|
|
f42a2ac10c | ||
|
|
4ea3a29c0e | ||
|
|
29877c9abe | ||
|
|
480f83c42d | ||
|
|
faf89350ac | ||
|
|
d9c3ce935f | ||
|
|
8d8f57e8b8 | ||
|
|
0a3be0b7ea | ||
|
|
00ebdfe0df | ||
|
|
d79fa131bb | ||
|
|
be2b24a155 | ||
|
|
03b341dbfd | ||
|
|
ca5e69897d | ||
|
|
3090dd4934 | ||
|
|
1f1d1ee502 | ||
|
|
1c2cf11f47 | ||
|
|
08961e751d | ||
|
|
88bf82be05 | ||
|
|
506884bc4d | ||
|
|
646fa341ab | ||
|
|
d73f7b1943 | ||
|
|
a75e8416a4 | ||
|
|
f743f2cfb8 | ||
|
|
3c0a3ca703 | ||
|
|
45062697c5 | ||
|
|
66e4e31209 | ||
|
|
c86cfe3cbf | ||
|
|
28e112743f | ||
|
|
4004966b16 | ||
|
|
fe95cb434a | ||
|
|
16e2283d19 | ||
|
|
c46536f469 | ||
|
|
9450ce4c3a | ||
|
|
1c9a8ec6bd | ||
|
|
8f5005efd5 | ||
|
|
e61795d5c6 | ||
|
|
892c10b1f0 | ||
|
|
ea107b6b86 | ||
|
|
b9b9c065cc | ||
|
|
b583770765 | ||
|
|
37d6f6abca | ||
|
|
be3f48c677 | ||
|
|
5de9d3f15f | ||
|
|
40afe6cf36 | ||
|
|
9fd07b594c | ||
|
|
dc7411e4c5 | ||
|
|
72e1e8fab1 | ||
|
|
dcdb4d27ec | ||
|
|
9b1406a1a7 | ||
|
|
545769ad88 | ||
|
|
f5a1f83f9f | ||
|
|
f9648d8544 | ||
|
|
2236b86c35 | ||
|
|
0dd319d0c8 | ||
|
|
88562d7dcf | ||
|
|
01bb09db67 | ||
|
|
43ce453938 | ||
|
|
7f22c6bf12 | ||
|
|
93a862cded | ||
|
|
9cc295827b | ||
|
|
a807cca29e | ||
|
|
57860f26b7 | ||
|
|
ab916a1819 | ||
|
|
a68831d3a1 | ||
|
|
a4c9cbc6dd |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.7
|
placeholder: v3.5.9
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.7
|
placeholder: v3.5.9
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|||||||
@@ -14,12 +14,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3></h3>
|
<h3></h3>
|
||||||
|
|
||||||
Some general tips for engaging here on GitHub:
|
## :information_source: Welcome to the Stadium!
|
||||||
|
|
||||||
|
In her book [Working in Public](https://www.amazon.com/Working-Public-Making-Maintenance-Software/dp/0578675862), Nadia Eghbal defines four production models for open source projects, categorized by contributor and user growth: federations, clubs, toys, and stadiums. The NetBox project fits her definition of a stadium very well:
|
||||||
|
|
||||||
|
> Stadiums are projects with low contributor growth and high user growth. While they may receive casual contributions, their regular contributor base does not grow proportionately to their users. As a result, they tend to be powered by one or a few developers.
|
||||||
|
|
||||||
|
The bulk of NetBox's development is carried out by a handful of core maintainers, with occasional contributions from collaborators in the community. We find the stadium analogy very useful in conveying the roles and obligations of both contributors and users.
|
||||||
|
|
||||||
|
If you're a contributor, actively working on the center stage, you have an obligation to produce quality content that will benefit the project as a whole. Conversely, if you're in the audience consuming the work being produced, you have the option of making requests and suggestions, but must also recognize that contributors are under no obligation to act on them.
|
||||||
|
|
||||||
|
NetBox users are welcome to participate in either role, on stage or in the crowd. We ask only that you acknowledge the role you've chosen and respect the roles of others.
|
||||||
|
|
||||||
|
### General Tips for Working on GitHub
|
||||||
|
|
||||||
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
|
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
|
||||||
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
|
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
|
||||||
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
|
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
|
||||||
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
|
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
|
||||||
|
* Familiarize yourself with [this list of discussion anti-patterns](https://github.com/bradfitz/issue-tracker-behaviors) and make every effort to avoid them.
|
||||||
|
|
||||||
## :bug: Reporting Bugs
|
## :bug: Reporting Bugs
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
# https://github.com/mozilla/bleach/blob/main/CHANGES
|
# https://github.com/mozilla/bleach/blob/main/CHANGES
|
||||||
bleach
|
bleach
|
||||||
|
|
||||||
|
# Python client for Amazon AWS API
|
||||||
|
# https://github.com/boto/boto3/blob/develop/CHANGELOG.rst
|
||||||
|
boto3
|
||||||
|
|
||||||
# The Python web framework on which NetBox is built
|
# The Python web framework on which NetBox is built
|
||||||
# https://docs.djangoproject.com/en/stable/releases/
|
# https://docs.djangoproject.com/en/stable/releases/
|
||||||
Django<5.0
|
Django<4.2
|
||||||
|
|
||||||
# Django middleware which permits cross-domain API requests
|
# Django middleware which permits cross-domain API requests
|
||||||
# https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst
|
# https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst
|
||||||
@@ -70,6 +74,10 @@ drf-spectacular
|
|||||||
# https://github.com/tfranzel/drf-spectacular-sidecar
|
# https://github.com/tfranzel/drf-spectacular-sidecar
|
||||||
drf-spectacular-sidecar
|
drf-spectacular-sidecar
|
||||||
|
|
||||||
|
# Git client for file sync
|
||||||
|
# https://github.com/jelmer/dulwich/releases
|
||||||
|
dulwich
|
||||||
|
|
||||||
# RSS feed parser
|
# RSS feed parser
|
||||||
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
|
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
|
||||||
feedparser
|
feedparser
|
||||||
@@ -113,8 +121,8 @@ netaddr
|
|||||||
Pillow
|
Pillow
|
||||||
|
|
||||||
# PostgreSQL database adapter for Python
|
# PostgreSQL database adapter for Python
|
||||||
# https://github.com/psycopg/psycopg/blob/master/docs/news.rst
|
# https://www.psycopg.org/docs/news.html
|
||||||
psycopg[binary,pool]
|
psycopg2-binary
|
||||||
|
|
||||||
# YAML rendering library
|
# YAML rendering library
|
||||||
# https://github.com/yaml/pyyaml/blob/master/CHANGES
|
# https://github.com/yaml/pyyaml/blob/master/CHANGES
|
||||||
|
|||||||
562
contrib/generated_schema.json
Normal file
562
contrib/generated_schema.json
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"400gbase-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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,13 +68,8 @@ When defining a permission constraint, administrators may use the special token
|
|||||||
|
|
||||||
The `$user` token can be used only as a constraint value, or as an item within a list of values. It cannot be modified or extended to reference specific user attributes.
|
The `$user` token can be used only as a constraint value, or as an item within a list of values. It cannot be modified or extended to reference specific user attributes.
|
||||||
|
|
||||||
### Default Permissions
|
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v3.6."
|
#### Example Constraint Definitions
|
||||||
|
|
||||||
While permissions are typically assigned to specific groups and/or users, it is also possible to define a set of default permissions that are applied to _all_ authenticated users. This is done using the [`DEFAULT_PERMISSIONS`](../configuration/security.md#default_permissions) configuration parameter. Note that statically configuring permissions for specific users or groups is **not** supported.
|
|
||||||
|
|
||||||
### Example Constraint Definitions
|
|
||||||
|
|
||||||
| Constraints | Description |
|
| Constraints | Description |
|
||||||
| ----------- | ----------- |
|
| ----------- | ----------- |
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ALLOWED_HOSTS = ['*']
|
|||||||
|
|
||||||
## DATABASE
|
## DATABASE
|
||||||
|
|
||||||
NetBox requires access to a PostgreSQL 12 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
NetBox requires access to a PostgreSQL 11 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
||||||
|
|
||||||
* `NAME` - Database name
|
* `NAME` - Database name
|
||||||
* `USER` - PostgreSQL username
|
* `USER` - PostgreSQL username
|
||||||
|
|||||||
@@ -90,38 +90,6 @@ CSRF_TRUSTED_ORIGINS = (
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## DEFAULT_PERMISSIONS
|
|
||||||
|
|
||||||
!!! info "This parameter was introduced in NetBox v3.6."
|
|
||||||
|
|
||||||
Default:
|
|
||||||
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
'users.view_token': ({'user': '$user'},),
|
|
||||||
'users.add_token': ({'user': '$user'},),
|
|
||||||
'users.change_token': ({'user': '$user'},),
|
|
||||||
'users.delete_token': ({'user': '$user'},),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This parameter defines object permissions that are applied automatically to _any_ authenticated user, regardless of what permissions have been defined in the database. By default, this parameter is defined to allow all users to manage their own API tokens, however it can be overriden for any purpose.
|
|
||||||
|
|
||||||
For example, to allow all users to create a device role beginning with the word "temp," you could configure the following:
|
|
||||||
|
|
||||||
```python
|
|
||||||
DEFAULT_PERMISSIONS = {
|
|
||||||
'dcim.add_devicerole': (
|
|
||||||
{'name__startswith': 'temp'},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
Setting a custom value for this parameter will overwrite the default permission mapping shown above. If you want to retain the default mapping, be sure to reproduce it in your custom configuration.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## EXEMPT_VIEW_PERMISSIONS
|
## EXEMPT_VIEW_PERMISSIONS
|
||||||
|
|
||||||
Default: Empty list
|
Default: Empty list
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ NetBox supports limited custom validation for custom field values. Following are
|
|||||||
|
|
||||||
### Custom Selection Fields
|
### Custom Selection Fields
|
||||||
|
|
||||||
Each custom selection field must designate a [choice set](../models/extras/customfieldchoiceset.md) containing at least two choices. These are specified as a comma-separated list.
|
Each custom selection field must have at least two choices. These are specified as a comma-separated list. Choices appear in forms in the order they are listed. Note that choice values are saved exactly as they appear, so it's best to avoid superfluous punctuation or symbols where possible.
|
||||||
|
|
||||||
If a default value is specified for a selection field, it must exactly match one of the provided choices. The value of a multiple selection field will always return a list, even if only one value is selected.
|
If a default value is specified for a selection field, it must exactly match one of the provided choices. The value of a multiple selection field will always return a list, even if only one value is selected.
|
||||||
|
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ class NewBranchScript(Script):
|
|||||||
name=f'{site.slug}-switch{i}',
|
name=f'{site.slug}-switch{i}',
|
||||||
site=site,
|
site=site,
|
||||||
status=DeviceStatusChoices.STATUS_PLANNED,
|
status=DeviceStatusChoices.STATUS_PLANNED,
|
||||||
role=switch_role
|
device_role=switch_role
|
||||||
)
|
)
|
||||||
switch.full_clean()
|
switch.full_clean()
|
||||||
switch.save()
|
switch.save()
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ The following methods are available to log results within a report:
|
|||||||
|
|
||||||
The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status. Log messages also support using markdown syntax and will be rendered on the report result page.
|
The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status. Log messages also support using markdown syntax and will be rendered on the report result page.
|
||||||
|
|
||||||
To perform additional tasks, such as sending an email or calling a webhook, before or after a report is run, extend the `pre_run()` and/or `post_run()` methods, respectively. The status of a completed report is available as `self.failed` and the results object is `self.result`.
|
To perform additional tasks, such as sending an email or calling a webhook, before or after a report is run, extend the `pre_run()` and/or `post_run()` methods, respectively.
|
||||||
|
|
||||||
By default, reports within a module are ordered alphabetically in the reports list page. To return reports in a specific order, you can define the `report_order` variable at the end of your module. The `report_order` variable is a tuple which contains each Report class in the desired order. Any reports that are omitted from this list will be listed last.
|
By default, reports within a module are ordered alphabetically in the reports list page. To return reports in a specific order, you can define the `report_order` variable at the end of your module. The `report_order` variable is a tuple which contains each Report class in the desired order. Any reports that are omitted from this list will be listed last.
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ The registry can be inspected by importing `registry` from `extras.registry`.
|
|||||||
|
|
||||||
## Stores
|
## Stores
|
||||||
|
|
||||||
### `counter_fields`
|
|
||||||
|
|
||||||
A dictionary mapping of models to foreign keys with which cached counter fields are associated.
|
|
||||||
|
|
||||||
### `data_backends`
|
### `data_backends`
|
||||||
|
|
||||||
A dictionary mapping data backend types to their respective classes. These are used to interact with [remote data sources](../models/core/datasource.md).
|
A dictionary mapping data backend types to their respective classes. These are used to interact with [remote data sources](../models/core/datasource.md).
|
||||||
|
|||||||
@@ -43,10 +43,22 @@ Follow these instructions to perform a new installation of NetBox in a temporary
|
|||||||
|
|
||||||
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below.
|
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below.
|
||||||
|
|
||||||
|
### Rebuild Demo Data (After Release)
|
||||||
|
|
||||||
|
After the release of a new minor version, generate a new demo data snapshot compatible with the new release. See the [`netbox-demo-data`](https://github.com/netbox-community/netbox-demo-data) repository for instructions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Patch Releases
|
## Patch Releases
|
||||||
|
|
||||||
|
### Notify netbox-docker Project of Any Relevant Changes
|
||||||
|
|
||||||
|
Notify the [`netbox-docker`](https://github.com/netbox-community/netbox-docker) maintainers (in **#netbox-docker**) of any changes that may be relevant to their build process, including:
|
||||||
|
|
||||||
|
* Significant changes to `upgrade.sh`
|
||||||
|
* Increases in minimum versions for service dependencies (PostgreSQL, Redis, etc.)
|
||||||
|
* Any changes to the reference installation
|
||||||
|
|
||||||
### Update Requirements
|
### Update Requirements
|
||||||
|
|
||||||
Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this:
|
Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this:
|
||||||
@@ -58,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).
|
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 Version and Changelog
|
||||||
|
|
||||||
* Update the `VERSION` constant in `settings.py` to the new release version.
|
* Update the `VERSION` constant in `settings.py` to the new release version.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Configuration Rendering
|
# Configuration Rendering
|
||||||
|
|
||||||
|
!!! info "This feature was introduced in NetBox v3.5."
|
||||||
|
|
||||||
One of the critical aspects of operating a network is ensuring that every network node is configured correctly. By leveraging configuration templates and [context data](./context-data.md), NetBox can render complete configuration files for each device on your network.
|
One of the critical aspects of operating a network is ensuring that every network node is configured correctly. By leveraging configuration templates and [context data](./context-data.md), NetBox can render complete configuration files for each device on your network.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ The `tag` filter can be specified multiple times to match only objects which hav
|
|||||||
GET /api/dcim/devices/?tag=monitored&tag=deprecated
|
GET /api/dcim/devices/?tag=monitored&tag=deprecated
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bookmarks
|
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v3.6."
|
|
||||||
|
|
||||||
Users can bookmark their most commonly visited objects for convenient access. Bookmarks are listed under a user's profile, and can be displayed with custom filtering and ordering on the user's personal dashboard.
|
|
||||||
|
|
||||||
## Custom Fields
|
## Custom Fields
|
||||||
|
|
||||||
While NetBox provides a rather extensive data model out of the box, the need may arise to store certain additional data associated with NetBox objects. For example, you might need to record the invoice ID alongside an installed device, or record an approving authority when creating a new IP prefix. NetBox administrators can create custom fields on built-in objects to meet these needs.
|
While NetBox provides a rather extensive data model out of the box, the need may arise to store certain additional data associated with NetBox objects. For example, you might need to record the invoice ID alongside an installed device, or record an approving authority when creating a new IP prefix. NetBox administrators can create custom fields on built-in objects to meet these needs.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Synchronized Data
|
# Synchronized Data
|
||||||
|
|
||||||
|
!!! info "This feature was introduced in NetBox v3.5."
|
||||||
|
|
||||||
Several models in NetBox support the automatic synchronization of local data from a designated remote source. For example, [configuration templates](./configuration-rendering.md) defined in NetBox can source their content from text files stored in a remote git repository. This accomplished using the core [data source](../models/core/datasource.md) and [data file](../models/core/datafile.md) models.
|
Several models in NetBox support the automatic synchronization of local data from a designated remote source. For example, [configuration templates](./configuration-rendering.md) defined in NetBox can source their content from text files stored in a remote git repository. This accomplished using the core [data source](../models/core/datasource.md) and [data file](../models/core/datafile.md) models.
|
||||||
|
|
||||||
To enable remote data synchronization, the NetBox administrator first designates one or more remote data sources. NetBox currently supports the following source types:
|
To enable remote data synchronization, the NetBox administrator first designates one or more remote data sources. NetBox currently supports the following source types:
|
||||||
@@ -10,10 +12,6 @@ To enable remote data synchronization, the NetBox administrator first designates
|
|||||||
|
|
||||||
(Local disk paths are considered "remote" in this context as they exist outside NetBox's database. These paths could also be mapped to external network shares.)
|
(Local disk paths are considered "remote" in this context as they exist outside NetBox's database. These paths could also be mapped to external network shares.)
|
||||||
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
Data backends which connect to external sources typically require the installation of one or more supporting Python libraries. The Git backend requires the [`dulwich`](https://www.dulwich.io/) package, and the S3 backend requires the [`boto3`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) package. These must be installed within NetBox's environment to enable these backends.
|
|
||||||
|
|
||||||
Each type of remote source has its own configuration parameters. For instance, a git source will ask the user to specify a branch and authentication credentials. Once the source has been created, a synchronization job is run to automatically replicate remote files in the local database.
|
Each type of remote source has its own configuration parameters. For instance, a git source will ask the user to specify a branch and authentication credentials. Once the source has been created, a synchronization job is run to automatically replicate remote files in the local database.
|
||||||
|
|
||||||
The following NetBox models can be associated with replicated data files:
|
The following NetBox models can be associated with replicated data files:
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
This section entails the installation and configuration of a local PostgreSQL database. If you already have a PostgreSQL database service in place, skip to [the next section](2-redis.md).
|
This section entails the installation and configuration of a local PostgreSQL database. If you already have a PostgreSQL database service in place, skip to [the next section](2-redis.md).
|
||||||
|
|
||||||
!!! warning "PostgreSQL 12 or later required"
|
!!! warning "PostgreSQL 11 or later required"
|
||||||
NetBox requires PostgreSQL 12 or later. Please note that MySQL and other relational databases are **not** supported.
|
NetBox requires PostgreSQL 11 or later. Please note that MySQL and other relational databases are **not** supported.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ This section entails the installation and configuration of a local PostgreSQL da
|
|||||||
sudo systemctl enable postgresql
|
sudo systemctl enable postgresql
|
||||||
```
|
```
|
||||||
|
|
||||||
Before continuing, verify that you have installed PostgreSQL 12 or later:
|
Before continuing, verify that you have installed PostgreSQL 11 or later:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
psql -V
|
psql -V
|
||||||
|
|||||||
@@ -211,22 +211,6 @@ By default, NetBox will use the local filesystem to store uploaded files. To use
|
|||||||
sudo sh -c "echo 'django-storages' >> /opt/netbox/local_requirements.txt"
|
sudo sh -c "echo 'django-storages' >> /opt/netbox/local_requirements.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remote Data Sources
|
|
||||||
|
|
||||||
NetBox supports integration with several remote data sources via configurable backends. Each of these requires the installation of one or more additional libraries.
|
|
||||||
|
|
||||||
* Amazon S3: [`boto3`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
|
|
||||||
* Git: [`dulwich`](https://www.dulwich.io/)
|
|
||||||
|
|
||||||
For example, to enable the Amazon S3 backend, add `boto3` to your local requirements file:
|
|
||||||
|
|
||||||
```no-highlight
|
|
||||||
sudo sh -c "echo 'boto3' >> /opt/netbox/local_requirements.txt"
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
These packages were previously required in NetBox v3.5 but now are optional.
|
|
||||||
|
|
||||||
## Run the Upgrade Script
|
## Run the Upgrade Script
|
||||||
|
|
||||||
Once NetBox has been configured, we're ready to proceed with the actual installation. We'll run the packaged upgrade script (`upgrade.sh`) to perform the following actions:
|
Once NetBox has been configured, we're ready to proceed with the actual installation. We'll run the packaged upgrade script (`upgrade.sh`) to perform the following actions:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ The following sections detail how to set up a new instance of NetBox:
|
|||||||
| Dependency | Minimum Version |
|
| Dependency | Minimum Version |
|
||||||
|------------|-----------------|
|
|------------|-----------------|
|
||||||
| Python | 3.8 |
|
| Python | 3.8 |
|
||||||
| PostgreSQL | 12 |
|
| PostgreSQL | 11 |
|
||||||
| Redis | 4.0 |
|
| Redis | 4.0 |
|
||||||
|
|
||||||
Below is a simplified overview of the NetBox application stack for reference:
|
Below is a simplified overview of the NetBox application stack for reference:
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ Prior to upgrading your NetBox instance, be sure to carefully review all [releas
|
|||||||
|
|
||||||
## 2. Update Dependencies to Required Versions
|
## 2. Update Dependencies to Required Versions
|
||||||
|
|
||||||
NetBox requires the following dependencies:
|
NetBox v3.0 and later require the following:
|
||||||
|
|
||||||
| Dependency | Minimum Version |
|
| Dependency | Minimum Version |
|
||||||
|------------|-----------------|
|
|------------|-----------------|
|
||||||
| Python | 3.8 |
|
| Python | 3.8 |
|
||||||
| PostgreSQL | 12 |
|
| PostgreSQL | 11 |
|
||||||
| Redis | 4.0 |
|
| Redis | 4.0 |
|
||||||
|
|
||||||
## 3. Install the Latest Release
|
## 3. Install the Latest Release
|
||||||
@@ -59,7 +59,7 @@ Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if pres
|
|||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
# Set $OLDVER to the NetBox version currently installed
|
# Set $OLDVER to the NetBox version currently installed
|
||||||
NEWVER=3.4.9
|
OLDVER=3.4.9
|
||||||
sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/
|
sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/
|
||||||
sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
|
sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
|
||||||
sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
|
sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
|
||||||
|
|||||||
@@ -671,6 +671,8 @@ This header specifies the API version in use. This will always match the version
|
|||||||
|
|
||||||
### `X-Request-ID`
|
### `X-Request-ID`
|
||||||
|
|
||||||
|
!!! info "This feature was introduced in NetBox v3.5."
|
||||||
|
|
||||||
This header specifies the unique ID assigned to the received API request. It can be very handy for correlating a request with change records. For example, after creating several new objects, you can filter against the object changes API endpoint to retrieve the resulting change records:
|
This header specifies the unique ID assigned to the received API request. It can be very handy for correlating a request with change records. For example, after creating several new objects, you can filter against the object changes API endpoint to retrieve the resulting change records:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -75,5 +75,5 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
|
|||||||
| HTTP service | nginx or Apache |
|
| HTTP service | nginx or Apache |
|
||||||
| WSGI service | gunicorn or uWSGI |
|
| WSGI service | gunicorn or uWSGI |
|
||||||
| Application | Django/Python |
|
| Application | Django/Python |
|
||||||
| Database | PostgreSQL 12+ |
|
| Database | PostgreSQL 11+ |
|
||||||
| Task queuing | Redis/django-rq |
|
| Task queuing | Redis/django-rq |
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Provider Accounts
|
# Provider Accounts
|
||||||
|
|
||||||
|
!!! info "This model was introduced in NetBox v3.5."
|
||||||
|
|
||||||
This model can be used to represent individual accounts associated with a provider.
|
This model can be used to represent individual accounts associated with a provider.
|
||||||
|
|
||||||
## Fields
|
## Fields
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ If installed in a rack, this field indicates the base rack unit in which the dev
|
|||||||
!!! tip
|
!!! tip
|
||||||
Devices with a height of more than one rack unit should be set to the lowest-numbered rack unit that they occupy.
|
Devices with a height of more than one rack unit should be set to the lowest-numbered rack unit that they occupy.
|
||||||
|
|
||||||
### Latitude & Longitude
|
|
||||||
|
|
||||||
GPS coordinates of the device for geolocation.
|
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
The device's operational status.
|
The device's operational status.
|
||||||
@@ -87,10 +83,6 @@ Each device may designate one primary IPv4 address and/or one primary IPv6 addre
|
|||||||
!!! tip
|
!!! tip
|
||||||
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
|
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
|
||||||
|
|
||||||
### Out-of-band (OOB) IP Address
|
|
||||||
|
|
||||||
Each device may designate its out-of-band IP address. Out-of-band IPs are typically used to access network infrastructure via a physically separate management network.
|
|
||||||
|
|
||||||
### Cluster
|
### Cluster
|
||||||
|
|
||||||
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)
|
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ The canonical distance between the two vertical rails on a face. (This is typica
|
|||||||
|
|
||||||
The height of the rack, measured in units.
|
The height of the rack, measured in units.
|
||||||
|
|
||||||
### Starting Unit
|
|
||||||
|
|
||||||
The number of the numerically lowest unit in the rack. This value defaults to one, but may be higher in certain situations. For example, you may want to model only a select range of units within a shared physical rack (e.g. U13 through U24).
|
|
||||||
|
|
||||||
### Outer Dimensions
|
### Outer Dimensions
|
||||||
|
|
||||||
The external width and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
|
The external width and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# Bookmarks
|
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v3.6."
|
|
||||||
|
|
||||||
A user can bookmark individual objects for convenient access. Bookmarks are listed under a user's profile and can be displayed using a dashboard widget.
|
|
||||||
|
|
||||||
## Fields
|
|
||||||
|
|
||||||
### User
|
|
||||||
|
|
||||||
The user to whom the bookmark belongs.
|
|
||||||
|
|
||||||
### Object
|
|
||||||
|
|
||||||
The bookmarked object.
|
|
||||||
@@ -79,9 +79,9 @@ Controls how and whether the custom field is displayed within the NetBox user in
|
|||||||
|
|
||||||
The default value to populate for the custom field when creating new objects (optional). This value must be expressed as JSON. If this is a choice or multi-choice field, this must be one of the available choices.
|
The default value to populate for the custom field when creating new objects (optional). This value must be expressed as JSON. If this is a choice or multi-choice field, this must be one of the available choices.
|
||||||
|
|
||||||
### Choice Set
|
### Choices
|
||||||
|
|
||||||
For selection and multi-select custom fields only, this is the [set of choices](./customfieldchoiceset.md) which are valid for the field.
|
For choice and multi-choice custom fields only. A comma-delimited list of the available choices.
|
||||||
|
|
||||||
### Cloneable
|
### Cloneable
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
# Custom Field Choice Sets
|
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v3.6."
|
|
||||||
|
|
||||||
Single- and multi-selection [custom fields](../../customization/custom-fields.md) must define a set of valid choices from which the user may choose when defining the field value. These choices are defined as sets that may be reused among multiple custom fields.
|
|
||||||
|
|
||||||
A choice set must define a base choice set and/or a set of arbitrary extra choices.
|
|
||||||
|
|
||||||
## Fields
|
|
||||||
|
|
||||||
### Name
|
|
||||||
|
|
||||||
The human-friendly name of the choice set.
|
|
||||||
|
|
||||||
### Base Choices
|
|
||||||
|
|
||||||
The set of pre-defined choices to include. Available sets are listed below. This is an optional setting.
|
|
||||||
|
|
||||||
* IATA airport codes
|
|
||||||
* ISO 3166 - Two-letter country codes
|
|
||||||
* UN/LOCODE - Five-character location identifiers
|
|
||||||
|
|
||||||
### Extra Choices
|
|
||||||
|
|
||||||
A set of custom choices that will be appended to the base choice set (if any).
|
|
||||||
|
|
||||||
### Order Alphabetically
|
|
||||||
|
|
||||||
If enabled, the choices list will be automatically ordered alphabetically. If disabled, choices will appear in the order in which they were defined.
|
|
||||||
@@ -15,11 +15,3 @@ A unique URL-friendly identifier. (This value will be used for filtering.) This
|
|||||||
### Color
|
### Color
|
||||||
|
|
||||||
The color to use when displaying the tag in the NetBox UI.
|
The color to use when displaying the tag in the NetBox UI.
|
||||||
|
|
||||||
### Object Types
|
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v3.6."
|
|
||||||
|
|
||||||
The assignment of a tag may be limited to a prescribed set of objects. For example, it may be desirable to limit the application of a specific tag to only devices and virtual machines.
|
|
||||||
|
|
||||||
If no object types are specified, the tag will be assignable to any type of object.
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# ASN Ranges
|
# ASN Ranges
|
||||||
|
|
||||||
|
!!! info "This model was introduced in NetBox v3.5."
|
||||||
|
|
||||||
Ranges can be defined to group [AS numbers](./asn.md) numerically and to facilitate their automatic provisioning. Each range must be assigned to a [RIR](./rir.md).
|
Ranges can be defined to group [AS numbers](./asn.md) numerically and to facilitate their automatic provisioning. Each range must be assigned to a [RIR](./rir.md).
|
||||||
|
|
||||||
## Fields
|
## Fields
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Dashboard Widgets
|
# Dashboard Widgets
|
||||||
|
|
||||||
|
!!! info "This feature was introduced in NetBox v3.5."
|
||||||
|
|
||||||
Each NetBox user can customize his or her personal dashboard by adding and removing widgets and by manipulating the size and position of each. Plugins can register their own dashboard widgets to complement those already available natively.
|
Each NetBox user can customize his or her personal dashboard by adding and removing widgets and by manipulating the size and position of each. Plugins can register their own dashboard widgets to complement those already available natively.
|
||||||
|
|
||||||
## The DashboardWidget Class
|
## The DashboardWidget Class
|
||||||
|
|||||||
@@ -165,6 +165,19 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
|
|||||||
options:
|
options:
|
||||||
members: false
|
members: false
|
||||||
|
|
||||||
|
## Choice Fields
|
||||||
|
|
||||||
|
!!! warning "Obsolete Fields"
|
||||||
|
NetBox's custom `ChoiceField` and `MultipleChoiceField` classes are no longer necessary thanks to improvements made to the user interface. Django's native form fields can be used instead. These custom field classes will be removed in NetBox v3.6.
|
||||||
|
|
||||||
|
::: utilities.forms.fields.ChoiceField
|
||||||
|
options:
|
||||||
|
members: false
|
||||||
|
|
||||||
|
::: utilities.forms.fields.MultipleChoiceField
|
||||||
|
options:
|
||||||
|
members: false
|
||||||
|
|
||||||
## Dynamic Object Fields
|
## Dynamic Object Fields
|
||||||
|
|
||||||
::: utilities.forms.fields.DynamicModelChoiceField
|
::: utilities.forms.fields.DynamicModelChoiceField
|
||||||
|
|||||||
@@ -62,12 +62,13 @@ These lookup expressions can be applied by adding a suffix to the desired field'
|
|||||||
Numeric based fields (ASN, VLAN ID, etc) support these lookup expressions:
|
Numeric based fields (ASN, VLAN ID, etc) support these lookup expressions:
|
||||||
|
|
||||||
| Filter | Description |
|
| Filter | Description |
|
||||||
|--------|-------------|
|
|---------|--------------------------|
|
||||||
| `n` | Not equal to |
|
| `n` | Not equal to |
|
||||||
| `lt` | Less than |
|
| `lt` | Less than |
|
||||||
| `lte` | Less than or equal to |
|
| `lte` | Less than or equal to |
|
||||||
| `gt` | Greater than |
|
| `gt` | Greater than |
|
||||||
| `gte` | Greater than or equal to |
|
| `gte` | Greater than or equal to |
|
||||||
|
| `empty` | Is empty/null (boolean) |
|
||||||
|
|
||||||
Here is an example of a numeric field lookup expression that will return all VLANs with a VLAN ID greater than 900:
|
Here is an example of a numeric field lookup expression that will return all VLANs with a VLAN ID greater than 900:
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ GET /api/ipam/vlans/?vid__gt=900
|
|||||||
String based (char) fields (Name, Address, etc) support these lookup expressions:
|
String based (char) fields (Name, Address, etc) support these lookup expressions:
|
||||||
|
|
||||||
| Filter | Description |
|
| Filter | Description |
|
||||||
|--------|-------------|
|
|---------|----------------------------------------|
|
||||||
| `n` | Not equal to |
|
| `n` | Not equal to |
|
||||||
| `ic` | Contains (case-insensitive) |
|
| `ic` | Contains (case-insensitive) |
|
||||||
| `nic` | Does not contain (case-insensitive) |
|
| `nic` | Does not contain (case-insensitive) |
|
||||||
@@ -90,7 +91,7 @@ String based (char) fields (Name, Address, etc) support these lookup expressions
|
|||||||
| `niew` | Does not end with (case-insensitive) |
|
| `niew` | Does not end with (case-insensitive) |
|
||||||
| `ie` | Exact match (case-insensitive) |
|
| `ie` | Exact match (case-insensitive) |
|
||||||
| `nie` | Inverse exact match (case-insensitive) |
|
| `nie` | Inverse exact match (case-insensitive) |
|
||||||
| `empty` | Is empty (boolean) |
|
| `empty` | Is empty/null (boolean) |
|
||||||
|
|
||||||
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:
|
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,59 @@
|
|||||||
# NetBox v3.5
|
# NetBox v3.5
|
||||||
|
|
||||||
## v3.5.8 (FUTURE)
|
## v3.5.9 (2023-08-28)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#12489](https://github.com/netbox-community/netbox/issues/12489) - Dynamically render location and device lists under site and location views
|
||||||
|
* [#12825](https://github.com/netbox-community/netbox/issues/12825) - Display assigned values count per obejct type under custom field view
|
||||||
|
* [#13313](https://github.com/netbox-community/netbox/issues/13313) - Enable filtering IP ranges by containing prefix
|
||||||
|
* [#13415](https://github.com/netbox-community/netbox/issues/13415) - Include request object in custom link renderer on tables
|
||||||
|
* [#13536](https://github.com/netbox-community/netbox/issues/13536) - Move child VLANs list to a separate tab under VLAN group view
|
||||||
|
* [#13542](https://github.com/netbox-community/netbox/issues/13542) - Pass additional HTTP headers through to custom script context
|
||||||
|
* [#13585](https://github.com/netbox-community/netbox/issues/13585) - Introduce `empty` lookup for numeric value filters
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#11272](https://github.com/netbox-community/netbox/issues/11272) - Fix localization support for device position field
|
||||||
|
* [#13358](https://github.com/netbox-community/netbox/issues/13358) - Git backend should send HTTP auth headers only if credentials have been defined
|
||||||
|
* [#13477](https://github.com/netbox-community/netbox/issues/13477) - Fix filtering of modified objects after bulk import/update
|
||||||
|
* [#13478](https://github.com/netbox-community/netbox/issues/13478) - Fix filtering of export templates by content type under web UI
|
||||||
|
* [#13500](https://github.com/netbox-community/netbox/issues/13500) - Fix form validation for bulk update of L2VPN terminations via bulk import form
|
||||||
|
* [#13503](https://github.com/netbox-community/netbox/issues/13503) - Fix utilization graph proportions when localization is enabled
|
||||||
|
* [#13507](https://github.com/netbox-community/netbox/issues/13507) - Avoid raising exception for invalid content type during global search
|
||||||
|
* [#13516](https://github.com/netbox-community/netbox/issues/13516) - Plugin utility functions should be importable from `extras.plugins`
|
||||||
|
* [#13530](https://github.com/netbox-community/netbox/issues/13530) - Ensure script log messages can be serialized as JSON data
|
||||||
|
* [#13543](https://github.com/netbox-community/netbox/issues/13543) - Config context tab under device/VM view should not require `extras.view_configcontext` permission
|
||||||
|
* [#13544](https://github.com/netbox-community/netbox/issues/13544) - Ensure `reindex` command clears all cached values when not in lazy mode
|
||||||
|
* [#13556](https://github.com/netbox-community/netbox/issues/13556) - Correct REST API representation of VDC status choice
|
||||||
|
* [#13569](https://github.com/netbox-community/netbox/issues/13569) - Fix selection widgets for related interfaces when bulk editing interfaces under device view
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
# NetBox v3.6
|
|
||||||
|
|
||||||
## v3.6-beta1 (2023-08-02)
|
|
||||||
|
|
||||||
### Breaking Changes
|
|
||||||
|
|
||||||
* PostgreSQL 11 is no longer supported (dropped in Django 4.2). NetBox v3.6 requires PostgreSQL 12 or later.
|
|
||||||
* The `device_role` field on the Device model has been renamed to `role`. The `device_role` field has been temporarily retained on the REST API serializer for devices for backward compatibility, but is read-only.
|
|
||||||
* The `choices` array field has been removed from the CustomField model. Any defined choices are automatically migrated to CustomFieldChoiceSets, accessible via the new `choice_set` field on the CustomField model.
|
|
||||||
* The `napalm_driver` and `napalm_args` fields (which were deprecated in v3.5) have been removed from the Platform model.
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
#### Relocated Admin Views ([#12589](https://github.com/netbox-community/netbox/issues/12589), [#12590](https://github.com/netbox-community/netbox/issues/12590), [#12591](https://github.com/netbox-community/netbox/issues/12591), [#13044](https://github.com/netbox-community/netbox/issues/13044))
|
|
||||||
|
|
||||||
Management views for the following object types, previously available only under the backend admin interface, have been relocated to the primary user interface:
|
|
||||||
|
|
||||||
* Users
|
|
||||||
* Groups
|
|
||||||
* Object permissions
|
|
||||||
* API tokens
|
|
||||||
* Configuration revisions
|
|
||||||
|
|
||||||
This migration provides a more consistent user experience and unlocks advanced functionality not feasible using Django's built-in views. The admin UI is scheduled for complete removal in NetBox v4.0.
|
|
||||||
|
|
||||||
#### Configurable Default Permissions ([#13038](https://github.com/netbox-community/netbox/issues/13038))
|
|
||||||
|
|
||||||
Administrators now have the option of configuring default permissions for _all_ users globally, regardless of explicit permission or group assignments granted in the database. This is accomplished by defining the `DEFAULT_PERMISSIONS` configuration parameter. By default, all users are granted permission to manage their own bookmarks and API tokens.
|
|
||||||
|
|
||||||
#### User Bookmarks ([#8248](https://github.com/netbox-community/netbox/issues/8248))
|
|
||||||
|
|
||||||
Users can now bookmark their favorite objects in NetBox. Bookmarks are accessible under each user's personal bookmarks list, and can also be added as a dashboard widget.
|
|
||||||
|
|
||||||
#### Custom Field Choice Sets ([#12988](https://github.com/netbox-community/netbox/issues/12988))
|
|
||||||
|
|
||||||
Selection and multi-select custom fields now employ discrete, reusable choice sets containing the valid options for each field. A choice set may be shared by multiple custom fields. Additionally, each choice within a set can now specify both a raw value and a human-friendly label (see [#13241](https://github.com/netbox-community/netbox/issues/13241)). Pre-existing custom field choices are migrated to choice sets automatically during the upgrade process.
|
|
||||||
|
|
||||||
#### Pre-Defined Location Choices for Custom Fields ([#12194](https://github.com/netbox-community/netbox/issues/12194))
|
|
||||||
|
|
||||||
Users now have the option to employ one of several pre-defined sets of choices when creating a custom field. These include:
|
|
||||||
|
|
||||||
* IATA airport codes
|
|
||||||
* ISO 3166 country codes
|
|
||||||
* UN/LOCODE location identifiers
|
|
||||||
|
|
||||||
When defining a choice set, one of the above can be employed as the base set, with the option to define extra, custom choices as well.
|
|
||||||
|
|
||||||
#### Restrict Tag Usage by Object Type ([#11541](https://github.com/netbox-community/netbox/issues/11541))
|
|
||||||
|
|
||||||
Tags may now be restricted to use with designated object types. Tags that have no specific object types assigned may be used with any object that supports tag assignment.
|
|
||||||
|
|
||||||
### Enhancements
|
|
||||||
|
|
||||||
* [#6347](https://github.com/netbox-community/netbox/issues/6347) - Cache the number of assigned components for devices and virtual machines
|
|
||||||
* [#8137](https://github.com/netbox-community/netbox/issues/8137) - Add a field for designating the out-of-band (OOB) IP address for devices
|
|
||||||
* [#10197](https://github.com/netbox-community/netbox/issues/10197) - Cache the number of member devices on each virtual chassis
|
|
||||||
* [#11305](https://github.com/netbox-community/netbox/issues/11305) - Add GPS coordinate fields to the device model
|
|
||||||
* [#11519](https://github.com/netbox-community/netbox/issues/11519) - Add a SQL index for IP address host values to optimize queries
|
|
||||||
* [#11732](https://github.com/netbox-community/netbox/issues/11732) - Prevent inadvertent overwriting of object attributes by competing users
|
|
||||||
* [#11936](https://github.com/netbox-community/netbox/issues/11936) - Introduce support for tags and custom fields on webhooks
|
|
||||||
* [#12175](https://github.com/netbox-community/netbox/issues/12175) - Permit racks to start numbering at values greater than one
|
|
||||||
* [#12210](https://github.com/netbox-community/netbox/issues/12210) - Add tenancy assignment for power feeds
|
|
||||||
* [#12882](https://github.com/netbox-community/netbox/issues/12882) - Add tag support for contact assignments
|
|
||||||
* [#13170](https://github.com/netbox-community/netbox/issues/13170) - Add `rf_role` to InterfaceTemplate
|
|
||||||
* [#13269](https://github.com/netbox-community/netbox/issues/13269) - Cache the number of assigned component templates for device types
|
|
||||||
|
|
||||||
### Other Changes
|
|
||||||
|
|
||||||
* Work has begun on introducing translation and localization support in NetBox. This work is being performed in preparation for release 4.0.
|
|
||||||
* [#6391](https://github.com/netbox-community/netbox/issues/6391) - Rename the `device_role` field on Device to `role` for consistency with VirtualMachine
|
|
||||||
* [#9077](https://github.com/netbox-community/netbox/issues/9077) - Prevent the errant execution of dangerous instance methods in Django templates
|
|
||||||
* [#11766](https://github.com/netbox-community/netbox/issues/11766) - Remove obsolete custom `ChoiceField` and `MultipleChoiceField` classes
|
|
||||||
* [#12180](https://github.com/netbox-community/netbox/issues/12180) - All API endpoints for available objects (e.g. IP addresses) now inherit from a common parent view
|
|
||||||
* [#12237](https://github.com/netbox-community/netbox/issues/12237) - Upgrade Django to v4.2
|
|
||||||
* [#12794](https://github.com/netbox-community/netbox/issues/12794) - Avoid direct imports of Django's stock user model
|
|
||||||
* [#12320](https://github.com/netbox-community/netbox/issues/12320) - Remove obsolete fields `napalm_driver` and `napalm_args` from Platform
|
|
||||||
* [#12964](https://github.com/netbox-community/netbox/issues/12964) - Drop support for PostgreSQL 11
|
|
||||||
* [#13309](https://github.com/netbox-community/netbox/issues/13309) - User account-specific resources have been moved to a new `account` app for better organization
|
|
||||||
|
|
||||||
### REST API Changes
|
|
||||||
|
|
||||||
* Introduced the following endpoints:
|
|
||||||
* `/api/extras/bookmarks/`
|
|
||||||
* `/api/extras/custom-field-choice-sets/`
|
|
||||||
* Added the `/api/extras/custom-fields/{id}/choices/` endpoint for select and multi-select custom fields
|
|
||||||
* dcim.Device
|
|
||||||
* Renamed `device_role` to `device`. Added a read-only `device_role` field for limited backward compatibility.
|
|
||||||
* Added the `latitude` and `longitude` fields (for GPS coordinates)
|
|
||||||
* Added the `oob_ip` field for out-of-band IP address assignment
|
|
||||||
* dcim.DeviceType
|
|
||||||
* Added read-only counter fields for assigned component templates:
|
|
||||||
* `console_port_template_count`
|
|
||||||
* `console_server_port_template_count`
|
|
||||||
* `power_port_template_count`
|
|
||||||
* `power_outlet_template_count`
|
|
||||||
* `interface_template_count`
|
|
||||||
* `front_port_template_count`
|
|
||||||
* `rear_port_template_count`
|
|
||||||
* `device_bay_template_count`
|
|
||||||
* `module_bay_template_count`
|
|
||||||
* `inventory_item_template_count`
|
|
||||||
* dcim.InterfaceTemplate
|
|
||||||
* Added the `rf_role` field
|
|
||||||
* dcim.Platform
|
|
||||||
* Removed the `napalm_driver` and `napalm_args` fields
|
|
||||||
* dcim.PowerFeed
|
|
||||||
* Added the `tenant` field
|
|
||||||
* dcim.Rack
|
|
||||||
* Added the `starting_unit` field
|
|
||||||
* dcim.VirtualChassis
|
|
||||||
* Added the read-only `member_count` field
|
|
||||||
* extras.CustomField
|
|
||||||
* Removed the `choices` array field
|
|
||||||
* Added the `choice_set` foreign key field (to ChoiceSet)
|
|
||||||
* extras.Tag
|
|
||||||
* Added the `object_types` field for optional restriction to specific object types
|
|
||||||
* extras.Webhook
|
|
||||||
* Added `custom_fields` and `tags` support
|
|
||||||
* tenancy.ContactAssignment
|
|
||||||
* Added `tags` support
|
|
||||||
* virtualization.VirtualMachine
|
|
||||||
* Added the `oob_ip` field for out-of-band IP address assignment
|
|
||||||
@@ -206,7 +206,6 @@ nav:
|
|||||||
- VirtualChassis: 'models/dcim/virtualchassis.md'
|
- VirtualChassis: 'models/dcim/virtualchassis.md'
|
||||||
- VirtualDeviceContext: 'models/dcim/virtualdevicecontext.md'
|
- VirtualDeviceContext: 'models/dcim/virtualdevicecontext.md'
|
||||||
- Extras:
|
- Extras:
|
||||||
- Bookmark: 'models/extras/bookmark.md'
|
|
||||||
- Branch: 'models/extras/branch.md'
|
- Branch: 'models/extras/branch.md'
|
||||||
- ConfigContext: 'models/extras/configcontext.md'
|
- ConfigContext: 'models/extras/configcontext.md'
|
||||||
- ConfigTemplate: 'models/extras/configtemplate.md'
|
- ConfigTemplate: 'models/extras/configtemplate.md'
|
||||||
@@ -274,7 +273,6 @@ nav:
|
|||||||
- git Cheat Sheet: 'development/git-cheat-sheet.md'
|
- git Cheat Sheet: 'development/git-cheat-sheet.md'
|
||||||
- Release Notes:
|
- Release Notes:
|
||||||
- Summary: 'release-notes/index.md'
|
- Summary: 'release-notes/index.md'
|
||||||
- Version 3.6: 'release-notes/version-3.6.md'
|
|
||||||
- Version 3.5: 'release-notes/version-3.5.md'
|
- Version 3.5: 'release-notes/version-3.5.md'
|
||||||
- Version 3.4: 'release-notes/version-3.4.md'
|
- Version 3.4: 'release-notes/version-3.4.md'
|
||||||
- Version 3.3: 'release-notes/version-3.3.md'
|
- Version 3.3: 'release-notes/version-3.3.md'
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
# Generated by Django 4.1.10 on 2023-07-30 17:49
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('users', '0004_netboxgroup_netboxuser'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserToken',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'token',
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('users.token',),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from users.models import Token
|
|
||||||
|
|
||||||
|
|
||||||
class UserToken(Token):
|
|
||||||
"""
|
|
||||||
Proxy model for users to manage their own API tokens.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = 'token'
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('account:usertoken', args=[self.pk])
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
from account.models import UserToken
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'UserTokenTable',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
TOKEN = """<samp><span id="token_{{ record.pk }}">{{ record }}</span></samp>"""
|
|
||||||
|
|
||||||
ALLOWED_IPS = """{{ value|join:", " }}"""
|
|
||||||
|
|
||||||
COPY_BUTTON = """
|
|
||||||
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
|
|
||||||
{% copy_content record.pk prefix="token_" color="success" %}
|
|
||||||
{% endif %}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class UserTokenTable(NetBoxTable):
|
|
||||||
"""
|
|
||||||
Table for users to manager their own API tokens under account views.
|
|
||||||
"""
|
|
||||||
key = columns.TemplateColumn(
|
|
||||||
verbose_name=_('Key'),
|
|
||||||
template_code=TOKEN,
|
|
||||||
)
|
|
||||||
write_enabled = columns.BooleanColumn(
|
|
||||||
verbose_name=_('Write Enabled')
|
|
||||||
)
|
|
||||||
created = columns.DateColumn(
|
|
||||||
verbose_name=_('Created'),
|
|
||||||
)
|
|
||||||
expires = columns.DateColumn(
|
|
||||||
verbose_name=_('Expires'),
|
|
||||||
)
|
|
||||||
last_used = columns.DateTimeColumn(
|
|
||||||
verbose_name=_('Last Used'),
|
|
||||||
)
|
|
||||||
allowed_ips = columns.TemplateColumn(
|
|
||||||
verbose_name=_('Allowed IPs'),
|
|
||||||
template_code=ALLOWED_IPS
|
|
||||||
)
|
|
||||||
actions = columns.ActionsColumn(
|
|
||||||
actions=('edit', 'delete'),
|
|
||||||
extra_buttons=COPY_BUTTON
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
|
||||||
model = UserToken
|
|
||||||
fields = (
|
|
||||||
'pk', 'id', 'key', 'description', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from django.urls import include, path
|
|
||||||
|
|
||||||
from utilities.urls import get_model_urls
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
app_name = 'account'
|
|
||||||
urlpatterns = [
|
|
||||||
|
|
||||||
# Account views
|
|
||||||
path('profile/', views.ProfileView.as_view(), name='profile'),
|
|
||||||
path('bookmarks/', views.BookmarkListView.as_view(), name='bookmarks'),
|
|
||||||
path('preferences/', views.UserConfigView.as_view(), name='preferences'),
|
|
||||||
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
|
|
||||||
path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
|
|
||||||
path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
|
|
||||||
path('api-tokens/<int:pk>/', include(get_model_urls('account', 'usertoken'))),
|
|
||||||
|
|
||||||
]
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
|
||||||
from django.contrib.auth import update_session_auth_hash
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.contrib.auth.models import update_last_login
|
|
||||||
from django.contrib.auth.signals import user_logged_in
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
|
||||||
from django.shortcuts import render, resolve_url
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
|
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
|
||||||
from django.views.generic import View
|
|
||||||
from social_core.backends.utils import load_backends
|
|
||||||
|
|
||||||
from account.models import UserToken
|
|
||||||
from extras.models import Bookmark, ObjectChange
|
|
||||||
from extras.tables import BookmarkTable, ObjectChangeTable
|
|
||||||
from netbox.authentication import get_auth_backend_display, get_saml_idps
|
|
||||||
from netbox.config import get_config
|
|
||||||
from netbox.views import generic
|
|
||||||
from users import forms, tables
|
|
||||||
from users.models import UserConfig
|
|
||||||
from utilities.views import register_model_view
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Login/logout
|
|
||||||
#
|
|
||||||
|
|
||||||
class LoginView(View):
|
|
||||||
"""
|
|
||||||
Perform user authentication via the web UI.
|
|
||||||
"""
|
|
||||||
template_name = 'login.html'
|
|
||||||
|
|
||||||
@method_decorator(sensitive_post_parameters('password'))
|
|
||||||
def dispatch(self, *args, **kwargs):
|
|
||||||
return super().dispatch(*args, **kwargs)
|
|
||||||
|
|
||||||
def gen_auth_data(self, name, url, params):
|
|
||||||
display_name, icon_name = get_auth_backend_display(name)
|
|
||||||
return {
|
|
||||||
'display_name': display_name,
|
|
||||||
'icon_name': icon_name,
|
|
||||||
'url': f'{url}?{urlencode(params)}',
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_auth_backends(self, request):
|
|
||||||
auth_backends = []
|
|
||||||
saml_idps = get_saml_idps()
|
|
||||||
|
|
||||||
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
|
|
||||||
url = reverse('social:begin', args=[name])
|
|
||||||
params = {}
|
|
||||||
if next := request.GET.get('next'):
|
|
||||||
params['next'] = next
|
|
||||||
if name.lower() == 'saml' and saml_idps:
|
|
||||||
for idp in saml_idps:
|
|
||||||
params['idp'] = idp
|
|
||||||
data = self.gen_auth_data(name, url, params)
|
|
||||||
data['display_name'] = f'{data["display_name"]} ({idp})'
|
|
||||||
auth_backends.append(data)
|
|
||||||
else:
|
|
||||||
auth_backends.append(self.gen_auth_data(name, url, params))
|
|
||||||
|
|
||||||
return auth_backends
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
form = forms.LoginForm(request)
|
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
logger = logging.getLogger('netbox.auth.login')
|
|
||||||
return self.redirect_to_next(request, logger)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'auth_backends': self.get_auth_backends(request),
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
logger = logging.getLogger('netbox.auth.login')
|
|
||||||
form = forms.LoginForm(request, data=request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
logger.debug("Login form validation was successful")
|
|
||||||
|
|
||||||
# If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
|
|
||||||
# last_login time upon authentication.
|
|
||||||
if get_config().MAINTENANCE_MODE:
|
|
||||||
logger.warning("Maintenance mode enabled: disabling update of most recent login time")
|
|
||||||
user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
|
|
||||||
|
|
||||||
# Authenticate user
|
|
||||||
auth_login(request, form.get_user())
|
|
||||||
logger.info(f"User {request.user} successfully authenticated")
|
|
||||||
messages.success(request, f"Logged in as {request.user}.")
|
|
||||||
|
|
||||||
# Ensure the user has a UserConfig defined. (This should normally be handled by
|
|
||||||
# create_userconfig() on user creation.)
|
|
||||||
if not hasattr(request.user, 'config'):
|
|
||||||
config = get_config()
|
|
||||||
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
|
|
||||||
|
|
||||||
return self.redirect_to_next(request, logger)
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'auth_backends': self.get_auth_backends(request),
|
|
||||||
})
|
|
||||||
|
|
||||||
def redirect_to_next(self, request, logger):
|
|
||||||
data = request.POST if request.method == "POST" else request.GET
|
|
||||||
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
|
|
||||||
|
|
||||||
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
|
|
||||||
logger.debug(f"Redirecting user to {redirect_url}")
|
|
||||||
else:
|
|
||||||
if redirect_url:
|
|
||||||
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_url}")
|
|
||||||
redirect_url = reverse('home')
|
|
||||||
|
|
||||||
return HttpResponseRedirect(redirect_url)
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(View):
|
|
||||||
"""
|
|
||||||
Deauthenticate a web user.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
logger = logging.getLogger('netbox.auth.logout')
|
|
||||||
|
|
||||||
# Log out the user
|
|
||||||
username = request.user
|
|
||||||
auth_logout(request)
|
|
||||||
logger.info(f"User {username} has logged out")
|
|
||||||
messages.info(request, "You have logged out.")
|
|
||||||
|
|
||||||
# Delete session key cookie (if set) upon logout
|
|
||||||
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
|
|
||||||
response.delete_cookie('session_key')
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# User profiles
|
|
||||||
#
|
|
||||||
|
|
||||||
class ProfileView(LoginRequiredMixin, View):
|
|
||||||
template_name = 'account/profile.html'
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
|
|
||||||
# Compile changelog table
|
|
||||||
changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter(
|
|
||||||
user=request.user
|
|
||||||
).prefetch_related(
|
|
||||||
'changed_object_type'
|
|
||||||
)[:20]
|
|
||||||
changelog_table = ObjectChangeTable(changelog)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'changelog_table': changelog_table,
|
|
||||||
'active_tab': 'profile',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class UserConfigView(LoginRequiredMixin, View):
|
|
||||||
template_name = 'account/preferences.html'
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
userconfig = request.user.config
|
|
||||||
form = forms.UserConfigForm(instance=userconfig)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'active_tab': 'preferences',
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
userconfig = request.user.config
|
|
||||||
form = forms.UserConfigForm(request.POST, instance=userconfig)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
|
|
||||||
messages.success(request, "Your preferences have been updated.")
|
|
||||||
return redirect('account:preferences')
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'active_tab': 'preferences',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordView(LoginRequiredMixin, View):
|
|
||||||
template_name = 'account/password.html'
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
# LDAP users cannot change their password here
|
|
||||||
if getattr(request.user, 'ldap_username', None):
|
|
||||||
messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
|
|
||||||
return redirect('account:profile')
|
|
||||||
|
|
||||||
form = forms.PasswordChangeForm(user=request.user)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'active_tab': 'password',
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
form = forms.PasswordChangeForm(user=request.user, data=request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
update_session_auth_hash(request, form.user)
|
|
||||||
messages.success(request, "Your password has been changed successfully.")
|
|
||||||
return redirect('account:profile')
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'active_tab': 'change_password',
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Bookmarks
|
|
||||||
#
|
|
||||||
|
|
||||||
class BookmarkListView(LoginRequiredMixin, generic.ObjectListView):
|
|
||||||
table = BookmarkTable
|
|
||||||
template_name = 'account/bookmarks.html'
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return Bookmark.objects.filter(user=request.user)
|
|
||||||
|
|
||||||
def get_extra_context(self, request):
|
|
||||||
return {
|
|
||||||
'active_tab': 'bookmarks',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# User views for token management
|
|
||||||
#
|
|
||||||
|
|
||||||
class UserTokenListView(LoginRequiredMixin, View):
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
tokens = UserToken.objects.filter(user=request.user)
|
|
||||||
table = tables.UserTokenTable(tokens)
|
|
||||||
table.configure(request)
|
|
||||||
|
|
||||||
return render(request, 'account/token_list.html', {
|
|
||||||
'tokens': tokens,
|
|
||||||
'active_tab': 'api-tokens',
|
|
||||||
'table': table,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(UserToken)
|
|
||||||
class UserTokenView(LoginRequiredMixin, View):
|
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
|
||||||
key = token.key if settings.ALLOW_TOKEN_RETRIEVAL else None
|
|
||||||
|
|
||||||
return render(request, 'account/token.html', {
|
|
||||||
'object': token,
|
|
||||||
'key': key,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(UserToken, 'edit')
|
|
||||||
class UserTokenEditView(generic.ObjectEditView):
|
|
||||||
queryset = UserToken.objects.all()
|
|
||||||
form = forms.UserTokenForm
|
|
||||||
default_return_url = 'account:usertoken_list'
|
|
||||||
|
|
||||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
|
||||||
if not obj.pk:
|
|
||||||
obj.user = request.user
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(UserToken, 'delete')
|
|
||||||
class UserTokenDeleteView(generic.ObjectDeleteView):
|
|
||||||
queryset = UserToken.objects.all()
|
|
||||||
default_return_url = 'account:usertoken_list'
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from utilities.choices import ChoiceSet
|
from utilities.choices import ChoiceSet
|
||||||
|
|
||||||
|
|
||||||
@@ -18,12 +16,12 @@ class CircuitStatusChoices(ChoiceSet):
|
|||||||
STATUS_DECOMMISSIONED = 'decommissioned'
|
STATUS_DECOMMISSIONED = 'decommissioned'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_PROVISIONING, _('Provisioning'), 'blue'),
|
(STATUS_PROVISIONING, 'Provisioning', 'blue'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_OFFLINE, _('Offline'), 'red'),
|
(STATUS_OFFLINE, 'Offline', 'red'),
|
||||||
(STATUS_DEPROVISIONING, _('Deprovisioning'), 'yellow'),
|
(STATUS_DEPROVISIONING, 'Deprovisioning', 'yellow'),
|
||||||
(STATUS_DECOMMISSIONED, _('Decommissioned'), 'gray'),
|
(STATUS_DECOMMISSIONED, 'Decommissioned', 'gray'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
|
from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
@@ -26,11 +26,12 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField(
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
@@ -43,16 +44,16 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField(
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
|
|
||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
@@ -65,7 +66,6 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -75,11 +75,12 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
label=_('Service ID')
|
label=_('Service ID')
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField(
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
|
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
@@ -92,7 +93,6 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -106,17 +106,14 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
type = DynamicModelChoiceField(
|
type = DynamicModelChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
queryset=CircuitType.objects.all(),
|
queryset=CircuitType.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
provider_account = DynamicModelChoiceField(
|
provider_account = DynamicModelChoiceField(
|
||||||
label=_('Provider account'),
|
|
||||||
queryset=ProviderAccount.objects.all(),
|
queryset=ProviderAccount.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -124,23 +121,19 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=add_blank_choice(CircuitStatusChoices),
|
choices=add_blank_choice(CircuitStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial=''
|
initial=''
|
||||||
)
|
)
|
||||||
tenant = DynamicModelChoiceField(
|
tenant = DynamicModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
install_date = forms.DateField(
|
install_date = forms.DateField(
|
||||||
label=_('Install date'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePicker()
|
widget=DatePicker()
|
||||||
)
|
)
|
||||||
termination_date = forms.DateField(
|
termination_date = forms.DateField(
|
||||||
label=_('Termination date'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePicker()
|
widget=DatePicker()
|
||||||
)
|
)
|
||||||
@@ -152,17 +145,18 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField(
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
|
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit'), ('provider', 'type', 'status', 'description')),
|
('Circuit', ('provider', 'type', 'status', 'description')),
|
||||||
(_('Service Parameters'), ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
|
('Service Parameters', ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
|
||||||
(_('Tenancy'), ('tenant',)),
|
('Tenancy', ('tenant',)),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'tenant', 'commit_rate', 'description', 'comments',
|
'tenant', 'commit_rate', 'description', 'comments',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django import forms
|
|||||||
from circuits.choices import CircuitStatusChoices
|
from circuits.choices import CircuitStatusChoices
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
@@ -31,7 +31,6 @@ class ProviderImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ProviderAccountImportForm(NetBoxModelImportForm):
|
class ProviderAccountImportForm(NetBoxModelImportForm):
|
||||||
provider = CSVModelChoiceField(
|
provider = CSVModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned provider')
|
help_text=_('Assigned provider')
|
||||||
@@ -46,7 +45,6 @@ class ProviderAccountImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ProviderNetworkImportForm(NetBoxModelImportForm):
|
class ProviderNetworkImportForm(NetBoxModelImportForm):
|
||||||
provider = CSVModelChoiceField(
|
provider = CSVModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned provider')
|
help_text=_('Assigned provider')
|
||||||
@@ -69,31 +67,26 @@ class CircuitTypeImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class CircuitImportForm(NetBoxModelImportForm):
|
class CircuitImportForm(NetBoxModelImportForm):
|
||||||
provider = CSVModelChoiceField(
|
provider = CSVModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned provider')
|
help_text=_('Assigned provider')
|
||||||
)
|
)
|
||||||
provider_account = CSVModelChoiceField(
|
provider_account = CSVModelChoiceField(
|
||||||
label=_('Provider account'),
|
|
||||||
queryset=ProviderAccount.objects.all(),
|
queryset=ProviderAccount.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned provider account'),
|
help_text=_('Assigned provider account'),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
type = CSVModelChoiceField(
|
type = CSVModelChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
queryset=CircuitType.objects.all(),
|
queryset=CircuitType.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Type of circuit')
|
help_text=_('Type of circuit')
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=CircuitStatusChoices,
|
choices=CircuitStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -110,13 +103,11 @@ class CircuitImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
|
class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
provider_network = CSVModelChoiceField(
|
provider_network = CSVModelChoiceField(
|
||||||
label=_('Provider network'),
|
|
||||||
queryset=ProviderNetwork.objects.all(),
|
queryset=ProviderNetwork.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = Provider
|
model = Provider
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||||
(_('ASN'), ('asn',)),
|
('ASN', ('asn',)),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -62,7 +62,7 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('provider_id', 'account')),
|
('Attributes', ('provider_id', 'account')),
|
||||||
)
|
)
|
||||||
provider_id = DynamicModelMultipleChoiceField(
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
@@ -70,7 +70,6 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('Provider')
|
label=_('Provider')
|
||||||
)
|
)
|
||||||
account = forms.CharField(
|
account = forms.CharField(
|
||||||
label=_('Account'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
@@ -80,7 +79,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('provider_id', 'service_id')),
|
('Attributes', ('provider_id', 'service_id')),
|
||||||
)
|
)
|
||||||
provider_id = DynamicModelMultipleChoiceField(
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
@@ -88,7 +87,6 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('Provider')
|
label=_('Provider')
|
||||||
)
|
)
|
||||||
service_id = forms.CharField(
|
service_id = forms.CharField(
|
||||||
label=_('Service id'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -104,11 +102,11 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
|
|||||||
model = Circuit
|
model = Circuit
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Provider'), ('provider_id', 'provider_account_id', 'provider_network_id')),
|
('Provider', ('provider_id', 'provider_account_id', 'provider_network_id')),
|
||||||
(_('Attributes'), ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
|
('Attributes', ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
type_id = DynamicModelMultipleChoiceField(
|
type_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=CircuitType.objects.all(),
|
queryset=CircuitType.objects.all(),
|
||||||
@@ -137,7 +135,6 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
|
|||||||
label=_('Provider network')
|
label=_('Provider network')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=CircuitStatusChoices,
|
choices=CircuitStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -161,12 +158,10 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
|
|||||||
label=_('Site')
|
label=_('Site')
|
||||||
)
|
)
|
||||||
install_date = forms.DateField(
|
install_date = forms.DateField(
|
||||||
label=_('Install date'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePicker
|
widget=DatePicker
|
||||||
)
|
)
|
||||||
termination_date = forms.DateField(
|
termination_date = forms.DateField(
|
||||||
label=_('Termination date'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePicker
|
widget=DatePicker
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
|
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
@@ -29,7 +29,7 @@ class ProviderForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Provider'), ('name', 'slug', 'asns', 'description', 'tags')),
|
('Provider', ('name', 'slug', 'asns', 'description', 'tags')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -41,7 +41,6 @@ class ProviderForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class ProviderAccountForm(NetBoxModelForm):
|
class ProviderAccountForm(NetBoxModelForm):
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all()
|
queryset=Provider.objects.all()
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
@@ -55,13 +54,12 @@ class ProviderAccountForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class ProviderNetworkForm(NetBoxModelForm):
|
class ProviderNetworkForm(NetBoxModelForm):
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all()
|
queryset=Provider.objects.all()
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Provider Network'), ('provider', 'name', 'service_id', 'description', 'tags')),
|
('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -75,7 +73,7 @@ class CircuitTypeForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit Type'), (
|
('Circuit Type', (
|
||||||
'name', 'slug', 'description', 'tags',
|
'name', 'slug', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -89,12 +87,10 @@ class CircuitTypeForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class CircuitForm(TenancyForm, NetBoxModelForm):
|
class CircuitForm(TenancyForm, NetBoxModelForm):
|
||||||
provider = DynamicModelChoiceField(
|
provider = DynamicModelChoiceField(
|
||||||
label=_('Provider'),
|
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
provider_account = DynamicModelChoiceField(
|
provider_account = DynamicModelChoiceField(
|
||||||
label=_('Provider account'),
|
|
||||||
queryset=ProviderAccount.objects.all(),
|
queryset=ProviderAccount.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -107,9 +103,9 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit'), ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
|
('Circuit', ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
|
||||||
(_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
|
('Service Parameters', ('install_date', 'termination_date', 'commit_rate')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -129,18 +125,15 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class CircuitTerminationForm(NetBoxModelForm):
|
class CircuitTerminationForm(NetBoxModelForm):
|
||||||
circuit = DynamicModelChoiceField(
|
circuit = DynamicModelChoiceField(
|
||||||
label=_('Circuit'),
|
|
||||||
queryset=Circuit.objects.all(),
|
queryset=Circuit.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
provider_network = DynamicModelChoiceField(
|
provider_network = DynamicModelChoiceField(
|
||||||
label=_('Provider network'),
|
|
||||||
queryset=ProviderNetwork.objects.all(),
|
queryset=ProviderNetwork.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
selector=True
|
selector=True
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from dcim.models import CabledObjectModel
|
from dcim.models import CabledObjectModel
|
||||||
@@ -34,8 +34,8 @@ class Circuit(PrimaryModel):
|
|||||||
"""
|
"""
|
||||||
cid = models.CharField(
|
cid = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
verbose_name=_('circuit ID'),
|
verbose_name='Circuit ID',
|
||||||
help_text=_('Unique circuit ID')
|
help_text=_("Unique circuit ID")
|
||||||
)
|
)
|
||||||
provider = models.ForeignKey(
|
provider = models.ForeignKey(
|
||||||
to='circuits.Provider',
|
to='circuits.Provider',
|
||||||
@@ -55,7 +55,6 @@ class Circuit(PrimaryModel):
|
|||||||
related_name='circuits'
|
related_name='circuits'
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=CircuitStatusChoices,
|
choices=CircuitStatusChoices,
|
||||||
default=CircuitStatusChoices.STATUS_ACTIVE
|
default=CircuitStatusChoices.STATUS_ACTIVE
|
||||||
@@ -70,17 +69,17 @@ class Circuit(PrimaryModel):
|
|||||||
install_date = models.DateField(
|
install_date = models.DateField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('installed')
|
verbose_name='Installed'
|
||||||
)
|
)
|
||||||
termination_date = models.DateField(
|
termination_date = models.DateField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('terminates')
|
verbose_name='Terminates'
|
||||||
)
|
)
|
||||||
commit_rate = models.PositiveIntegerField(
|
commit_rate = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('commit rate (Kbps)'),
|
verbose_name='Commit rate (Kbps)',
|
||||||
help_text=_("Committed rate")
|
help_text=_("Committed rate")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,7 +162,7 @@ class CircuitTermination(
|
|||||||
term_side = models.CharField(
|
term_side = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
choices=CircuitTerminationSideChoices,
|
choices=CircuitTerminationSideChoices,
|
||||||
verbose_name=_('termination')
|
verbose_name='Termination'
|
||||||
)
|
)
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
to='dcim.Site',
|
to='dcim.Site',
|
||||||
@@ -180,31 +179,30 @@ class CircuitTermination(
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
port_speed = models.PositiveIntegerField(
|
port_speed = models.PositiveIntegerField(
|
||||||
verbose_name=_('port speed (Kbps)'),
|
verbose_name='Port speed (Kbps)',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_('Physical circuit speed')
|
help_text=_("Physical circuit speed")
|
||||||
)
|
)
|
||||||
upstream_speed = models.PositiveIntegerField(
|
upstream_speed = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('upstream speed (Kbps)'),
|
verbose_name='Upstream speed (Kbps)',
|
||||||
help_text=_('Upstream speed, if different from port speed')
|
help_text=_('Upstream speed, if different from port speed')
|
||||||
)
|
)
|
||||||
xconnect_id = models.CharField(
|
xconnect_id = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('cross-connect ID'),
|
verbose_name='Cross-connect ID',
|
||||||
help_text=_('ID of the local cross-connect')
|
help_text=_("ID of the local cross-connect")
|
||||||
)
|
)
|
||||||
pp_info = models.CharField(
|
pp_info = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('patch panel/port(s)'),
|
verbose_name='Patch panel/port(s)',
|
||||||
help_text=_('Patch panel ID and port number(s)')
|
help_text=_("Patch panel ID and port number(s)")
|
||||||
)
|
)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
verbose_name=_('description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
|
|
||||||
@@ -19,13 +19,11 @@ class Provider(PrimaryModel):
|
|||||||
stores information pertinent to the user's relationship with the Provider.
|
stores information pertinent to the user's relationship with the Provider.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True,
|
unique=True,
|
||||||
help_text=_('Full name of the provider')
|
help_text=_("Full name of the provider")
|
||||||
)
|
)
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
verbose_name=_('slug'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
@@ -63,10 +61,9 @@ class ProviderAccount(PrimaryModel):
|
|||||||
)
|
)
|
||||||
account = models.CharField(
|
account = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
verbose_name=_('account ID')
|
verbose_name='Account ID'
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
@@ -107,7 +104,6 @@ class ProviderNetwork(PrimaryModel):
|
|||||||
unimportant to the user.
|
unimportant to the user.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
provider = models.ForeignKey(
|
provider = models.ForeignKey(
|
||||||
@@ -118,7 +114,7 @@ class ProviderNetwork(PrimaryModel):
|
|||||||
service_id = models.CharField(
|
service_id = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('service ID')
|
verbose_name='Service ID'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
@@ -25,8 +24,7 @@ CIRCUITTERMINATION_LINK = """
|
|||||||
|
|
||||||
class CircuitTypeTable(NetBoxTable):
|
class CircuitTypeTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
linkify=True,
|
linkify=True
|
||||||
verbose_name=_('Name'),
|
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:circuittype_list'
|
url_name='circuits:circuittype_list'
|
||||||
@@ -34,7 +32,7 @@ class CircuitTypeTable(NetBoxTable):
|
|||||||
circuit_count = columns.LinkedCountColumn(
|
circuit_count = columns.LinkedCountColumn(
|
||||||
viewname='circuits:circuit_list',
|
viewname='circuits:circuit_list',
|
||||||
url_params={'type_id': 'pk'},
|
url_params={'type_id': 'pk'},
|
||||||
verbose_name=_('Circuits')
|
verbose_name='Circuits'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
@@ -48,31 +46,28 @@ class CircuitTypeTable(NetBoxTable):
|
|||||||
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||||
cid = tables.Column(
|
cid = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Circuit ID')
|
verbose_name='Circuit ID'
|
||||||
)
|
)
|
||||||
provider = tables.Column(
|
provider = tables.Column(
|
||||||
verbose_name=_('Provider'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
provider_account = tables.Column(
|
provider_account = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Account')
|
verbose_name='Account'
|
||||||
)
|
)
|
||||||
status = columns.ChoiceFieldColumn()
|
status = columns.ChoiceFieldColumn()
|
||||||
termination_a = tables.TemplateColumn(
|
termination_a = tables.TemplateColumn(
|
||||||
template_code=CIRCUITTERMINATION_LINK,
|
template_code=CIRCUITTERMINATION_LINK,
|
||||||
verbose_name=_('Side A')
|
verbose_name='Side A'
|
||||||
)
|
)
|
||||||
termination_z = tables.TemplateColumn(
|
termination_z = tables.TemplateColumn(
|
||||||
template_code=CIRCUITTERMINATION_LINK,
|
template_code=CIRCUITTERMINATION_LINK,
|
||||||
verbose_name=_('Side Z')
|
verbose_name='Side Z'
|
||||||
)
|
)
|
||||||
commit_rate = CommitRateColumn(
|
commit_rate = CommitRateColumn(
|
||||||
verbose_name=_('Commit Rate')
|
verbose_name='Commit Rate'
|
||||||
)
|
|
||||||
comments = columns.MarkdownColumn(
|
|
||||||
verbose_name=_('Comments'),
|
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:circuit_list'
|
url_name='circuits:circuit_list'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
from tenancy.tables import ContactsColumnMixin
|
from tenancy.tables import ContactsColumnMixin
|
||||||
@@ -15,38 +14,35 @@ __all__ = (
|
|||||||
|
|
||||||
class ProviderTable(ContactsColumnMixin, NetBoxTable):
|
class ProviderTable(ContactsColumnMixin, NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
accounts = columns.ManyToManyColumn(
|
accounts = columns.ManyToManyColumn(
|
||||||
linkify_item=True,
|
linkify_item=True,
|
||||||
verbose_name=_('Accounts')
|
verbose_name='Accounts'
|
||||||
)
|
)
|
||||||
account_count = columns.LinkedCountColumn(
|
account_count = columns.LinkedCountColumn(
|
||||||
accessor=tables.A('accounts__count'),
|
accessor=tables.A('accounts__count'),
|
||||||
viewname='circuits:provideraccount_list',
|
viewname='circuits:provideraccount_list',
|
||||||
url_params={'account_id': 'pk'},
|
url_params={'account_id': 'pk'},
|
||||||
verbose_name=_('Account Count')
|
verbose_name='Account Count'
|
||||||
)
|
)
|
||||||
asns = columns.ManyToManyColumn(
|
asns = columns.ManyToManyColumn(
|
||||||
linkify_item=True,
|
linkify_item=True,
|
||||||
verbose_name=_('ASNs')
|
verbose_name='ASNs'
|
||||||
)
|
)
|
||||||
asn_count = columns.LinkedCountColumn(
|
asn_count = columns.LinkedCountColumn(
|
||||||
accessor=tables.A('asns__count'),
|
accessor=tables.A('asns__count'),
|
||||||
viewname='ipam:asn_list',
|
viewname='ipam:asn_list',
|
||||||
url_params={'provider_id': 'pk'},
|
url_params={'provider_id': 'pk'},
|
||||||
verbose_name=_('ASN Count')
|
verbose_name='ASN Count'
|
||||||
)
|
)
|
||||||
circuit_count = columns.LinkedCountColumn(
|
circuit_count = columns.LinkedCountColumn(
|
||||||
accessor=Accessor('count_circuits'),
|
accessor=Accessor('count_circuits'),
|
||||||
viewname='circuits:circuit_list',
|
viewname='circuits:circuit_list',
|
||||||
url_params={'provider_id': 'pk'},
|
url_params={'provider_id': 'pk'},
|
||||||
verbose_name=_('Circuits')
|
verbose_name='Circuits'
|
||||||
)
|
|
||||||
comments = columns.MarkdownColumn(
|
|
||||||
verbose_name=_('Comments'),
|
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:provider_list'
|
url_name='circuits:provider_list'
|
||||||
)
|
)
|
||||||
@@ -62,25 +58,19 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
|
|||||||
|
|
||||||
class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
|
class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
|
||||||
account = tables.Column(
|
account = tables.Column(
|
||||||
linkify=True,
|
linkify=True
|
||||||
verbose_name=_('Account'),
|
|
||||||
)
|
|
||||||
name = tables.Column(
|
|
||||||
verbose_name=_('Name'),
|
|
||||||
)
|
)
|
||||||
|
name = tables.Column()
|
||||||
provider = tables.Column(
|
provider = tables.Column(
|
||||||
verbose_name=_('Provider'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
circuit_count = columns.LinkedCountColumn(
|
circuit_count = columns.LinkedCountColumn(
|
||||||
accessor=Accessor('count_circuits'),
|
accessor=Accessor('count_circuits'),
|
||||||
viewname='circuits:circuit_list',
|
viewname='circuits:circuit_list',
|
||||||
url_params={'provider_account_id': 'pk'},
|
url_params={'provider_account_id': 'pk'},
|
||||||
verbose_name=_('Circuits')
|
verbose_name='Circuits'
|
||||||
)
|
|
||||||
comments = columns.MarkdownColumn(
|
|
||||||
verbose_name=_('Comments'),
|
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:provideraccount_list'
|
url_name='circuits:provideraccount_list'
|
||||||
)
|
)
|
||||||
@@ -96,16 +86,12 @@ class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
|
|||||||
|
|
||||||
class ProviderNetworkTable(NetBoxTable):
|
class ProviderNetworkTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
provider = tables.Column(
|
provider = tables.Column(
|
||||||
verbose_name=_('Provider'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
comments = columns.MarkdownColumn(
|
comments = columns.MarkdownColumn()
|
||||||
verbose_name=_('Comments'),
|
|
||||||
)
|
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:providernetwork_list'
|
url_name='circuits:providernetwork_list'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class ProviderNetworkView(generic.ObjectView):
|
|||||||
related_models = (
|
related_models = (
|
||||||
(
|
(
|
||||||
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
||||||
'providernetwork_id',
|
'provider_network_id',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from utilities.choices import ChoiceSet
|
from utilities.choices import ChoiceSet
|
||||||
|
|
||||||
@@ -63,12 +63,12 @@ class JobStatusChoices(ChoiceSet):
|
|||||||
STATUS_FAILED = 'failed'
|
STATUS_FAILED = 'failed'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(STATUS_PENDING, _('Pending'), 'cyan'),
|
(STATUS_PENDING, 'Pending', 'cyan'),
|
||||||
(STATUS_SCHEDULED, _('Scheduled'), 'gray'),
|
(STATUS_SCHEDULED, 'Scheduled', 'gray'),
|
||||||
(STATUS_RUNNING, _('Running'), 'blue'),
|
(STATUS_RUNNING, 'Running', 'blue'),
|
||||||
(STATUS_COMPLETED, _('Completed'), 'green'),
|
(STATUS_COMPLETED, 'Completed', 'green'),
|
||||||
(STATUS_ERRORED, _('Errored'), 'red'),
|
(STATUS_ERRORED, 'Errored', 'red'),
|
||||||
(STATUS_FAILED, _('Failed'), 'red'),
|
(STATUS_FAILED, 'Failed', 'red'),
|
||||||
)
|
)
|
||||||
|
|
||||||
TERMINAL_STATE_CHOICES = (
|
TERMINAL_STATE_CHOICES = (
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ from contextlib import contextmanager
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
from botocore.config import Config as Boto3Config
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from dulwich import porcelain
|
||||||
|
from dulwich.config import ConfigDict
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from .choices import DataSourceTypeChoices
|
from .choices import DataSourceTypeChoices
|
||||||
@@ -39,20 +43,9 @@ class DataBackend:
|
|||||||
parameters = {}
|
parameters = {}
|
||||||
sensitive_parameters = []
|
sensitive_parameters = []
|
||||||
|
|
||||||
# Prevent Django's template engine from calling the backend
|
|
||||||
# class when referenced via DataSource.backend_class
|
|
||||||
do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def __init__(self, url, **kwargs):
|
def __init__(self, url, **kwargs):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.params = kwargs
|
self.params = kwargs
|
||||||
self.config = self.init_config()
|
|
||||||
|
|
||||||
def init_config(self):
|
|
||||||
"""
|
|
||||||
Hook to initialize the instance's configuration.
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url_scheme(self):
|
def url_scheme(self):
|
||||||
@@ -65,7 +58,6 @@ class DataBackend:
|
|||||||
|
|
||||||
@register_backend(DataSourceTypeChoices.LOCAL)
|
@register_backend(DataSourceTypeChoices.LOCAL)
|
||||||
class LocalBackend(DataBackend):
|
class LocalBackend(DataBackend):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
logger.debug(f"Data source type is local; skipping fetch")
|
logger.debug(f"Data source type is local; skipping fetch")
|
||||||
@@ -97,34 +89,21 @@ class GitBackend(DataBackend):
|
|||||||
}
|
}
|
||||||
sensitive_parameters = ['password']
|
sensitive_parameters = ['password']
|
||||||
|
|
||||||
def init_config(self):
|
|
||||||
from dulwich.config import ConfigDict
|
|
||||||
|
|
||||||
# Initialize backend config
|
|
||||||
config = ConfigDict()
|
|
||||||
|
|
||||||
# Apply HTTP proxy (if configured)
|
|
||||||
if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
|
|
||||||
if proxy := settings.HTTP_PROXIES.get(self.url_scheme):
|
|
||||||
config.set("http", "proxy", proxy)
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
from dulwich import porcelain
|
|
||||||
|
|
||||||
local_path = tempfile.TemporaryDirectory()
|
local_path = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
|
config = ConfigDict()
|
||||||
clone_args = {
|
clone_args = {
|
||||||
"branch": self.params.get('branch'),
|
"branch": self.params.get('branch'),
|
||||||
"config": self.config,
|
"config": config,
|
||||||
"depth": 1,
|
"depth": 1,
|
||||||
"errstream": porcelain.NoneStream(),
|
"errstream": porcelain.NoneStream(),
|
||||||
"quiet": True,
|
"quiet": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.url_scheme in ('http', 'https'):
|
if self.url_scheme in ('http', 'https'):
|
||||||
|
if self.params.get('username'):
|
||||||
clone_args.update(
|
clone_args.update(
|
||||||
{
|
{
|
||||||
"username": self.params.get('username'),
|
"username": self.params.get('username'),
|
||||||
@@ -132,6 +111,10 @@ class GitBackend(DataBackend):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
|
||||||
|
if proxy := settings.HTTP_PROXIES.get(self.url_scheme):
|
||||||
|
config.set("http", "proxy", proxy)
|
||||||
|
|
||||||
logger.debug(f"Cloning git repo: {self.url}")
|
logger.debug(f"Cloning git repo: {self.url}")
|
||||||
try:
|
try:
|
||||||
porcelain.clone(self.url, local_path.name, **clone_args)
|
porcelain.clone(self.url, local_path.name, **clone_args)
|
||||||
@@ -159,20 +142,15 @@ class S3Backend(DataBackend):
|
|||||||
|
|
||||||
REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com'
|
REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com'
|
||||||
|
|
||||||
def init_config(self):
|
|
||||||
from botocore.config import Config as Boto3Config
|
|
||||||
|
|
||||||
# Initialize backend config
|
|
||||||
return Boto3Config(
|
|
||||||
proxies=settings.HTTP_PROXIES,
|
|
||||||
)
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
import boto3
|
|
||||||
|
|
||||||
local_path = tempfile.TemporaryDirectory()
|
local_path = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
|
# Build the S3 configuration
|
||||||
|
s3_config = Boto3Config(
|
||||||
|
proxies=settings.HTTP_PROXIES,
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize the S3 resource and bucket
|
# Initialize the S3 resource and bucket
|
||||||
aws_access_key_id = self.params.get('aws_access_key_id')
|
aws_access_key_id = self.params.get('aws_access_key_id')
|
||||||
aws_secret_access_key = self.params.get('aws_secret_access_key')
|
aws_secret_access_key = self.params.get('aws_secret_access_key')
|
||||||
@@ -181,7 +159,7 @@ class S3Backend(DataBackend):
|
|||||||
region_name=self._region_name,
|
region_name=self._region_name,
|
||||||
aws_access_key_id=aws_access_key_id,
|
aws_access_key_id=aws_access_key_id,
|
||||||
aws_secret_access_key=aws_secret_access_key,
|
aws_secret_access_key=aws_secret_access_key,
|
||||||
config=self.config
|
config=s3_config
|
||||||
)
|
)
|
||||||
bucket = s3.Bucket(self._bucket_name)
|
bucket = s3.Bucket(self._bucket_name)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.choices import DataSourceTypeChoices
|
from core.choices import DataSourceTypeChoices
|
||||||
from core.models import *
|
from core.models import *
|
||||||
@@ -15,7 +15,6 @@ __all__ = (
|
|||||||
|
|
||||||
class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=add_blank_choice(DataSourceTypeChoices),
|
choices=add_blank_choice(DataSourceTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial=''
|
initial=''
|
||||||
@@ -26,17 +25,16 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
label=_('Enforce unique space')
|
label=_('Enforce unique space')
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField(
|
||||||
|
label=_('Comments')
|
||||||
|
)
|
||||||
parameters = forms.JSONField(
|
parameters = forms.JSONField(
|
||||||
label=_('Parameters'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
ignore_rules = forms.CharField(
|
ignore_rules = forms.CharField(
|
||||||
label=_('Ignore rules'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Textarea()
|
widget=forms.Textarea()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.choices import *
|
from core.choices import *
|
||||||
from core.models import *
|
from core.models import *
|
||||||
@@ -23,20 +23,17 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = DataSource
|
model = DataSource
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
(None, ('q', 'filter_id')),
|
||||||
(_('Data Source'), ('type', 'status')),
|
('Data Source', ('type', 'status')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=DataSourceTypeChoices,
|
choices=DataSourceTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=DataSourceStatusChoices,
|
choices=DataSourceStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
enabled = forms.NullBooleanField(
|
enabled = forms.NullBooleanField(
|
||||||
label=_('Enabled'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
@@ -48,7 +45,7 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = DataFile
|
model = DataFile
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
(None, ('q', 'filter_id')),
|
||||||
(_('File'), ('source_id',)),
|
('File', ('source_id',)),
|
||||||
)
|
)
|
||||||
source_id = DynamicModelMultipleChoiceField(
|
source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@@ -60,8 +57,8 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class JobFilterForm(SavedFiltersMixin, FilterForm):
|
class JobFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
(None, ('q', 'filter_id')),
|
||||||
(_('Attributes'), ('object_type', 'status')),
|
('Attributes', ('object_type', 'status')),
|
||||||
(_('Creation'), (
|
('Creation', (
|
||||||
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
|
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
|
||||||
'started__after', 'completed__before', 'completed__after', 'user',
|
'started__after', 'completed__before', 'completed__after', 'user',
|
||||||
)),
|
)),
|
||||||
@@ -72,52 +69,43 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=JobStatusChoices,
|
choices=JobStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
created__after = forms.DateTimeField(
|
created__after = forms.DateTimeField(
|
||||||
label=_('Created after'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
created__before = forms.DateTimeField(
|
created__before = forms.DateTimeField(
|
||||||
label=_('Created before'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
scheduled__after = forms.DateTimeField(
|
scheduled__after = forms.DateTimeField(
|
||||||
label=_('Scheduled after'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
scheduled__before = forms.DateTimeField(
|
scheduled__before = forms.DateTimeField(
|
||||||
label=_('Scheduled before'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
started__after = forms.DateTimeField(
|
started__after = forms.DateTimeField(
|
||||||
label=_('Started after'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
started__before = forms.DateTimeField(
|
started__before = forms.DateTimeField(
|
||||||
label=_('Started before'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
completed__after = forms.DateTimeField(
|
completed__after = forms.DateTimeField(
|
||||||
label=_('Completed after'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
completed__before = forms.DateTimeField(
|
completed__before = forms.DateTimeField(
|
||||||
label=_('Completed before'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=DateTimePicker()
|
widget=DateTimePicker()
|
||||||
)
|
)
|
||||||
user = DynamicModelMultipleChoiceField(
|
user = DynamicModelMultipleChoiceField(
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=User.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('User'),
|
label=_('User'),
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.models import DataFile, DataSource
|
from core.models import DataFile, DataSource
|
||||||
from utilities.forms.fields import DynamicModelChoiceField
|
from utilities.forms.fields import DynamicModelChoiceField
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from core.forms.mixins import SyncedDataMixin
|
from core.forms.mixins import SyncedDataMixin
|
||||||
from core.models import *
|
from core.models import *
|
||||||
@@ -39,11 +38,11 @@ class DataSourceForm(NetBoxModelForm):
|
|||||||
@property
|
@property
|
||||||
def fieldsets(self):
|
def fieldsets(self):
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(_('Source'), ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
|
('Source', ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
|
||||||
]
|
]
|
||||||
if self.backend_fields:
|
if self.backend_fields:
|
||||||
fieldsets.append(
|
fieldsets.append(
|
||||||
(_('Backend Parameters'), self.backend_fields)
|
('Backend Parameters', self.backend_fields)
|
||||||
)
|
)
|
||||||
|
|
||||||
return fieldsets
|
return fieldsets
|
||||||
@@ -80,8 +79,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('File Upload'), ('upload_file',)),
|
('File Upload', ('upload_file',)),
|
||||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import sys
|
|||||||
from django import get_version
|
from django import get_version
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Additional objects to include
|
# Additional objects to include
|
||||||
namespace['ContentType'] = ContentType
|
namespace['ContentType'] = ContentType
|
||||||
namespace['User'] = get_user_model()
|
namespace['User'] = User
|
||||||
|
|
||||||
# Load convenience commands
|
# Load convenience commands
|
||||||
namespace.update({
|
namespace.update({
|
||||||
|
|||||||
@@ -39,12 +39,10 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
A remote source, such as a git repository, from which DataFiles are synchronized.
|
A remote source, such as a git repository, from which DataFiles are synchronized.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=DataSourceTypeChoices,
|
choices=DataSourceTypeChoices,
|
||||||
default=DataSourceTypeChoices.LOCAL
|
default=DataSourceTypeChoices.LOCAL
|
||||||
@@ -54,28 +52,23 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
verbose_name=_('URL')
|
verbose_name=_('URL')
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=DataSourceStatusChoices,
|
choices=DataSourceStatusChoices,
|
||||||
default=DataSourceStatusChoices.NEW,
|
default=DataSourceStatusChoices.NEW,
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
verbose_name=_('enabled'),
|
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
ignore_rules = models.TextField(
|
ignore_rules = models.TextField(
|
||||||
verbose_name=_('ignore rules'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("Patterns (one per line) matching files to ignore when syncing")
|
help_text=_("Patterns (one per line) matching files to ignore when syncing")
|
||||||
)
|
)
|
||||||
parameters = models.JSONField(
|
parameters = models.JSONField(
|
||||||
verbose_name=_('parameters'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
last_synced = models.DateTimeField(
|
last_synced = models.DateTimeField(
|
||||||
verbose_name=_('last synced'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
editable=False
|
editable=False
|
||||||
@@ -104,10 +97,6 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
def url_scheme(self):
|
def url_scheme(self):
|
||||||
return urlparse(self.source_url).scheme.lower()
|
return urlparse(self.source_url).scheme.lower()
|
||||||
|
|
||||||
@property
|
|
||||||
def backend_class(self):
|
|
||||||
return registry['data_backends'].get(self.type)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_local(self):
|
def is_local(self):
|
||||||
return self.type == DataSourceTypeChoices.LOCAL
|
return self.type == DataSourceTypeChoices.LOCAL
|
||||||
@@ -143,15 +132,17 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_backend(self):
|
def get_backend(self):
|
||||||
|
backend_cls = registry['data_backends'].get(self.type)
|
||||||
backend_params = self.parameters or {}
|
backend_params = self.parameters or {}
|
||||||
return self.backend_class(self.source_url, **backend_params)
|
|
||||||
|
return backend_cls(self.source_url, **backend_params)
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""
|
"""
|
||||||
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
|
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
|
||||||
"""
|
"""
|
||||||
if self.status == DataSourceStatusChoices.SYNCING:
|
if self.status == DataSourceStatusChoices.SYNCING:
|
||||||
raise SyncError("Cannot initiate sync; syncing already in progress.")
|
raise SyncError(f"Cannot initiate sync; syncing already in progress.")
|
||||||
|
|
||||||
# Emit the pre_sync signal
|
# Emit the pre_sync signal
|
||||||
pre_sync.send(sender=self.__class__, instance=self)
|
pre_sync.send(sender=self.__class__, instance=self)
|
||||||
@@ -160,12 +151,7 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
DataSource.objects.filter(pk=self.pk).update(status=self.status)
|
DataSource.objects.filter(pk=self.pk).update(status=self.status)
|
||||||
|
|
||||||
# Replicate source data locally
|
# Replicate source data locally
|
||||||
try:
|
|
||||||
backend = self.get_backend()
|
backend = self.get_backend()
|
||||||
except ModuleNotFoundError as e:
|
|
||||||
raise SyncError(
|
|
||||||
f"There was an error initializing the backend. A dependency needs to be installed: {e}"
|
|
||||||
)
|
|
||||||
with backend.fetch() as local_path:
|
with backend.fetch() as local_path:
|
||||||
|
|
||||||
logger.debug(f'Syncing files from source root {local_path}')
|
logger.debug(f'Syncing files from source root {local_path}')
|
||||||
@@ -214,7 +200,6 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
|
|
||||||
# Emit the post_sync signal
|
# Emit the post_sync signal
|
||||||
post_sync.send(sender=self.__class__, instance=self)
|
post_sync.send(sender=self.__class__, instance=self)
|
||||||
sync.alters_data = True
|
|
||||||
|
|
||||||
def _walk(self, root):
|
def _walk(self, root):
|
||||||
"""
|
"""
|
||||||
@@ -253,11 +238,9 @@ class DataFile(models.Model):
|
|||||||
updated, or deleted only by calling DataSource.sync().
|
updated, or deleted only by calling DataSource.sync().
|
||||||
"""
|
"""
|
||||||
created = models.DateTimeField(
|
created = models.DateTimeField(
|
||||||
verbose_name=_('created'),
|
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
last_updated = models.DateTimeField(
|
last_updated = models.DateTimeField(
|
||||||
verbose_name=_('last updated'),
|
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
source = models.ForeignKey(
|
source = models.ForeignKey(
|
||||||
@@ -267,23 +250,20 @@ class DataFile(models.Model):
|
|||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
path = models.CharField(
|
path = models.CharField(
|
||||||
verbose_name=_('path'),
|
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
editable=False,
|
editable=False,
|
||||||
help_text=_("File path relative to the data source's root")
|
help_text=_("File path relative to the data source's root")
|
||||||
)
|
)
|
||||||
size = models.PositiveIntegerField(
|
size = models.PositiveIntegerField(
|
||||||
editable=False,
|
editable=False
|
||||||
verbose_name=_('size')
|
|
||||||
)
|
)
|
||||||
hash = models.CharField(
|
hash = models.CharField(
|
||||||
verbose_name=_('hash'),
|
|
||||||
max_length=64,
|
max_length=64,
|
||||||
editable=False,
|
editable=False,
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(regex='^[0-9a-f]{64}$', message=_("Length must be 64 hexadecimal characters."))
|
RegexValidator(regex='^[0-9a-f]{64}$', message=_("Length must be 64 hexadecimal characters."))
|
||||||
],
|
],
|
||||||
help_text=_('SHA256 hash of the file data')
|
help_text=_("SHA256 hash of the file data")
|
||||||
)
|
)
|
||||||
data = models.BinaryField()
|
data = models.BinaryField()
|
||||||
|
|
||||||
@@ -309,10 +289,8 @@ class DataFile(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def data_as_string(self):
|
def data_as_string(self):
|
||||||
if not self.data:
|
|
||||||
return None
|
|
||||||
try:
|
try:
|
||||||
return bytes(self.data, 'utf-8')
|
return self.data.tobytes().decode('utf-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -23,24 +23,20 @@ class ManagedFile(SyncedDataMixin, models.Model):
|
|||||||
to provide additional functionality.
|
to provide additional functionality.
|
||||||
"""
|
"""
|
||||||
created = models.DateTimeField(
|
created = models.DateTimeField(
|
||||||
verbose_name=_('created'),
|
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
last_updated = models.DateTimeField(
|
last_updated = models.DateTimeField(
|
||||||
verbose_name=_('last updated'),
|
|
||||||
editable=False,
|
editable=False,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
file_root = models.CharField(
|
file_root = models.CharField(
|
||||||
verbose_name=_('file root'),
|
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
choices=ManagedFileRootPathChoices
|
choices=ManagedFileRootPathChoices
|
||||||
)
|
)
|
||||||
file_path = models.FilePathField(
|
file_path = models.FilePathField(
|
||||||
verbose_name=_('file path'),
|
|
||||||
editable=False,
|
editable=False,
|
||||||
help_text=_('File path relative to the designated root path')
|
help_text=_("File path relative to the designated root path")
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import django_rq
|
import django_rq
|
||||||
from django.conf import settings
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
@@ -43,57 +43,48 @@ class Job(models.Model):
|
|||||||
for_concrete_model=False
|
for_concrete_model=False
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=200
|
max_length=200
|
||||||
)
|
)
|
||||||
created = models.DateTimeField(
|
created = models.DateTimeField(
|
||||||
verbose_name=_('created'),
|
|
||||||
auto_now_add=True
|
auto_now_add=True
|
||||||
)
|
)
|
||||||
scheduled = models.DateTimeField(
|
scheduled = models.DateTimeField(
|
||||||
verbose_name=_('scheduled'),
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
interval = models.PositiveIntegerField(
|
interval = models.PositiveIntegerField(
|
||||||
verbose_name=_('interval'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=(
|
validators=(
|
||||||
MinValueValidator(1),
|
MinValueValidator(1),
|
||||||
),
|
),
|
||||||
help_text=_('Recurrence interval (in minutes)')
|
help_text=_("Recurrence interval (in minutes)")
|
||||||
)
|
)
|
||||||
started = models.DateTimeField(
|
started = models.DateTimeField(
|
||||||
verbose_name=_('started'),
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
completed = models.DateTimeField(
|
completed = models.DateTimeField(
|
||||||
verbose_name=_('completed'),
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=User,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=JobStatusChoices,
|
choices=JobStatusChoices,
|
||||||
default=JobStatusChoices.STATUS_PENDING
|
default=JobStatusChoices.STATUS_PENDING
|
||||||
)
|
)
|
||||||
data = models.JSONField(
|
data = models.JSONField(
|
||||||
verbose_name=_('data'),
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
job_id = models.UUIDField(
|
job_id = models.UUIDField(
|
||||||
verbose_name=_('job ID'),
|
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from core.models import *
|
from core.models import *
|
||||||
@@ -12,18 +11,11 @@ __all__ = (
|
|||||||
|
|
||||||
class DataSourceTable(NetBoxTable):
|
class DataSourceTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
type = columns.ChoiceFieldColumn(
|
type = columns.ChoiceFieldColumn()
|
||||||
verbose_name=_('Type'),
|
status = columns.ChoiceFieldColumn()
|
||||||
)
|
enabled = columns.BooleanColumn()
|
||||||
status = columns.ChoiceFieldColumn(
|
|
||||||
verbose_name=_('Status'),
|
|
||||||
)
|
|
||||||
enabled = columns.BooleanColumn(
|
|
||||||
verbose_name=_('Enabled'),
|
|
||||||
)
|
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='core:datasource_list'
|
url_name='core:datasource_list'
|
||||||
)
|
)
|
||||||
@@ -42,16 +34,12 @@ class DataSourceTable(NetBoxTable):
|
|||||||
|
|
||||||
class DataFileTable(NetBoxTable):
|
class DataFileTable(NetBoxTable):
|
||||||
source = tables.Column(
|
source = tables.Column(
|
||||||
verbose_name=_('Source'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
path = tables.Column(
|
path = tables.Column(
|
||||||
verbose_name=_('Path'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
last_updated = columns.DateTimeColumn(
|
last_updated = columns.DateTimeColumn()
|
||||||
verbose_name=_('Last updated'),
|
|
||||||
)
|
|
||||||
actions = columns.ActionsColumn(
|
actions = columns.ActionsColumn(
|
||||||
actions=('delete',)
|
actions=('delete',)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from ..models import Job
|
from ..models import Job
|
||||||
@@ -7,38 +7,23 @@ from ..models import Job
|
|||||||
|
|
||||||
class JobTable(NetBoxTable):
|
class JobTable(NetBoxTable):
|
||||||
id = tables.Column(
|
id = tables.Column(
|
||||||
verbose_name=_('ID'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
object_type = columns.ContentTypeColumn(
|
object_type = columns.ContentTypeColumn(
|
||||||
verbose_name=_('Type')
|
verbose_name=_('Type')
|
||||||
)
|
)
|
||||||
object = tables.Column(
|
object = tables.Column(
|
||||||
verbose_name=_('Object'),
|
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
status = columns.ChoiceFieldColumn(
|
status = columns.ChoiceFieldColumn()
|
||||||
verbose_name=_('Status'),
|
created = columns.DateTimeColumn()
|
||||||
)
|
scheduled = columns.DateTimeColumn()
|
||||||
created = columns.DateTimeColumn(
|
interval = columns.DurationColumn()
|
||||||
verbose_name=_('Created'),
|
started = columns.DateTimeColumn()
|
||||||
)
|
completed = columns.DateTimeColumn()
|
||||||
scheduled = columns.DateTimeColumn(
|
|
||||||
verbose_name=_('Scheduled'),
|
|
||||||
)
|
|
||||||
interval = columns.DurationColumn(
|
|
||||||
verbose_name=_('Interval'),
|
|
||||||
)
|
|
||||||
started = columns.DateTimeColumn(
|
|
||||||
verbose_name=_('Started'),
|
|
||||||
)
|
|
||||||
completed = columns.DateTimeColumn(
|
|
||||||
verbose_name=_('Completed'),
|
|
||||||
)
|
|
||||||
actions = columns.ActionsColumn(
|
actions = columns.ActionsColumn(
|
||||||
actions=('delete',)
|
actions=('delete',)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -214,9 +214,9 @@ class RackSerializer(NetBoxModelSerializer):
|
|||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
|
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
|
||||||
'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'weight', 'max_weight', 'weight_unit',
|
'asset_tag', 'type', 'width', 'u_height', 'weight', 'max_weight', 'weight_unit', 'desc_units',
|
||||||
'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments',
|
'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags',
|
||||||
'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
|
'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -327,28 +327,12 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
|||||||
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
|
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
console_port_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
console_server_port_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
power_port_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
power_outlet_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
interface_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
front_port_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
rear_port_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
device_bay_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
module_bay_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
inventory_item_template_count = serializers.IntegerField(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height',
|
'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
|
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
|
||||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||||
'console_port_template_count', 'console_server_port_template_count', 'power_port_template_count',
|
|
||||||
'power_outlet_template_count', 'interface_template_count', 'front_port_template_count',
|
|
||||||
'rear_port_template_count', 'device_bay_template_count', 'module_bay_template_count',
|
|
||||||
'inventory_item_template_count',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -514,18 +498,12 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
|||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
rf_role = ChoiceField(
|
|
||||||
choices=WirelessRoleChoices,
|
|
||||||
required=False,
|
|
||||||
allow_blank=True,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only',
|
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only',
|
||||||
'description', 'bridge', 'poe_mode', 'poe_type', 'rf_role', 'created', 'last_updated',
|
'description', 'bridge', 'poe_mode', 'poe_type', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -657,16 +635,15 @@ class PlatformSerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
|
'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args',
|
||||||
'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
|
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class DeviceSerializer(NetBoxModelSerializer):
|
class DeviceSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
role = NestedDeviceRoleSerializer()
|
device_role = NestedDeviceRoleSerializer()
|
||||||
device_role = NestedDeviceRoleSerializer(read_only=True, help_text='Deprecated in v3.6 in favor of `role`.')
|
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
|
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
|
||||||
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
@@ -686,35 +663,19 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
oob_ip = NestedIPAddressSerializer(required=False, allow_null=True)
|
|
||||||
parent_device = serializers.SerializerMethodField()
|
parent_device = serializers.SerializerMethodField()
|
||||||
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
||||||
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
|
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
|
||||||
vc_position = serializers.IntegerField(allow_null=True, max_value=255, min_value=0, default=None)
|
vc_position = serializers.IntegerField(allow_null=True, max_value=255, min_value=0, default=None)
|
||||||
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
|
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
console_port_count = serializers.IntegerField(read_only=True)
|
|
||||||
console_server_port_count = serializers.IntegerField(read_only=True)
|
|
||||||
power_port_count = serializers.IntegerField(read_only=True)
|
|
||||||
power_outlet_count = serializers.IntegerField(read_only=True)
|
|
||||||
interface_count = serializers.IntegerField(read_only=True)
|
|
||||||
front_port_count = serializers.IntegerField(read_only=True)
|
|
||||||
rear_port_count = serializers.IntegerField(read_only=True)
|
|
||||||
device_bay_count = serializers.IntegerField(read_only=True)
|
|
||||||
module_bay_count = serializers.IntegerField(read_only=True)
|
|
||||||
inventory_item_count = serializers.IntegerField(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'device_type', 'role', 'device_role', 'tenant', 'platform', 'serial',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device',
|
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
|
||||||
'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
|
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||||
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags',
|
'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
|
||||||
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
|
|
||||||
'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@extend_schema_field(NestedDeviceSerializer)
|
@extend_schema_field(NestedDeviceSerializer)
|
||||||
@@ -728,22 +689,17 @@ class DeviceSerializer(NetBoxModelSerializer):
|
|||||||
data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
|
data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_device_role(self, obj):
|
|
||||||
return obj.role
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceWithConfigContextSerializer(DeviceSerializer):
|
class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||||
config_context = serializers.SerializerMethodField(read_only=True)
|
config_context = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta(DeviceSerializer.Meta):
|
class Meta(DeviceSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'device_type', 'role', 'device_role', 'tenant', 'platform', 'serial',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow',
|
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
|
||||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position',
|
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||||
'vc_priority', 'description', 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context',
|
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'config_template',
|
||||||
'config_template', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
'created', 'last_updated',
|
||||||
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
|
|
||||||
'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||||
@@ -758,6 +714,7 @@ class VirtualDeviceContextSerializer(NetBoxModelSerializer):
|
|||||||
primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True)
|
primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True)
|
||||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
|
status = ChoiceField(choices=VirtualDeviceContextStatusChoices)
|
||||||
|
|
||||||
# Related object counts
|
# Related object counts
|
||||||
interface_count = serializers.IntegerField(read_only=True)
|
interface_count = serializers.IntegerField(read_only=True)
|
||||||
@@ -1039,8 +996,7 @@ class ModuleBaySerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags',
|
'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags', 'custom_fields',
|
||||||
'custom_fields',
|
|
||||||
'created', 'last_updated',
|
'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1183,15 +1139,13 @@ class CablePathSerializer(serializers.ModelSerializer):
|
|||||||
class VirtualChassisSerializer(NetBoxModelSerializer):
|
class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||||
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
member_count = serializers.IntegerField(read_only=True)
|
member_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
|
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'member_count',
|
'member_count', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -1241,10 +1195,6 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
|||||||
choices=PowerFeedPhaseChoices,
|
choices=PowerFeedPhaseChoices,
|
||||||
default=lambda: PowerFeedPhaseChoices.PHASE_SINGLE,
|
default=lambda: PowerFeedPhaseChoices.PHASE_SINGLE,
|
||||||
)
|
)
|
||||||
tenant = NestedTenantSerializer(
|
|
||||||
required=False,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
@@ -1252,5 +1202,5 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
|||||||
'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
|
'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
|
||||||
'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||||
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'description',
|
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'description',
|
||||||
'tenant', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ class InventoryItemTemplateViewSet(NetBoxModelViewSet):
|
|||||||
|
|
||||||
class DeviceRoleViewSet(NetBoxModelViewSet):
|
class DeviceRoleViewSet(NetBoxModelViewSet):
|
||||||
queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate(
|
queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate(
|
||||||
device_count=count_related(Device, 'role'),
|
device_count=count_related(Device, 'device_role'),
|
||||||
virtualmachine_count=count_related(VirtualMachine, 'role')
|
virtualmachine_count=count_related(VirtualMachine, 'role')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.DeviceRoleSerializer
|
serializer_class = serializers.DeviceRoleSerializer
|
||||||
@@ -393,7 +393,7 @@ class DeviceViewSet(
|
|||||||
NetBoxModelViewSet
|
NetBoxModelViewSet
|
||||||
):
|
):
|
||||||
queryset = Device.objects.prefetch_related(
|
queryset = Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer', 'role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
||||||
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
|
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
|
||||||
)
|
)
|
||||||
filterset_class = filtersets.DeviceFilterSet
|
filterset_class = filtersets.DeviceFilterSet
|
||||||
@@ -579,7 +579,9 @@ class CableTerminationViewSet(NetBoxModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualChassisViewSet(NetBoxModelViewSet):
|
class VirtualChassisViewSet(NetBoxModelViewSet):
|
||||||
queryset = VirtualChassis.objects.prefetch_related('tags')
|
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
||||||
|
member_count=count_related(Device, 'virtual_chassis')
|
||||||
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
filterset_class = filtersets.VirtualChassisFilterSet
|
filterset_class = filtersets.VirtualChassisFilterSet
|
||||||
brief_prefetch_fields = ['master']
|
brief_prefetch_fields = ['master']
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ class DCIMConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals, search
|
from . import signals, search
|
||||||
from .models import CableTermination, Device, DeviceType, VirtualChassis
|
from .models import CableTermination
|
||||||
from utilities.counters import connect_counters
|
|
||||||
|
|
||||||
# Register denormalized fields
|
# Register denormalized fields
|
||||||
denormalized.register(CableTermination, '_device', {
|
denormalized.register(CableTermination, '_device', {
|
||||||
@@ -25,6 +24,3 @@ class DCIMConfig(AppConfig):
|
|||||||
denormalized.register(CableTermination, '_location', {
|
denormalized.register(CableTermination, '_location', {
|
||||||
'_site': 'site',
|
'_site': 'site',
|
||||||
})
|
})
|
||||||
|
|
||||||
# Register counters
|
|
||||||
connect_counters(Device, DeviceType, VirtualChassis)
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from utilities.choices import ChoiceSet
|
from utilities.choices import ChoiceSet
|
||||||
|
|
||||||
|
|
||||||
@@ -17,11 +15,11 @@ class SiteStatusChoices(ChoiceSet):
|
|||||||
STATUS_RETIRED = 'retired'
|
STATUS_RETIRED = 'retired'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_STAGING, _('Staging'), 'blue'),
|
(STATUS_STAGING, 'Staging', 'blue'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
|
||||||
(STATUS_RETIRED, _('Retired'), 'red'),
|
(STATUS_RETIRED, 'Retired', 'red'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -62,13 +60,13 @@ class RackTypeChoices(ChoiceSet):
|
|||||||
TYPE_WALLCABINET_VERTICAL = 'wall-cabinet-vertical'
|
TYPE_WALLCABINET_VERTICAL = 'wall-cabinet-vertical'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(TYPE_2POST, _('2-post frame')),
|
(TYPE_2POST, '2-post frame'),
|
||||||
(TYPE_4POST, _('4-post frame')),
|
(TYPE_4POST, '4-post frame'),
|
||||||
(TYPE_CABINET, _('4-post cabinet')),
|
(TYPE_CABINET, '4-post cabinet'),
|
||||||
(TYPE_WALLFRAME, _('Wall-mounted frame')),
|
(TYPE_WALLFRAME, 'Wall-mounted frame'),
|
||||||
(TYPE_WALLFRAME_VERTICAL, _('Wall-mounted frame (vertical)')),
|
(TYPE_WALLFRAME_VERTICAL, 'Wall-mounted frame (vertical)'),
|
||||||
(TYPE_WALLCABINET, _('Wall-mounted cabinet')),
|
(TYPE_WALLCABINET, 'Wall-mounted cabinet'),
|
||||||
(TYPE_WALLCABINET_VERTICAL, _('Wall-mounted cabinet (vertical)')),
|
(TYPE_WALLCABINET_VERTICAL, 'Wall-mounted cabinet (vertical)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -80,10 +78,10 @@ class RackWidthChoices(ChoiceSet):
|
|||||||
WIDTH_23IN = 23
|
WIDTH_23IN = 23
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(WIDTH_10IN, _('10 inches')),
|
(WIDTH_10IN, '10 inches'),
|
||||||
(WIDTH_19IN, _('19 inches')),
|
(WIDTH_19IN, '19 inches'),
|
||||||
(WIDTH_21IN, _('21 inches')),
|
(WIDTH_21IN, '21 inches'),
|
||||||
(WIDTH_23IN, _('23 inches')),
|
(WIDTH_23IN, '23 inches'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -97,11 +95,11 @@ class RackStatusChoices(ChoiceSet):
|
|||||||
STATUS_DEPRECATED = 'deprecated'
|
STATUS_DEPRECATED = 'deprecated'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_RESERVED, _('Reserved'), 'yellow'),
|
(STATUS_RESERVED, 'Reserved', 'yellow'),
|
||||||
(STATUS_AVAILABLE, _('Available'), 'green'),
|
(STATUS_AVAILABLE, 'Available', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'blue'),
|
(STATUS_ACTIVE, 'Active', 'blue'),
|
||||||
(STATUS_DEPRECATED, _('Deprecated'), 'red'),
|
(STATUS_DEPRECATED, 'Deprecated', 'red'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -111,8 +109,8 @@ class RackDimensionUnitChoices(ChoiceSet):
|
|||||||
UNIT_INCH = 'in'
|
UNIT_INCH = 'in'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(UNIT_MILLIMETER, _('Millimeters')),
|
(UNIT_MILLIMETER, 'Millimeters'),
|
||||||
(UNIT_INCH, _('Inches')),
|
(UNIT_INCH, 'Inches'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -137,8 +135,8 @@ class SubdeviceRoleChoices(ChoiceSet):
|
|||||||
ROLE_CHILD = 'child'
|
ROLE_CHILD = 'child'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(ROLE_PARENT, _('Parent')),
|
(ROLE_PARENT, 'Parent'),
|
||||||
(ROLE_CHILD, _('Child')),
|
(ROLE_CHILD, 'Child'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -152,8 +150,8 @@ class DeviceFaceChoices(ChoiceSet):
|
|||||||
FACE_REAR = 'rear'
|
FACE_REAR = 'rear'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(FACE_FRONT, _('Front')),
|
(FACE_FRONT, 'Front'),
|
||||||
(FACE_REAR, _('Rear')),
|
(FACE_REAR, 'Rear'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -169,13 +167,13 @@ class DeviceStatusChoices(ChoiceSet):
|
|||||||
STATUS_DECOMMISSIONING = 'decommissioning'
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_OFFLINE, _('Offline'), 'gray'),
|
(STATUS_OFFLINE, 'Offline', 'gray'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_STAGED, _('Staged'), 'blue'),
|
(STATUS_STAGED, 'Staged', 'blue'),
|
||||||
(STATUS_FAILED, _('Failed'), 'red'),
|
(STATUS_FAILED, 'Failed', 'red'),
|
||||||
(STATUS_INVENTORY, _('Inventory'), 'purple'),
|
(STATUS_INVENTORY, 'Inventory', 'purple'),
|
||||||
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -190,13 +188,13 @@ class DeviceAirflowChoices(ChoiceSet):
|
|||||||
AIRFLOW_MIXED = 'mixed'
|
AIRFLOW_MIXED = 'mixed'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(AIRFLOW_FRONT_TO_REAR, _('Front to rear')),
|
(AIRFLOW_FRONT_TO_REAR, 'Front to rear'),
|
||||||
(AIRFLOW_REAR_TO_FRONT, _('Rear to front')),
|
(AIRFLOW_REAR_TO_FRONT, 'Rear to front'),
|
||||||
(AIRFLOW_LEFT_TO_RIGHT, _('Left to right')),
|
(AIRFLOW_LEFT_TO_RIGHT, 'Left to right'),
|
||||||
(AIRFLOW_RIGHT_TO_LEFT, _('Right to left')),
|
(AIRFLOW_RIGHT_TO_LEFT, 'Right to left'),
|
||||||
(AIRFLOW_SIDE_TO_REAR, _('Side to rear')),
|
(AIRFLOW_SIDE_TO_REAR, 'Side to rear'),
|
||||||
(AIRFLOW_PASSIVE, _('Passive')),
|
(AIRFLOW_PASSIVE, 'Passive'),
|
||||||
(AIRFLOW_MIXED, _('Mixed')),
|
(AIRFLOW_MIXED, 'Mixed'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -215,12 +213,12 @@ class ModuleStatusChoices(ChoiceSet):
|
|||||||
STATUS_DECOMMISSIONING = 'decommissioning'
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_OFFLINE, _('Offline'), 'gray'),
|
(STATUS_OFFLINE, 'Offline', 'gray'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_STAGED, _('Staged'), 'blue'),
|
(STATUS_STAGED, 'Staged', 'blue'),
|
||||||
(STATUS_FAILED, _('Failed'), 'red'),
|
(STATUS_FAILED, 'Failed', 'red'),
|
||||||
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -440,7 +438,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
||||||
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
||||||
)),
|
)),
|
||||||
(_('NEMA (Non-locking)'), (
|
('NEMA (Non-locking)', (
|
||||||
(TYPE_NEMA_115P, 'NEMA 1-15P'),
|
(TYPE_NEMA_115P, 'NEMA 1-15P'),
|
||||||
(TYPE_NEMA_515P, 'NEMA 5-15P'),
|
(TYPE_NEMA_515P, 'NEMA 5-15P'),
|
||||||
(TYPE_NEMA_520P, 'NEMA 5-20P'),
|
(TYPE_NEMA_520P, 'NEMA 5-20P'),
|
||||||
@@ -462,7 +460,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEMA_1550P, 'NEMA 15-50P'),
|
(TYPE_NEMA_1550P, 'NEMA 15-50P'),
|
||||||
(TYPE_NEMA_1560P, 'NEMA 15-60P'),
|
(TYPE_NEMA_1560P, 'NEMA 15-60P'),
|
||||||
)),
|
)),
|
||||||
(_('NEMA (Locking)'), (
|
('NEMA (Locking)', (
|
||||||
(TYPE_NEMA_L115P, 'NEMA L1-15P'),
|
(TYPE_NEMA_L115P, 'NEMA L1-15P'),
|
||||||
(TYPE_NEMA_L515P, 'NEMA L5-15P'),
|
(TYPE_NEMA_L515P, 'NEMA L5-15P'),
|
||||||
(TYPE_NEMA_L520P, 'NEMA L5-20P'),
|
(TYPE_NEMA_L520P, 'NEMA L5-20P'),
|
||||||
@@ -485,7 +483,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEMA_L2130P, 'NEMA L21-30P'),
|
(TYPE_NEMA_L2130P, 'NEMA L21-30P'),
|
||||||
(TYPE_NEMA_L2230P, 'NEMA L22-30P'),
|
(TYPE_NEMA_L2230P, 'NEMA L22-30P'),
|
||||||
)),
|
)),
|
||||||
(_('California Style'), (
|
('California Style', (
|
||||||
(TYPE_CS6361C, 'CS6361C'),
|
(TYPE_CS6361C, 'CS6361C'),
|
||||||
(TYPE_CS6365C, 'CS6365C'),
|
(TYPE_CS6365C, 'CS6365C'),
|
||||||
(TYPE_CS8165C, 'CS8165C'),
|
(TYPE_CS8165C, 'CS8165C'),
|
||||||
@@ -493,7 +491,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_CS8365C, 'CS8365C'),
|
(TYPE_CS8365C, 'CS8365C'),
|
||||||
(TYPE_CS8465C, 'CS8465C'),
|
(TYPE_CS8465C, 'CS8465C'),
|
||||||
)),
|
)),
|
||||||
(_('International/ITA'), (
|
('International/ITA', (
|
||||||
(TYPE_ITA_C, 'ITA Type C (CEE 7/16)'),
|
(TYPE_ITA_C, 'ITA Type C (CEE 7/16)'),
|
||||||
(TYPE_ITA_E, 'ITA Type E (CEE 7/6)'),
|
(TYPE_ITA_E, 'ITA Type E (CEE 7/6)'),
|
||||||
(TYPE_ITA_F, 'ITA Type F (CEE 7/4)'),
|
(TYPE_ITA_F, 'ITA Type F (CEE 7/4)'),
|
||||||
@@ -523,7 +521,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
('DC', (
|
('DC', (
|
||||||
(TYPE_DC, 'DC Terminal'),
|
(TYPE_DC, 'DC Terminal'),
|
||||||
)),
|
)),
|
||||||
(_('Proprietary'), (
|
('Proprietary', (
|
||||||
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
||||||
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
|
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
|
||||||
(TYPE_NEUTRIK_POWERCON_32A, 'Neutrik powerCON (32A)'),
|
(TYPE_NEUTRIK_POWERCON_32A, 'Neutrik powerCON (32A)'),
|
||||||
@@ -531,7 +529,7 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
|
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
|
||||||
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
|
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
|
||||||
)),
|
)),
|
||||||
(_('Other'), (
|
('Other', (
|
||||||
(TYPE_HARDWIRED, 'Hardwired'),
|
(TYPE_HARDWIRED, 'Hardwired'),
|
||||||
(TYPE_OTHER, 'Other'),
|
(TYPE_OTHER, 'Other'),
|
||||||
)),
|
)),
|
||||||
@@ -677,7 +675,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
||||||
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
||||||
)),
|
)),
|
||||||
(_('NEMA (Non-locking)'), (
|
('NEMA (Non-locking)', (
|
||||||
(TYPE_NEMA_115R, 'NEMA 1-15R'),
|
(TYPE_NEMA_115R, 'NEMA 1-15R'),
|
||||||
(TYPE_NEMA_515R, 'NEMA 5-15R'),
|
(TYPE_NEMA_515R, 'NEMA 5-15R'),
|
||||||
(TYPE_NEMA_520R, 'NEMA 5-20R'),
|
(TYPE_NEMA_520R, 'NEMA 5-20R'),
|
||||||
@@ -699,7 +697,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEMA_1550R, 'NEMA 15-50R'),
|
(TYPE_NEMA_1550R, 'NEMA 15-50R'),
|
||||||
(TYPE_NEMA_1560R, 'NEMA 15-60R'),
|
(TYPE_NEMA_1560R, 'NEMA 15-60R'),
|
||||||
)),
|
)),
|
||||||
(_('NEMA (Locking)'), (
|
('NEMA (Locking)', (
|
||||||
(TYPE_NEMA_L115R, 'NEMA L1-15R'),
|
(TYPE_NEMA_L115R, 'NEMA L1-15R'),
|
||||||
(TYPE_NEMA_L515R, 'NEMA L5-15R'),
|
(TYPE_NEMA_L515R, 'NEMA L5-15R'),
|
||||||
(TYPE_NEMA_L520R, 'NEMA L5-20R'),
|
(TYPE_NEMA_L520R, 'NEMA L5-20R'),
|
||||||
@@ -722,7 +720,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEMA_L2130R, 'NEMA L21-30R'),
|
(TYPE_NEMA_L2130R, 'NEMA L21-30R'),
|
||||||
(TYPE_NEMA_L2230R, 'NEMA L22-30R'),
|
(TYPE_NEMA_L2230R, 'NEMA L22-30R'),
|
||||||
)),
|
)),
|
||||||
(_('California Style'), (
|
('California Style', (
|
||||||
(TYPE_CS6360C, 'CS6360C'),
|
(TYPE_CS6360C, 'CS6360C'),
|
||||||
(TYPE_CS6364C, 'CS6364C'),
|
(TYPE_CS6364C, 'CS6364C'),
|
||||||
(TYPE_CS8164C, 'CS8164C'),
|
(TYPE_CS8164C, 'CS8164C'),
|
||||||
@@ -730,7 +728,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_CS8364C, 'CS8364C'),
|
(TYPE_CS8364C, 'CS8364C'),
|
||||||
(TYPE_CS8464C, 'CS8464C'),
|
(TYPE_CS8464C, 'CS8464C'),
|
||||||
)),
|
)),
|
||||||
(_('ITA/International'), (
|
('ITA/International', (
|
||||||
(TYPE_ITA_E, 'ITA Type E (CEE 7/5)'),
|
(TYPE_ITA_E, 'ITA Type E (CEE 7/5)'),
|
||||||
(TYPE_ITA_F, 'ITA Type F (CEE 7/3)'),
|
(TYPE_ITA_F, 'ITA Type F (CEE 7/3)'),
|
||||||
(TYPE_ITA_G, 'ITA Type G (BS 1363)'),
|
(TYPE_ITA_G, 'ITA Type G (BS 1363)'),
|
||||||
@@ -752,7 +750,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
('DC', (
|
('DC', (
|
||||||
(TYPE_DC, 'DC Terminal'),
|
(TYPE_DC, 'DC Terminal'),
|
||||||
)),
|
)),
|
||||||
(_('Proprietary'), (
|
('Proprietary', (
|
||||||
(TYPE_HDOT_CX, 'HDOT Cx'),
|
(TYPE_HDOT_CX, 'HDOT Cx'),
|
||||||
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
||||||
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
|
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
|
||||||
@@ -761,7 +759,7 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
|
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
|
||||||
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
|
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
|
||||||
)),
|
)),
|
||||||
(_('Other'), (
|
('Other', (
|
||||||
(TYPE_HARDWIRED, 'Hardwired'),
|
(TYPE_HARDWIRED, 'Hardwired'),
|
||||||
(TYPE_OTHER, 'Other'),
|
(TYPE_OTHER, 'Other'),
|
||||||
)),
|
)),
|
||||||
@@ -791,9 +789,9 @@ class InterfaceKindChoices(ChoiceSet):
|
|||||||
KIND_WIRELESS = 'wireless'
|
KIND_WIRELESS = 'wireless'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(KIND_PHYSICAL, _('Physical')),
|
(KIND_PHYSICAL, 'Physical'),
|
||||||
(KIND_VIRTUAL, _('Virtual')),
|
(KIND_VIRTUAL, 'Virtual'),
|
||||||
(KIND_WIRELESS, _('Wireless')),
|
(KIND_WIRELESS, 'Wireless'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -836,6 +834,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
||||||
TYPE_200GE_QSFP56 = '200gbase-x-qsfp56'
|
TYPE_200GE_QSFP56 = '200gbase-x-qsfp56'
|
||||||
TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd'
|
TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd'
|
||||||
|
TYPE_400GE_CFP2 = '400gbase-x-cfp2'
|
||||||
TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd'
|
TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd'
|
||||||
TYPE_400GE_OSFP = '400gbase-x-osfp'
|
TYPE_400GE_OSFP = '400gbase-x-osfp'
|
||||||
TYPE_400GE_CDFP = '400gbase-x-cdfp'
|
TYPE_400GE_CDFP = '400gbase-x-cdfp'
|
||||||
@@ -941,15 +940,15 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
_('Virtual interfaces'),
|
'Virtual interfaces',
|
||||||
(
|
(
|
||||||
(TYPE_VIRTUAL, _('Virtual')),
|
(TYPE_VIRTUAL, 'Virtual'),
|
||||||
(TYPE_BRIDGE, _('Bridge')),
|
(TYPE_BRIDGE, 'Bridge'),
|
||||||
(TYPE_LAG, _('Link Aggregation Group (LAG)')),
|
(TYPE_LAG, 'Link Aggregation Group (LAG)'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Ethernet (fixed)'),
|
'Ethernet (fixed)',
|
||||||
(
|
(
|
||||||
(TYPE_100ME_FX, '100BASE-FX (10/100ME FIBER)'),
|
(TYPE_100ME_FX, '100BASE-FX (10/100ME FIBER)'),
|
||||||
(TYPE_100ME_LFX, '100BASE-LFX (10/100ME FIBER)'),
|
(TYPE_100ME_LFX, '100BASE-LFX (10/100ME FIBER)'),
|
||||||
@@ -963,7 +962,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Ethernet (modular)'),
|
'Ethernet (modular)',
|
||||||
(
|
(
|
||||||
(TYPE_1GE_GBIC, 'GBIC (1GE)'),
|
(TYPE_1GE_GBIC, 'GBIC (1GE)'),
|
||||||
(TYPE_1GE_SFP, 'SFP (1GE)'),
|
(TYPE_1GE_SFP, 'SFP (1GE)'),
|
||||||
@@ -978,6 +977,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
(TYPE_100GE_CFP, 'CFP (100GE)'),
|
(TYPE_100GE_CFP, 'CFP (100GE)'),
|
||||||
(TYPE_100GE_CFP2, 'CFP2 (100GE)'),
|
(TYPE_100GE_CFP2, 'CFP2 (100GE)'),
|
||||||
(TYPE_200GE_CFP2, 'CFP2 (200GE)'),
|
(TYPE_200GE_CFP2, 'CFP2 (200GE)'),
|
||||||
|
(TYPE_400GE_CFP2, 'CFP2 (400GE)'),
|
||||||
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
||||||
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
||||||
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
||||||
@@ -996,7 +996,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Ethernet (backplane)'),
|
'Ethernet (backplane)',
|
||||||
(
|
(
|
||||||
(TYPE_1GE_KX, '1000BASE-KX (1GE)'),
|
(TYPE_1GE_KX, '1000BASE-KX (1GE)'),
|
||||||
(TYPE_10GE_KR, '10GBASE-KR (10GE)'),
|
(TYPE_10GE_KR, '10GBASE-KR (10GE)'),
|
||||||
@@ -1010,7 +1010,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Wireless'),
|
'Wireless',
|
||||||
(
|
(
|
||||||
(TYPE_80211A, 'IEEE 802.11a'),
|
(TYPE_80211A, 'IEEE 802.11a'),
|
||||||
(TYPE_80211G, 'IEEE 802.11b/g'),
|
(TYPE_80211G, 'IEEE 802.11b/g'),
|
||||||
@@ -1024,7 +1024,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Cellular'),
|
'Cellular',
|
||||||
(
|
(
|
||||||
(TYPE_GSM, 'GSM'),
|
(TYPE_GSM, 'GSM'),
|
||||||
(TYPE_CDMA, 'CDMA'),
|
(TYPE_CDMA, 'CDMA'),
|
||||||
@@ -1071,7 +1071,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Serial'),
|
'Serial',
|
||||||
(
|
(
|
||||||
(TYPE_T1, 'T1 (1.544 Mbps)'),
|
(TYPE_T1, 'T1 (1.544 Mbps)'),
|
||||||
(TYPE_E1, 'E1 (2.048 Mbps)'),
|
(TYPE_E1, 'E1 (2.048 Mbps)'),
|
||||||
@@ -1086,7 +1086,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Coaxial'),
|
'Coaxial',
|
||||||
(
|
(
|
||||||
(TYPE_DOCSIS, 'DOCSIS'),
|
(TYPE_DOCSIS, 'DOCSIS'),
|
||||||
)
|
)
|
||||||
@@ -1103,7 +1103,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Stacking'),
|
'Stacking',
|
||||||
(
|
(
|
||||||
(TYPE_STACKWISE, 'Cisco StackWise'),
|
(TYPE_STACKWISE, 'Cisco StackWise'),
|
||||||
(TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'),
|
(TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'),
|
||||||
@@ -1122,9 +1122,9 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Other'),
|
'Other',
|
||||||
(
|
(
|
||||||
(TYPE_OTHER, _('Other')),
|
(TYPE_OTHER, 'Other'),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1141,6 +1141,8 @@ class InterfaceSpeedChoices(ChoiceSet):
|
|||||||
(25000000, '25 Gbps'),
|
(25000000, '25 Gbps'),
|
||||||
(40000000, '40 Gbps'),
|
(40000000, '40 Gbps'),
|
||||||
(100000000, '100 Gbps'),
|
(100000000, '100 Gbps'),
|
||||||
|
(200000000, '200 Gbps'),
|
||||||
|
(400000000, '400 Gbps'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -1151,9 +1153,9 @@ class InterfaceDuplexChoices(ChoiceSet):
|
|||||||
DUPLEX_AUTO = 'auto'
|
DUPLEX_AUTO = 'auto'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(DUPLEX_HALF, _('Half')),
|
(DUPLEX_HALF, 'Half'),
|
||||||
(DUPLEX_FULL, _('Full')),
|
(DUPLEX_FULL, 'Full'),
|
||||||
(DUPLEX_AUTO, _('Auto')),
|
(DUPLEX_AUTO, 'Auto'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1164,9 +1166,9 @@ class InterfaceModeChoices(ChoiceSet):
|
|||||||
MODE_TAGGED_ALL = 'tagged-all'
|
MODE_TAGGED_ALL = 'tagged-all'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(MODE_ACCESS, _('Access')),
|
(MODE_ACCESS, 'Access'),
|
||||||
(MODE_TAGGED, _('Tagged')),
|
(MODE_TAGGED, 'Tagged'),
|
||||||
(MODE_TAGGED_ALL, _('Tagged (All)')),
|
(MODE_TAGGED_ALL, 'Tagged (All)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1195,7 +1197,7 @@ class InterfacePoETypeChoices(ChoiceSet):
|
|||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
_('IEEE Standard'),
|
'IEEE Standard',
|
||||||
(
|
(
|
||||||
(TYPE_1_8023AF, '802.3af (Type 1)'),
|
(TYPE_1_8023AF, '802.3af (Type 1)'),
|
||||||
(TYPE_2_8023AT, '802.3at (Type 2)'),
|
(TYPE_2_8023AT, '802.3at (Type 2)'),
|
||||||
@@ -1204,12 +1206,12 @@ class InterfacePoETypeChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Passive'),
|
'Passive',
|
||||||
(
|
(
|
||||||
(PASSIVE_24V_2PAIR, _('Passive 24V (2-pair)')),
|
(PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'),
|
||||||
(PASSIVE_24V_4PAIR, _('Passive 24V (4-pair)')),
|
(PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'),
|
||||||
(PASSIVE_48V_2PAIR, _('Passive 48V (2-pair)')),
|
(PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'),
|
||||||
(PASSIVE_48V_4PAIR, _('Passive 48V (4-pair)')),
|
(PASSIVE_48V_4PAIR, 'Passive 48V (4-pair)'),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1271,7 +1273,7 @@ class PortTypeChoices(ChoiceSet):
|
|||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
_('Copper'),
|
'Copper',
|
||||||
(
|
(
|
||||||
(TYPE_8P8C, '8P8C'),
|
(TYPE_8P8C, '8P8C'),
|
||||||
(TYPE_8P6C, '8P6C'),
|
(TYPE_8P6C, '8P6C'),
|
||||||
@@ -1294,7 +1296,7 @@ class PortTypeChoices(ChoiceSet):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Fiber Optic'),
|
'Fiber Optic',
|
||||||
(
|
(
|
||||||
(TYPE_FC, 'FC'),
|
(TYPE_FC, 'FC'),
|
||||||
(TYPE_LC, 'LC'),
|
(TYPE_LC, 'LC'),
|
||||||
@@ -1327,9 +1329,9 @@ class PortTypeChoices(ChoiceSet):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Other'),
|
'Other',
|
||||||
(
|
(
|
||||||
(TYPE_OTHER, _('Other')),
|
(TYPE_OTHER, 'Other'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1367,7 +1369,7 @@ class CableTypeChoices(ChoiceSet):
|
|||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
_('Copper'), (
|
'Copper', (
|
||||||
(TYPE_CAT3, 'CAT3'),
|
(TYPE_CAT3, 'CAT3'),
|
||||||
(TYPE_CAT5, 'CAT5'),
|
(TYPE_CAT5, 'CAT5'),
|
||||||
(TYPE_CAT5E, 'CAT5e'),
|
(TYPE_CAT5E, 'CAT5e'),
|
||||||
@@ -1383,7 +1385,7 @@ class CableTypeChoices(ChoiceSet):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_('Fiber'), (
|
'Fiber', (
|
||||||
(TYPE_MMF, 'Multimode Fiber'),
|
(TYPE_MMF, 'Multimode Fiber'),
|
||||||
(TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
(TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
||||||
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
||||||
@@ -1396,7 +1398,7 @@ class CableTypeChoices(ChoiceSet):
|
|||||||
(TYPE_AOC, 'Active Optical Cabling (AOC)'),
|
(TYPE_AOC, 'Active Optical Cabling (AOC)'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(TYPE_POWER, _('Power')),
|
(TYPE_POWER, 'Power'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1407,9 +1409,9 @@ class LinkStatusChoices(ChoiceSet):
|
|||||||
STATUS_DECOMMISSIONING = 'decommissioning'
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(STATUS_CONNECTED, _('Connected'), 'green'),
|
(STATUS_CONNECTED, 'Connected', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'blue'),
|
(STATUS_PLANNED, 'Planned', 'blue'),
|
||||||
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
|
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1426,12 +1428,12 @@ class CableLengthUnitChoices(ChoiceSet):
|
|||||||
UNIT_INCH = 'in'
|
UNIT_INCH = 'in'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(UNIT_KILOMETER, _('Kilometers')),
|
(UNIT_KILOMETER, 'Kilometers'),
|
||||||
(UNIT_METER, _('Meters')),
|
(UNIT_METER, 'Meters'),
|
||||||
(UNIT_CENTIMETER, _('Centimeters')),
|
(UNIT_CENTIMETER, 'Centimeters'),
|
||||||
(UNIT_MILE, _('Miles')),
|
(UNIT_MILE, 'Miles'),
|
||||||
(UNIT_FOOT, _('Feet')),
|
(UNIT_FOOT, 'Feet'),
|
||||||
(UNIT_INCH, _('Inches')),
|
(UNIT_INCH, 'Inches'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1446,10 +1448,10 @@ class WeightUnitChoices(ChoiceSet):
|
|||||||
UNIT_OUNCE = 'oz'
|
UNIT_OUNCE = 'oz'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(UNIT_KILOGRAM, _('Kilograms')),
|
(UNIT_KILOGRAM, 'Kilograms'),
|
||||||
(UNIT_GRAM, _('Grams')),
|
(UNIT_GRAM, 'Grams'),
|
||||||
(UNIT_POUND, _('Pounds')),
|
(UNIT_POUND, 'Pounds'),
|
||||||
(UNIT_OUNCE, _('Ounces')),
|
(UNIT_OUNCE, 'Ounces'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1482,10 +1484,10 @@ class PowerFeedStatusChoices(ChoiceSet):
|
|||||||
STATUS_FAILED = 'failed'
|
STATUS_FAILED = 'failed'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_OFFLINE, _('Offline'), 'gray'),
|
(STATUS_OFFLINE, 'Offline', 'gray'),
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'blue'),
|
(STATUS_PLANNED, 'Planned', 'blue'),
|
||||||
(STATUS_FAILED, _('Failed'), 'red'),
|
(STATUS_FAILED, 'Failed', 'red'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -1495,8 +1497,8 @@ class PowerFeedTypeChoices(ChoiceSet):
|
|||||||
TYPE_REDUNDANT = 'redundant'
|
TYPE_REDUNDANT = 'redundant'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(TYPE_PRIMARY, _('Primary'), 'green'),
|
(TYPE_PRIMARY, 'Primary', 'green'),
|
||||||
(TYPE_REDUNDANT, _('Redundant'), 'cyan'),
|
(TYPE_REDUNDANT, 'Redundant', 'cyan'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1517,8 +1519,8 @@ class PowerFeedPhaseChoices(ChoiceSet):
|
|||||||
PHASE_3PHASE = 'three-phase'
|
PHASE_3PHASE = 'three-phase'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(PHASE_SINGLE, _('Single phase')),
|
(PHASE_SINGLE, 'Single phase'),
|
||||||
(PHASE_3PHASE, _('Three-phase')),
|
(PHASE_3PHASE, 'Three-phase'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1533,7 +1535,7 @@ class VirtualDeviceContextStatusChoices(ChoiceSet):
|
|||||||
STATUS_OFFLINE = 'offline'
|
STATUS_OFFLINE = 'offline'
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
(STATUS_ACTIVE, _('Active'), 'green'),
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
(STATUS_PLANNED, _('Planned'), 'cyan'),
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
(STATUS_OFFLINE, _('Offline'), 'red'),
|
(STATUS_OFFLINE, 'Offline', 'red'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ RACK_ELEVATION_BORDER_WIDTH = 2
|
|||||||
RACK_ELEVATION_DEFAULT_LEGEND_WIDTH = 30
|
RACK_ELEVATION_DEFAULT_LEGEND_WIDTH = 30
|
||||||
RACK_ELEVATION_DEFAULT_MARGIN_WIDTH = 15
|
RACK_ELEVATION_DEFAULT_MARGIN_WIDTH = 15
|
||||||
|
|
||||||
RACK_STARTING_UNIT_DEFAULT = 1
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# RearPorts
|
# RearPorts
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
|
|||||||
from .lookups import PathContains
|
from .lookups import PathContains
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'ASNField',
|
||||||
'MACAddressField',
|
'MACAddressField',
|
||||||
'PathField',
|
'PathField',
|
||||||
'WWNField',
|
'WWNField',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from extras.filtersets import LocalConfigContextFilterSet
|
from extras.filtersets import LocalConfigContextFilterSet
|
||||||
@@ -323,8 +323,8 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
|
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||||
'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit'
|
'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit'
|
||||||
]
|
]
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
@@ -395,12 +395,12 @@ class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
label=_('Location (slug)'),
|
label=_('Location (slug)'),
|
||||||
)
|
)
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=User.objects.all(),
|
||||||
label=_('User (ID)'),
|
label=_('User (ID)'),
|
||||||
)
|
)
|
||||||
user = django_filters.ModelMultipleChoiceFilter(
|
user = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='user__username',
|
field_name='user__username',
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=User.objects.all(),
|
||||||
to_field_name='username',
|
to_field_name='username',
|
||||||
label=_('User (name)'),
|
label=_('User (name)'),
|
||||||
)
|
)
|
||||||
@@ -696,9 +696,6 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
|||||||
poe_type = django_filters.MultipleChoiceFilter(
|
poe_type = django_filters.MultipleChoiceFilter(
|
||||||
choices=InterfacePoETypeChoices
|
choices=InterfacePoETypeChoices
|
||||||
)
|
)
|
||||||
rf_role = django_filters.MultipleChoiceFilter(
|
|
||||||
choices=WirelessRoleChoices
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
@@ -814,7 +811,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
|
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
|
||||||
@@ -840,12 +837,12 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
label=_('Device type (ID)'),
|
label=_('Device type (ID)'),
|
||||||
)
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='role_id',
|
field_name='device_role_id',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
label=_('Role (ID)'),
|
label=_('Role (ID)'),
|
||||||
)
|
)
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
role = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='role__slug',
|
field_name='device_role__slug',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Role (slug)'),
|
label=_('Role (slug)'),
|
||||||
@@ -944,10 +941,6 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
method='_has_primary_ip',
|
method='_has_primary_ip',
|
||||||
label=_('Has a primary IP'),
|
label=_('Has a primary IP'),
|
||||||
)
|
)
|
||||||
has_oob_ip = django_filters.BooleanFilter(
|
|
||||||
method='_has_oob_ip',
|
|
||||||
label=_('Has an out-of-band IP'),
|
|
||||||
)
|
|
||||||
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='virtual_chassis',
|
field_name='virtual_chassis',
|
||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
@@ -1003,15 +996,10 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
label=_('Primary IPv6 (ID)'),
|
label=_('Primary IPv6 (ID)'),
|
||||||
)
|
)
|
||||||
oob_ip_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='oob_ip',
|
|
||||||
queryset=IPAddress.objects.all(),
|
|
||||||
label=_('OOB IP (ID)'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = ['id', 'asset_tag', 'face', 'position', 'latitude', 'longitude', 'airflow', 'vc_position', 'vc_priority']
|
fields = ['id', 'asset_tag', 'face', 'position', 'airflow', 'vc_position', 'vc_priority']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@@ -1032,12 +1020,6 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
|
|||||||
return queryset.filter(params)
|
return queryset.filter(params)
|
||||||
return queryset.exclude(params)
|
return queryset.exclude(params)
|
||||||
|
|
||||||
def _has_oob_ip(self, queryset, name, value):
|
|
||||||
params = Q(oob_ip__isnull=False)
|
|
||||||
if value:
|
|
||||||
return queryset.filter(params)
|
|
||||||
return queryset.exclude(params)
|
|
||||||
|
|
||||||
def _virtual_chassis_member(self, queryset, name, value):
|
def _virtual_chassis_member(self, queryset, name, value):
|
||||||
return queryset.exclude(virtual_chassis__isnull=value)
|
return queryset.exclude(virtual_chassis__isnull=value)
|
||||||
|
|
||||||
@@ -1251,13 +1233,13 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
|||||||
to_field_name='model',
|
to_field_name='model',
|
||||||
label=_('Device type (model)'),
|
label=_('Device type (model)'),
|
||||||
)
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device__role',
|
field_name='device__device_role',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
label=_('Device role (ID)'),
|
label=_('Device role (ID)'),
|
||||||
)
|
)
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
device_role = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device__role__slug',
|
field_name='device__device_role__slug',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Device role (slug)'),
|
label=_('Device role (slug)'),
|
||||||
@@ -1273,18 +1255,6 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label=_('Virtual Chassis'),
|
label=_('Virtual Chassis'),
|
||||||
)
|
)
|
||||||
# TODO: Remove in v4.0
|
|
||||||
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='device__role',
|
|
||||||
queryset=DeviceRole.objects.all(),
|
|
||||||
label=_('Device role (ID)'),
|
|
||||||
)
|
|
||||||
device_role = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='device__role__slug',
|
|
||||||
queryset=DeviceRole.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label=_('Device role (slug)'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@@ -1892,7 +1862,7 @@ class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet, TenancyFilterSet):
|
class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
|
||||||
region_id = TreeNodeMultipleChoiceFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
field_name='power_panel__site__region',
|
field_name='power_panel__site__region',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from extras.forms import CustomFieldsMixin
|
from extras.forms import CustomFieldsMixin
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from utilities.forms import BootstrapMixin, form_from_model
|
from utilities.forms import BootstrapMixin, form_from_model
|
||||||
@@ -32,12 +32,10 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCre
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tags = DynamicModelMultipleChoiceField(
|
tags = DynamicModelMultipleChoiceField(
|
||||||
label=_('Tags'),
|
|
||||||
queryset=Tag.objects.all(),
|
queryset=Tag.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -78,14 +76,14 @@ class PowerOutletBulkCreateForm(
|
|||||||
|
|
||||||
class InterfaceBulkCreateForm(
|
class InterfaceBulkCreateForm(
|
||||||
form_from_model(Interface, [
|
form_from_model(Interface, [
|
||||||
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type', 'rf_role'
|
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type',
|
||||||
]),
|
]),
|
||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = Interface
|
model = Interface
|
||||||
field_order = (
|
field_order = (
|
||||||
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
||||||
'poe_type', 'mark_connected', 'rf_role', 'description', 'tags',
|
'poe_type', 'mark_connected', 'description', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.contrib.postgres.forms.array import SimpleArrayField
|
from django.contrib.postgres.forms.array import SimpleArrayField
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
@@ -56,7 +56,6 @@ __all__ = (
|
|||||||
|
|
||||||
class RegionImportForm(NetBoxModelImportForm):
|
class RegionImportForm(NetBoxModelImportForm):
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -70,7 +69,6 @@ class RegionImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class SiteGroupImportForm(NetBoxModelImportForm):
|
class SiteGroupImportForm(NetBoxModelImportForm):
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -84,26 +82,22 @@ class SiteGroupImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class SiteImportForm(NetBoxModelImportForm):
|
class SiteImportForm(NetBoxModelImportForm):
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=SiteStatusChoices,
|
choices=SiteStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
region = CSVModelChoiceField(
|
region = CSVModelChoiceField(
|
||||||
label=_('Region'),
|
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned region')
|
help_text=_('Assigned region')
|
||||||
)
|
)
|
||||||
group = CSVModelChoiceField(
|
group = CSVModelChoiceField(
|
||||||
label=_('Group'),
|
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned group')
|
help_text=_('Assigned group')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -125,13 +119,11 @@ class SiteImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class LocationImportForm(NetBoxModelImportForm):
|
class LocationImportForm(NetBoxModelImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned site')
|
help_text=_('Assigned site')
|
||||||
)
|
)
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -141,12 +133,10 @@ class LocationImportForm(NetBoxModelImportForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=LocationStatusChoices,
|
choices=LocationStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -171,54 +161,45 @@ class RackRoleImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class RackImportForm(NetBoxModelImportForm):
|
class RackImportForm(NetBoxModelImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
location = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Name of assigned tenant')
|
help_text=_('Name of assigned tenant')
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=RackStatusChoices,
|
choices=RackStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
role = CSVModelChoiceField(
|
role = CSVModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Name of assigned role')
|
help_text=_('Name of assigned role')
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=RackTypeChoices,
|
choices=RackTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Rack type')
|
help_text=_('Rack type')
|
||||||
)
|
)
|
||||||
width = forms.ChoiceField(
|
width = forms.ChoiceField(
|
||||||
label=_('Width'),
|
|
||||||
choices=RackWidthChoices,
|
choices=RackWidthChoices,
|
||||||
help_text=_('Rail-to-rail width (in inches)')
|
help_text=_('Rail-to-rail width (in inches)')
|
||||||
)
|
)
|
||||||
outer_unit = CSVChoiceField(
|
outer_unit = CSVChoiceField(
|
||||||
label=_('Outer unit'),
|
|
||||||
choices=RackDimensionUnitChoices,
|
choices=RackDimensionUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Unit for outer dimensions')
|
help_text=_('Unit for outer dimensions')
|
||||||
)
|
)
|
||||||
weight_unit = CSVChoiceField(
|
weight_unit = CSVChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=WeightUnitChoices,
|
choices=WeightUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Unit for rack weights')
|
help_text=_('Unit for rack weights')
|
||||||
@@ -244,32 +225,27 @@ class RackImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class RackReservationImportForm(NetBoxModelImportForm):
|
class RackReservationImportForm(NetBoxModelImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Parent site')
|
help_text=_('Parent site')
|
||||||
)
|
)
|
||||||
location = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Rack's location (if any)")
|
help_text=_("Rack's location (if any)")
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Rack')
|
help_text=_('Rack')
|
||||||
)
|
)
|
||||||
units = SimpleArrayField(
|
units = SimpleArrayField(
|
||||||
label=_('Units'),
|
|
||||||
base_field=forms.IntegerField(),
|
base_field=forms.IntegerField(),
|
||||||
required=True,
|
required=True,
|
||||||
help_text=_('Comma-separated list of individual unit numbers')
|
help_text=_('Comma-separated list of individual unit numbers')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -306,25 +282,21 @@ class ManufacturerImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class DeviceTypeImportForm(NetBoxModelImportForm):
|
class DeviceTypeImportForm(NetBoxModelImportForm):
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('The manufacturer which produces this device type')
|
help_text=_('The manufacturer which produces this device type')
|
||||||
)
|
)
|
||||||
default_platform = forms.ModelChoiceField(
|
default_platform = forms.ModelChoiceField(
|
||||||
label=_('Default platform'),
|
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('The default platform for devices of this type (optional)')
|
help_text=_('The default platform for devices of this type (optional)')
|
||||||
)
|
)
|
||||||
weight = forms.DecimalField(
|
weight = forms.DecimalField(
|
||||||
label=_('Weight'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Device weight'),
|
help_text=_('Device weight'),
|
||||||
)
|
)
|
||||||
weight_unit = CSVChoiceField(
|
weight_unit = CSVChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=WeightUnitChoices,
|
choices=WeightUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Unit for device weight')
|
help_text=_('Unit for device weight')
|
||||||
@@ -340,17 +312,14 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ModuleTypeImportForm(NetBoxModelImportForm):
|
class ModuleTypeImportForm(NetBoxModelImportForm):
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
weight = forms.DecimalField(
|
weight = forms.DecimalField(
|
||||||
label=_('Weight'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Module weight'),
|
help_text=_('Module weight'),
|
||||||
)
|
)
|
||||||
weight_unit = CSVChoiceField(
|
weight_unit = CSVChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=WeightUnitChoices,
|
choices=WeightUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Unit for module weight')
|
help_text=_('Unit for module weight')
|
||||||
@@ -363,7 +332,6 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class DeviceRoleImportForm(NetBoxModelImportForm):
|
class DeviceRoleImportForm(NetBoxModelImportForm):
|
||||||
config_template = CSVModelChoiceField(
|
config_template = CSVModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -382,14 +350,12 @@ class DeviceRoleImportForm(NetBoxModelImportForm):
|
|||||||
class PlatformImportForm(NetBoxModelImportForm):
|
class PlatformImportForm(NetBoxModelImportForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
manufacturer = CSVModelChoiceField(
|
manufacturer = CSVModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Limit platform assignments to this manufacturer')
|
help_text=_('Limit platform assignments to this manufacturer')
|
||||||
)
|
)
|
||||||
config_template = CSVModelChoiceField(
|
config_template = CSVModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -399,57 +365,49 @@ class PlatformImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
|
'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseDeviceImportForm(NetBoxModelImportForm):
|
class BaseDeviceImportForm(NetBoxModelImportForm):
|
||||||
role = CSVModelChoiceField(
|
device_role = CSVModelChoiceField(
|
||||||
label=_('Device role'),
|
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned role')
|
help_text=_('Assigned role')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned tenant')
|
help_text=_('Assigned tenant')
|
||||||
)
|
)
|
||||||
manufacturer = CSVModelChoiceField(
|
manufacturer = CSVModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Device type manufacturer')
|
help_text=_('Device type manufacturer')
|
||||||
)
|
)
|
||||||
device_type = CSVModelChoiceField(
|
device_type = CSVModelChoiceField(
|
||||||
label=_('Device type'),
|
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
to_field_name='model',
|
to_field_name='model',
|
||||||
help_text=_('Device type model')
|
help_text=_('Device type model')
|
||||||
)
|
)
|
||||||
platform = CSVModelChoiceField(
|
platform = CSVModelChoiceField(
|
||||||
label=_('Platform'),
|
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned platform')
|
help_text=_('Assigned platform')
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=DeviceStatusChoices,
|
choices=DeviceStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
virtual_chassis = CSVModelChoiceField(
|
virtual_chassis = CSVModelChoiceField(
|
||||||
label=_('Virtual chassis'),
|
|
||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Virtual chassis')
|
help_text=_('Virtual chassis')
|
||||||
)
|
)
|
||||||
cluster = CSVModelChoiceField(
|
cluster = CSVModelChoiceField(
|
||||||
label=_('Cluster'),
|
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -472,53 +430,45 @@ class BaseDeviceImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class DeviceImportForm(BaseDeviceImportForm):
|
class DeviceImportForm(BaseDeviceImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned site')
|
help_text=_('Assigned site')
|
||||||
)
|
)
|
||||||
location = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Assigned location (if any)")
|
help_text=_("Assigned location (if any)")
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Assigned rack (if any)")
|
help_text=_("Assigned rack (if any)")
|
||||||
)
|
)
|
||||||
face = CSVChoiceField(
|
face = CSVChoiceField(
|
||||||
label=_('Face'),
|
|
||||||
choices=DeviceFaceChoices,
|
choices=DeviceFaceChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Mounted rack face')
|
help_text=_('Mounted rack face')
|
||||||
)
|
)
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Parent device (for child devices)')
|
help_text=_('Parent device (for child devices)')
|
||||||
)
|
)
|
||||||
device_bay = CSVModelChoiceField(
|
device_bay = CSVModelChoiceField(
|
||||||
label=_('Device bay'),
|
|
||||||
queryset=DeviceBay.objects.all(),
|
queryset=DeviceBay.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Device bay in which this device is installed (for child devices)')
|
help_text=_('Device bay in which this device is installed (for child devices)')
|
||||||
)
|
)
|
||||||
airflow = CSVChoiceField(
|
airflow = CSVChoiceField(
|
||||||
label=_('Airflow'),
|
|
||||||
choices=DeviceAirflowChoices,
|
choices=DeviceAirflowChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Airflow direction')
|
help_text=_('Airflow direction')
|
||||||
)
|
)
|
||||||
config_template = CSVModelChoiceField(
|
config_template = CSVModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -527,10 +477,9 @@ class DeviceImportForm(BaseDeviceImportForm):
|
|||||||
|
|
||||||
class Meta(BaseDeviceImportForm.Meta):
|
class Meta(BaseDeviceImportForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||||
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent', 'device_bay', 'airflow',
|
'site', 'location', 'rack', 'position', 'face', 'parent', 'device_bay', 'airflow', 'virtual_chassis',
|
||||||
'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments',
|
'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments', 'tags',
|
||||||
'tags',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@@ -573,35 +522,29 @@ class DeviceImportForm(BaseDeviceImportForm):
|
|||||||
|
|
||||||
class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
|
class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('The device in which this module is installed')
|
help_text=_('The device in which this module is installed')
|
||||||
)
|
)
|
||||||
module_bay = CSVModelChoiceField(
|
module_bay = CSVModelChoiceField(
|
||||||
label=_('Module bay'),
|
|
||||||
queryset=ModuleBay.objects.all(),
|
queryset=ModuleBay.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('The module bay in which this module is installed')
|
help_text=_('The module bay in which this module is installed')
|
||||||
)
|
)
|
||||||
module_type = CSVModelChoiceField(
|
module_type = CSVModelChoiceField(
|
||||||
label=_('Module type'),
|
|
||||||
queryset=ModuleType.objects.all(),
|
queryset=ModuleType.objects.all(),
|
||||||
to_field_name='model',
|
to_field_name='model',
|
||||||
help_text=_('The type of module')
|
help_text=_('The type of module')
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=ModuleStatusChoices,
|
choices=ModuleStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
replicate_components = forms.BooleanField(
|
replicate_components = forms.BooleanField(
|
||||||
label=_('Replicate components'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Automatically populate components associated with this module type (enabled by default)')
|
help_text=_('Automatically populate components associated with this module type (enabled by default)')
|
||||||
)
|
)
|
||||||
adopt_components = forms.BooleanField(
|
adopt_components = forms.BooleanField(
|
||||||
label=_('Adopt components'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Adopt already existing components')
|
help_text=_('Adopt already existing components')
|
||||||
)
|
)
|
||||||
@@ -635,18 +578,15 @@ class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ConsolePortImportForm(NetBoxModelImportForm):
|
class ConsolePortImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Port type')
|
help_text=_('Port type')
|
||||||
)
|
)
|
||||||
speed = CSVTypedChoiceField(
|
speed = CSVTypedChoiceField(
|
||||||
label=_('Speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
coerce=int,
|
coerce=int,
|
||||||
empty_value=None,
|
empty_value=None,
|
||||||
@@ -661,18 +601,15 @@ class ConsolePortImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ConsoleServerPortImportForm(NetBoxModelImportForm):
|
class ConsoleServerPortImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Port type')
|
help_text=_('Port type')
|
||||||
)
|
)
|
||||||
speed = CSVTypedChoiceField(
|
speed = CSVTypedChoiceField(
|
||||||
label=_('Speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
coerce=int,
|
coerce=int,
|
||||||
empty_value=None,
|
empty_value=None,
|
||||||
@@ -687,12 +624,10 @@ class ConsoleServerPortImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class PowerPortImportForm(NetBoxModelImportForm):
|
class PowerPortImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Port type')
|
help_text=_('Port type')
|
||||||
@@ -707,25 +642,21 @@ class PowerPortImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class PowerOutletImportForm(NetBoxModelImportForm):
|
class PowerOutletImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Outlet type')
|
help_text=_('Outlet type')
|
||||||
)
|
)
|
||||||
power_port = CSVModelChoiceField(
|
power_port = CSVModelChoiceField(
|
||||||
label=_('Power port'),
|
|
||||||
queryset=PowerPort.objects.all(),
|
queryset=PowerPort.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Local power port which feeds this outlet')
|
help_text=_('Local power port which feeds this outlet')
|
||||||
)
|
)
|
||||||
feed_leg = CSVChoiceField(
|
feed_leg = CSVChoiceField(
|
||||||
label=_('Feed lag'),
|
|
||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Electrical phase (for three-phase circuits)')
|
help_text=_('Electrical phase (for three-phase circuits)')
|
||||||
@@ -760,75 +691,63 @@ class PowerOutletImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class InterfaceImportForm(NetBoxModelImportForm):
|
class InterfaceImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Parent interface')
|
help_text=_('Parent interface')
|
||||||
)
|
)
|
||||||
bridge = CSVModelChoiceField(
|
bridge = CSVModelChoiceField(
|
||||||
label=_('Bridge'),
|
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Bridged interface')
|
help_text=_('Bridged interface')
|
||||||
)
|
)
|
||||||
lag = CSVModelChoiceField(
|
lag = CSVModelChoiceField(
|
||||||
label=_('Lag'),
|
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Parent LAG interface')
|
help_text=_('Parent LAG interface')
|
||||||
)
|
)
|
||||||
vdcs = CSVModelMultipleChoiceField(
|
vdcs = CSVModelMultipleChoiceField(
|
||||||
label=_('Vdcs'),
|
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('VDC names separated by commas, encased with double quotes (e.g. "vdc1, vdc2, vdc3")')
|
help_text='VDC names separated by commas, encased with double quotes (e.g. "vdc1, vdc2, vdc3")'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=InterfaceTypeChoices,
|
choices=InterfaceTypeChoices,
|
||||||
help_text=_('Physical medium')
|
help_text=_('Physical medium')
|
||||||
)
|
)
|
||||||
duplex = CSVChoiceField(
|
duplex = CSVChoiceField(
|
||||||
label=_('Duplex'),
|
|
||||||
choices=InterfaceDuplexChoices,
|
choices=InterfaceDuplexChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
poe_mode = CSVChoiceField(
|
poe_mode = CSVChoiceField(
|
||||||
label=_('Poe mode'),
|
|
||||||
choices=InterfacePoEModeChoices,
|
choices=InterfacePoEModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('PoE mode')
|
help_text=_('PoE mode')
|
||||||
)
|
)
|
||||||
poe_type = CSVChoiceField(
|
poe_type = CSVChoiceField(
|
||||||
label=_('Poe type'),
|
|
||||||
choices=InterfacePoETypeChoices,
|
choices=InterfacePoETypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('PoE type')
|
help_text=_('PoE type')
|
||||||
)
|
)
|
||||||
mode = CSVChoiceField(
|
mode = CSVChoiceField(
|
||||||
label=_('Mode'),
|
|
||||||
choices=InterfaceModeChoices,
|
choices=InterfaceModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
|
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
|
||||||
)
|
)
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
help_text=_('Assigned VRF')
|
help_text=_('Assigned VRF')
|
||||||
)
|
)
|
||||||
rf_role = CSVChoiceField(
|
rf_role = CSVChoiceField(
|
||||||
label=_('Rf role'),
|
|
||||||
choices=WirelessRoleChoices,
|
choices=WirelessRoleChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Wireless role (AP/station)')
|
help_text=_('Wireless role (AP/station)')
|
||||||
@@ -872,18 +791,15 @@ class InterfaceImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class FrontPortImportForm(NetBoxModelImportForm):
|
class FrontPortImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
rear_port = CSVModelChoiceField(
|
rear_port = CSVModelChoiceField(
|
||||||
label=_('Rear port'),
|
|
||||||
queryset=RearPort.objects.all(),
|
queryset=RearPort.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Corresponding rear port')
|
help_text=_('Corresponding rear port')
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
help_text=_('Physical medium classification')
|
help_text=_('Physical medium classification')
|
||||||
)
|
)
|
||||||
@@ -920,12 +836,10 @@ class FrontPortImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class RearPortImportForm(NetBoxModelImportForm):
|
class RearPortImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
help_text=_('Physical medium classification'),
|
help_text=_('Physical medium classification'),
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
)
|
)
|
||||||
@@ -937,7 +851,6 @@ class RearPortImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class ModuleBayImportForm(NetBoxModelImportForm):
|
class ModuleBayImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
@@ -949,12 +862,10 @@ class ModuleBayImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class DeviceBayImportForm(NetBoxModelImportForm):
|
class DeviceBayImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
installed_device = CSVModelChoiceField(
|
installed_device = CSVModelChoiceField(
|
||||||
label=_('Installed device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -997,38 +908,32 @@ class DeviceBayImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class InventoryItemImportForm(NetBoxModelImportForm):
|
class InventoryItemImportForm(NetBoxModelImportForm):
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
role = CSVModelChoiceField(
|
role = CSVModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
manufacturer = CSVModelChoiceField(
|
manufacturer = CSVModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Parent inventory item')
|
help_text=_('Parent inventory item')
|
||||||
)
|
)
|
||||||
component_type = CSVContentTypeField(
|
component_type = CSVContentTypeField(
|
||||||
label=_('Component type'),
|
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=MODULAR_COMPONENT_MODELS,
|
limit_choices_to=MODULAR_COMPONENT_MODELS,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Component Type')
|
help_text=_('Component Type')
|
||||||
)
|
)
|
||||||
component_name = forms.CharField(
|
component_name = forms.CharField(
|
||||||
label=_('Compnent name'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Component Name')
|
help_text=_('Component Name')
|
||||||
)
|
)
|
||||||
@@ -1096,62 +1001,52 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
|
|||||||
class CableImportForm(NetBoxModelImportForm):
|
class CableImportForm(NetBoxModelImportForm):
|
||||||
# Termination A
|
# Termination A
|
||||||
side_a_device = CSVModelChoiceField(
|
side_a_device = CSVModelChoiceField(
|
||||||
label=_('Side a device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Side A device')
|
help_text=_('Side A device')
|
||||||
)
|
)
|
||||||
side_a_type = CSVContentTypeField(
|
side_a_type = CSVContentTypeField(
|
||||||
label=_('Side a type'),
|
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||||
help_text=_('Side A type')
|
help_text=_('Side A type')
|
||||||
)
|
)
|
||||||
side_a_name = forms.CharField(
|
side_a_name = forms.CharField(
|
||||||
label=_('Side a name'),
|
|
||||||
help_text=_('Side A component name')
|
help_text=_('Side A component name')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Termination B
|
# Termination B
|
||||||
side_b_device = CSVModelChoiceField(
|
side_b_device = CSVModelChoiceField(
|
||||||
label=_('Side b device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Side B device')
|
help_text=_('Side B device')
|
||||||
)
|
)
|
||||||
side_b_type = CSVContentTypeField(
|
side_b_type = CSVContentTypeField(
|
||||||
label=_('Side b type'),
|
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||||
help_text=_('Side B type')
|
help_text=_('Side B type')
|
||||||
)
|
)
|
||||||
side_b_name = forms.CharField(
|
side_b_name = forms.CharField(
|
||||||
label=_('Side b name'),
|
|
||||||
help_text=_('Side B component name')
|
help_text=_('Side B component name')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cable attributes
|
# Cable attributes
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=LinkStatusChoices,
|
choices=LinkStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Connection status')
|
help_text=_('Connection status')
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=CableTypeChoices,
|
choices=CableTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Physical medium classification')
|
help_text=_('Physical medium classification')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned tenant')
|
help_text=_('Assigned tenant')
|
||||||
)
|
)
|
||||||
length_unit = CSVChoiceField(
|
length_unit = CSVChoiceField(
|
||||||
label=_('Length unit'),
|
|
||||||
choices=CableLengthUnitChoices,
|
choices=CableLengthUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Length unit')
|
help_text=_('Length unit')
|
||||||
@@ -1214,7 +1109,6 @@ class CableImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class VirtualChassisImportForm(NetBoxModelImportForm):
|
class VirtualChassisImportForm(NetBoxModelImportForm):
|
||||||
master = CSVModelChoiceField(
|
master = CSVModelChoiceField(
|
||||||
label=_('Master'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -1232,13 +1126,11 @@ class VirtualChassisImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class PowerPanelImportForm(NetBoxModelImportForm):
|
class PowerPanelImportForm(NetBoxModelImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Name of parent site')
|
help_text=_('Name of parent site')
|
||||||
)
|
)
|
||||||
location = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
@@ -1260,54 +1152,40 @@ class PowerPanelImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class PowerFeedImportForm(NetBoxModelImportForm):
|
class PowerFeedImportForm(NetBoxModelImportForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned site')
|
help_text=_('Assigned site')
|
||||||
)
|
)
|
||||||
power_panel = CSVModelChoiceField(
|
power_panel = CSVModelChoiceField(
|
||||||
label=_('Power panel'),
|
|
||||||
queryset=PowerPanel.objects.all(),
|
queryset=PowerPanel.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Upstream power panel')
|
help_text=_('Upstream power panel')
|
||||||
)
|
)
|
||||||
location = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Rack's location (if any)")
|
help_text=_("Rack's location (if any)")
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Rack')
|
help_text=_('Rack')
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
required=False,
|
|
||||||
help_text=_('Assigned tenant')
|
|
||||||
)
|
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=PowerFeedStatusChoices,
|
choices=PowerFeedStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PowerFeedTypeChoices,
|
choices=PowerFeedTypeChoices,
|
||||||
help_text=_('Primary or redundant')
|
help_text=_('Primary or redundant')
|
||||||
)
|
)
|
||||||
supply = CSVChoiceField(
|
supply = CSVChoiceField(
|
||||||
label=_('Supply'),
|
|
||||||
choices=PowerFeedSupplyChoices,
|
choices=PowerFeedSupplyChoices,
|
||||||
help_text=_('Supply type (AC/DC)')
|
help_text=_('Supply type (AC/DC)')
|
||||||
)
|
)
|
||||||
phase = CSVChoiceField(
|
phase = CSVChoiceField(
|
||||||
label=_('Phase'),
|
|
||||||
choices=PowerFeedPhaseChoices,
|
choices=PowerFeedPhaseChoices,
|
||||||
help_text=_('Single or three-phase')
|
help_text=_('Single or three-phase')
|
||||||
)
|
)
|
||||||
@@ -1316,7 +1194,7 @@ class PowerFeedImportForm(NetBoxModelImportForm):
|
|||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fields = (
|
fields = (
|
||||||
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
||||||
'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'comments', 'tags',
|
'voltage', 'amperage', 'max_utilization', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@@ -1343,13 +1221,11 @@ class PowerFeedImportForm(NetBoxModelImportForm):
|
|||||||
class VirtualDeviceContextImportForm(NetBoxModelImportForm):
|
class VirtualDeviceContextImportForm(NetBoxModelImportForm):
|
||||||
|
|
||||||
device = CSVModelChoiceField(
|
device = CSVModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Assigned role'
|
help_text='Assigned role'
|
||||||
)
|
)
|
||||||
tenant = CSVModelChoiceField(
|
tenant = CSVModelChoiceField(
|
||||||
label=_('Tenant'),
|
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
@@ -47,7 +47,7 @@ class InterfaceCommonForm(forms.Form):
|
|||||||
# Untagged interfaces cannot be assigned tagged VLANs
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'mode': _("An access interface cannot have tagged VLANs assigned.")
|
'mode': "An access interface cannot have tagged VLANs assigned."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
||||||
@@ -61,10 +61,8 @@ class InterfaceCommonForm(forms.Form):
|
|||||||
|
|
||||||
if invalid_vlans:
|
if invalid_vlans:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'tagged_vlans': _(
|
'tagged_vlans': f"The tagged VLANs ({', '.join(invalid_vlans)}) must belong to the same site as "
|
||||||
"The tagged VLANs ({vlans}) must belong to the same site as the interface's parent device/VM, "
|
f"the interface's parent device/VM, or they must be global"
|
||||||
"or they must be global"
|
|
||||||
).format(vlans=', '.join(invalid_vlans))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +105,7 @@ class ModuleCommonForm(forms.Form):
|
|||||||
# Installing modules with placeholders require that the bay has a position value
|
# Installing modules with placeholders require that the bay has a position value
|
||||||
if MODULE_TOKEN in template.name and not module_bay.position:
|
if MODULE_TOKEN in template.name and not module_bay.position:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("Cannot install module with placeholder values in a module bay with no position defined.")
|
"Cannot install module with placeholder values in a module bay with no position defined"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
|
resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
|
||||||
@@ -116,17 +114,12 @@ class ModuleCommonForm(forms.Form):
|
|||||||
# It is not possible to adopt components already belonging to a module
|
# It is not possible to adopt components already belonging to a module
|
||||||
if adopt_components and existing_item and existing_item.module:
|
if adopt_components and existing_item and existing_item.module:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("Cannot adopt {name} '{resolved_name}' as it already belongs to a module").format(
|
f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs "
|
||||||
name=template.component_model.__name__,
|
f"to a module"
|
||||||
resolved_name=resolved_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If we are not adopting components we error if the component exists
|
# If we are not adopting components we error if the component exists
|
||||||
if not adopt_components and resolved_name in installed_components:
|
if not adopt_components and resolved_name in installed_components:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("{name} - {resolved_name} already exists").format(
|
f"{template.component_model.__name__} - {resolved_name} already exists"
|
||||||
name=template.component_model.__name__,
|
|
||||||
resolved_name=resolved_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
@@ -56,11 +56,9 @@ __all__ = (
|
|||||||
|
|
||||||
class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
label=_('Name'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
label = forms.CharField(
|
label = forms.CharField(
|
||||||
label=_('Label'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@@ -109,7 +107,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Device type')
|
label=_('Device type')
|
||||||
)
|
)
|
||||||
role_id = DynamicModelMultipleChoiceField(
|
device_role_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Device role')
|
label=_('Device role')
|
||||||
@@ -122,7 +120,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
|||||||
'location_id': '$location_id',
|
'location_id': '$location_id',
|
||||||
'virtual_chassis_id': '$virtual_chassis_id',
|
'virtual_chassis_id': '$virtual_chassis_id',
|
||||||
'device_type_id': '$device_type_id',
|
'device_type_id': '$device_type_id',
|
||||||
'role_id': '$role_id'
|
'role_id': '$device_role_id'
|
||||||
},
|
},
|
||||||
label=_('Device')
|
label=_('Device')
|
||||||
)
|
)
|
||||||
@@ -132,7 +130,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = Region
|
model = Region
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||||
)
|
)
|
||||||
parent_id = DynamicModelMultipleChoiceField(
|
parent_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -146,7 +144,7 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||||
)
|
)
|
||||||
parent_id = DynamicModelMultipleChoiceField(
|
parent_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
@@ -160,12 +158,11 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
model = Site
|
model = Site
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('status', 'region_id', 'group_id', 'asn_id')),
|
('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=SiteStatusChoices,
|
choices=SiteStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -191,9 +188,9 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
|
|||||||
model = Location
|
model = Location
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
|
('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -224,7 +221,6 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
|
|||||||
label=_('Parent')
|
label=_('Parent')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=LocationStatusChoices,
|
choices=LocationStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -240,12 +236,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
model = Rack
|
model = Rack
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||||
(_('Function'), ('status', 'role_id')),
|
('Function', ('status', 'role_id')),
|
||||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
('Weight', ('weight', 'max_weight', 'weight_unit')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -275,17 +271,14 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
label=_('Location')
|
label=_('Location')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=RackStatusChoices,
|
choices=RackStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=RackTypeChoices,
|
choices=RackTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
width = forms.MultipleChoiceField(
|
width = forms.MultipleChoiceField(
|
||||||
label=_('Width'),
|
|
||||||
choices=RackWidthChoices,
|
choices=RackWidthChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -296,26 +289,21 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
label=_('Role')
|
label=_('Role')
|
||||||
)
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
label=_('Serial'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
asset_tag = forms.CharField(
|
asset_tag = forms.CharField(
|
||||||
label=_('Asset tag'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
weight = forms.DecimalField(
|
weight = forms.DecimalField(
|
||||||
label=_('Weight'),
|
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1
|
min_value=1
|
||||||
)
|
)
|
||||||
max_weight = forms.IntegerField(
|
max_weight = forms.IntegerField(
|
||||||
label=_('Max weight'),
|
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1
|
min_value=1
|
||||||
)
|
)
|
||||||
weight_unit = forms.ChoiceField(
|
weight_unit = forms.ChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=add_blank_choice(WeightUnitChoices),
|
choices=add_blank_choice(WeightUnitChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -324,12 +312,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
class RackElevationFilterForm(RackFilterForm):
|
class RackElevationFilterForm(RackFilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
|
||||||
(_('Function'), ('status', 'role_id')),
|
('Function', ('status', 'role_id')),
|
||||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
('Weight', ('weight', 'max_weight', 'weight_unit')),
|
||||||
)
|
)
|
||||||
id = DynamicModelMultipleChoiceField(
|
id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
@@ -346,9 +334,9 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = RackReservation
|
model = RackReservation
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('User'), ('user_id',)),
|
('User', ('user_id',)),
|
||||||
(_('Rack'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -388,7 +376,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
label=_('Rack')
|
label=_('Rack')
|
||||||
)
|
)
|
||||||
user_id = DynamicModelMultipleChoiceField(
|
user_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=User.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('User'),
|
label=_('User'),
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
@@ -402,7 +390,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@@ -411,13 +399,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = DeviceType
|
model = DeviceType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Hardware'), ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
|
('Hardware', ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||||
(_('Images'), ('has_front_image', 'has_rear_image')),
|
('Images', ('has_front_image', 'has_rear_image')),
|
||||||
(_('Components'), (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
||||||
)),
|
)),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
('Weight', ('weight', 'weight_unit')),
|
||||||
)
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@@ -430,103 +418,98 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('Default platform')
|
label=_('Default platform')
|
||||||
)
|
)
|
||||||
part_number = forms.CharField(
|
part_number = forms.CharField(
|
||||||
label=_('Part number'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
subdevice_role = forms.MultipleChoiceField(
|
subdevice_role = forms.MultipleChoiceField(
|
||||||
label=_('Subdevice role'),
|
|
||||||
choices=add_blank_choice(SubdeviceRoleChoices),
|
choices=add_blank_choice(SubdeviceRoleChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
airflow = forms.MultipleChoiceField(
|
airflow = forms.MultipleChoiceField(
|
||||||
label=_('Airflow'),
|
|
||||||
choices=add_blank_choice(DeviceAirflowChoices),
|
choices=add_blank_choice(DeviceAirflowChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
has_front_image = forms.NullBooleanField(
|
has_front_image = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has a front image'),
|
label='Has a front image',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
has_rear_image = forms.NullBooleanField(
|
has_rear_image = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has a rear image'),
|
label='Has a rear image',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console_ports = forms.NullBooleanField(
|
console_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console ports'),
|
label='Has console ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console_server_ports = forms.NullBooleanField(
|
console_server_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console server ports'),
|
label='Has console server ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_ports = forms.NullBooleanField(
|
power_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power ports'),
|
label='Has power ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_outlets = forms.NullBooleanField(
|
power_outlets = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power outlets'),
|
label='Has power outlets',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
interfaces = forms.NullBooleanField(
|
interfaces = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has interfaces'),
|
label='Has interfaces',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pass_through_ports = forms.NullBooleanField(
|
pass_through_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has pass-through ports'),
|
label='Has pass-through ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
device_bays = forms.NullBooleanField(
|
device_bays = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has device bays'),
|
label='Has device bays',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
module_bays = forms.NullBooleanField(
|
module_bays = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has module bays'),
|
label='Has module bays',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
inventory_items = forms.NullBooleanField(
|
inventory_items = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has inventory items'),
|
label='Has inventory items',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
weight = forms.DecimalField(
|
weight = forms.DecimalField(
|
||||||
label=_('Weight'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
weight_unit = forms.ChoiceField(
|
weight_unit = forms.ChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=add_blank_choice(WeightUnitChoices),
|
choices=add_blank_choice(WeightUnitChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -536,12 +519,12 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
model = ModuleType
|
model = ModuleType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Hardware'), ('manufacturer_id', 'part_number')),
|
('Hardware', ('manufacturer_id', 'part_number')),
|
||||||
(_('Components'), (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports',
|
'pass_through_ports',
|
||||||
)),
|
)),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
('Weight', ('weight', 'weight_unit')),
|
||||||
)
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@@ -550,58 +533,55 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
fetch_trigger='open'
|
fetch_trigger='open'
|
||||||
)
|
)
|
||||||
part_number = forms.CharField(
|
part_number = forms.CharField(
|
||||||
label=_('Part number'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
console_ports = forms.NullBooleanField(
|
console_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console ports'),
|
label='Has console ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console_server_ports = forms.NullBooleanField(
|
console_server_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console server ports'),
|
label='Has console server ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_ports = forms.NullBooleanField(
|
power_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power ports'),
|
label='Has power ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_outlets = forms.NullBooleanField(
|
power_outlets = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power outlets'),
|
label='Has power outlets',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
interfaces = forms.NullBooleanField(
|
interfaces = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has interfaces'),
|
label='Has interfaces',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pass_through_ports = forms.NullBooleanField(
|
pass_through_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has pass-through ports'),
|
label='Has pass-through ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
weight = forms.DecimalField(
|
weight = forms.DecimalField(
|
||||||
label=_('Weight'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
weight_unit = forms.ChoiceField(
|
weight_unit = forms.ChoiceField(
|
||||||
label=_('Weight unit'),
|
|
||||||
choices=add_blank_choice(WeightUnitChoices),
|
choices=add_blank_choice(WeightUnitChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -641,17 +621,15 @@ class DeviceFilterForm(
|
|||||||
model = Device
|
model = Device
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Operation'), ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
|
('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
|
||||||
(_('Hardware'), ('manufacturer_id', 'device_type_id', 'platform_id')),
|
('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
(_('Components'), (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||||
)),
|
)),
|
||||||
(_('Miscellaneous'), (
|
('Miscellaneous', ('has_primary_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data'))
|
||||||
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -716,26 +694,22 @@ class DeviceFilterForm(
|
|||||||
label=_('Platform')
|
label=_('Platform')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=DeviceStatusChoices,
|
choices=DeviceStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
airflow = forms.MultipleChoiceField(
|
airflow = forms.MultipleChoiceField(
|
||||||
label=_('Airflow'),
|
|
||||||
choices=add_blank_choice(DeviceAirflowChoices),
|
choices=add_blank_choice(DeviceAirflowChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
label=_('Serial'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
asset_tag = forms.CharField(
|
asset_tag = forms.CharField(
|
||||||
label=_('Asset tag'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
mac_address = forms.CharField(
|
mac_address = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('MAC address')
|
label='MAC address'
|
||||||
)
|
)
|
||||||
config_template_id = DynamicModelMultipleChoiceField(
|
config_template_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
@@ -744,63 +718,56 @@ class DeviceFilterForm(
|
|||||||
)
|
)
|
||||||
has_primary_ip = forms.NullBooleanField(
|
has_primary_ip = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has a primary IP'),
|
label='Has a primary IP',
|
||||||
widget=forms.Select(
|
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
|
||||||
)
|
|
||||||
)
|
|
||||||
has_oob_ip = forms.NullBooleanField(
|
|
||||||
required=False,
|
|
||||||
label='Has an OOB IP',
|
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
virtual_chassis_member = forms.NullBooleanField(
|
virtual_chassis_member = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Virtual chassis member'),
|
label='Virtual chassis member',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console_ports = forms.NullBooleanField(
|
console_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console ports'),
|
label='Has console ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console_server_ports = forms.NullBooleanField(
|
console_server_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has console server ports'),
|
label='Has console server ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_ports = forms.NullBooleanField(
|
power_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power ports'),
|
label='Has power ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
power_outlets = forms.NullBooleanField(
|
power_outlets = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has power outlets'),
|
label='Has power outlets',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
interfaces = forms.NullBooleanField(
|
interfaces = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has interfaces'),
|
label='Has interfaces',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pass_through_ports = forms.NullBooleanField(
|
pass_through_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has pass-through ports'),
|
label='Has pass-through ports',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
@@ -815,8 +782,8 @@ class VirtualDeviceContextFilterForm(
|
|||||||
model = VirtualDeviceContext
|
model = VirtualDeviceContext
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('device', 'status', 'has_primary_ip')),
|
('Attributes', ('device', 'status', 'has_primary_ip')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
device = DynamicModelMultipleChoiceField(
|
device = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
@@ -825,13 +792,12 @@ class VirtualDeviceContextFilterForm(
|
|||||||
fetch_trigger='open'
|
fetch_trigger='open'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
required=False,
|
required=False,
|
||||||
choices=add_blank_choice(VirtualDeviceContextStatusChoices)
|
choices=add_blank_choice(VirtualDeviceContextStatusChoices)
|
||||||
)
|
)
|
||||||
has_primary_ip = forms.NullBooleanField(
|
has_primary_ip = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Has a primary IP'),
|
label='Has a primary IP',
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
@@ -843,7 +809,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||||||
model = Module
|
model = Module
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Hardware'), ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
|
('Hardware', ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
|
||||||
)
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@@ -861,16 +827,13 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||||||
fetch_trigger='open'
|
fetch_trigger='open'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=ModuleStatusChoices,
|
choices=ModuleStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
label=_('Serial'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
asset_tag = forms.CharField(
|
asset_tag = forms.CharField(
|
||||||
label=_('Asset tag'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
@@ -880,8 +843,8 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -909,9 +872,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = Cable
|
model = Cable
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')),
|
('Location', ('site_id', 'location_id', 'rack_id', 'device_id')),
|
||||||
(_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit')),
|
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -957,25 +920,20 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
label=_('Device')
|
label=_('Device')
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=add_blank_choice(CableTypeChoices),
|
choices=add_blank_choice(CableTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
required=False,
|
required=False,
|
||||||
choices=add_blank_choice(LinkStatusChoices)
|
choices=add_blank_choice(LinkStatusChoices)
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
label=_('Color'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
length = forms.IntegerField(
|
length = forms.IntegerField(
|
||||||
label=_('Length'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
length_unit = forms.ChoiceField(
|
length_unit = forms.ChoiceField(
|
||||||
label=_('Length unit'),
|
|
||||||
choices=add_blank_choice(CableLengthUnitChoices),
|
choices=add_blank_choice(CableLengthUnitChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -986,8 +944,8 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -1020,13 +978,12 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class PowerFeedFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
||||||
(_('Attributes'), ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@@ -1065,35 +1022,28 @@ class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
label=_('Rack')
|
label=_('Rack')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
label=_('Status'),
|
|
||||||
choices=PowerFeedStatusChoices,
|
choices=PowerFeedStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=add_blank_choice(PowerFeedTypeChoices),
|
choices=add_blank_choice(PowerFeedTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
supply = forms.ChoiceField(
|
supply = forms.ChoiceField(
|
||||||
label=_('Supply'),
|
|
||||||
choices=add_blank_choice(PowerFeedSupplyChoices),
|
choices=add_blank_choice(PowerFeedSupplyChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
phase = forms.ChoiceField(
|
phase = forms.ChoiceField(
|
||||||
label=_('Phase'),
|
|
||||||
choices=add_blank_choice(PowerFeedPhaseChoices),
|
choices=add_blank_choice(PowerFeedPhaseChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
voltage = forms.IntegerField(
|
voltage = forms.IntegerField(
|
||||||
label=_('Voltage'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
amperage = forms.IntegerField(
|
amperage = forms.IntegerField(
|
||||||
label=_('Amperage'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
max_utilization = forms.IntegerField(
|
max_utilization = forms.IntegerField(
|
||||||
label=_('Max utilization'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
@@ -1105,14 +1055,12 @@ class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
|
|
||||||
class CabledFilterForm(forms.Form):
|
class CabledFilterForm(forms.Form):
|
||||||
cabled = forms.NullBooleanField(
|
cabled = forms.NullBooleanField(
|
||||||
label=_('Cabled'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
occupied = forms.NullBooleanField(
|
occupied = forms.NullBooleanField(
|
||||||
label=_('Occupied'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
@@ -1122,7 +1070,6 @@ class CabledFilterForm(forms.Form):
|
|||||||
|
|
||||||
class PathEndpointFilterForm(CabledFilterForm):
|
class PathEndpointFilterForm(CabledFilterForm):
|
||||||
connected = forms.NullBooleanField(
|
connected = forms.NullBooleanField(
|
||||||
label=_('Connected'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
@@ -1134,18 +1081,16 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
speed = forms.MultipleChoiceField(
|
speed = forms.MultipleChoiceField(
|
||||||
label=_('Speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -1156,18 +1101,16 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
|
|||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
speed = forms.MultipleChoiceField(
|
speed = forms.MultipleChoiceField(
|
||||||
label=_('Speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -1178,13 +1121,12 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
model = PowerPort
|
model = PowerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type')),
|
('Attributes', ('name', 'label', 'type')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -1195,13 +1137,12 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type')),
|
('Attributes', ('name', 'label', 'type')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -1212,13 +1153,13 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
model = Interface
|
model = Interface
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
||||||
(_('Addressing'), ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
|
('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
('PoE', ('poe_mode', 'poe_type')),
|
||||||
(_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
vdc_id = DynamicModelMultipleChoiceField(
|
vdc_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
@@ -1229,36 +1170,30 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
label=_('Virtual Device Context')
|
label=_('Virtual Device Context')
|
||||||
)
|
)
|
||||||
kind = forms.MultipleChoiceField(
|
kind = forms.MultipleChoiceField(
|
||||||
label=_('Kind'),
|
|
||||||
choices=InterfaceKindChoices,
|
choices=InterfaceKindChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=InterfaceTypeChoices,
|
choices=InterfaceTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
speed = forms.IntegerField(
|
speed = forms.IntegerField(
|
||||||
label=_('Speed'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=NumberWithOptions(
|
widget=NumberWithOptions(
|
||||||
options=InterfaceSpeedChoices
|
options=InterfaceSpeedChoices
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
duplex = forms.MultipleChoiceField(
|
duplex = forms.MultipleChoiceField(
|
||||||
label=_('Duplex'),
|
|
||||||
choices=InterfaceDuplexChoices,
|
choices=InterfaceDuplexChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
enabled = forms.NullBooleanField(
|
enabled = forms.NullBooleanField(
|
||||||
label=_('Enabled'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mgmt_only = forms.NullBooleanField(
|
mgmt_only = forms.NullBooleanField(
|
||||||
label=_('Mgmt only'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
@@ -1266,50 +1201,50 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
)
|
)
|
||||||
mac_address = forms.CharField(
|
mac_address = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('MAC address')
|
label='MAC address'
|
||||||
)
|
)
|
||||||
wwn = forms.CharField(
|
wwn = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('WWN')
|
label='WWN'
|
||||||
)
|
)
|
||||||
poe_mode = forms.MultipleChoiceField(
|
poe_mode = forms.MultipleChoiceField(
|
||||||
choices=InterfacePoEModeChoices,
|
choices=InterfacePoEModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('PoE mode')
|
label='PoE mode'
|
||||||
)
|
)
|
||||||
poe_type = forms.MultipleChoiceField(
|
poe_type = forms.MultipleChoiceField(
|
||||||
choices=InterfacePoETypeChoices,
|
choices=InterfacePoETypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('PoE type')
|
label='PoE type'
|
||||||
)
|
)
|
||||||
rf_role = forms.MultipleChoiceField(
|
rf_role = forms.MultipleChoiceField(
|
||||||
choices=WirelessRoleChoices,
|
choices=WirelessRoleChoices,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Wireless role')
|
label='Wireless role'
|
||||||
)
|
)
|
||||||
rf_channel = forms.MultipleChoiceField(
|
rf_channel = forms.MultipleChoiceField(
|
||||||
choices=WirelessChannelChoices,
|
choices=WirelessChannelChoices,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Wireless channel')
|
label='Wireless channel'
|
||||||
)
|
)
|
||||||
rf_channel_frequency = forms.IntegerField(
|
rf_channel_frequency = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Channel frequency (MHz)')
|
label='Channel frequency (MHz)'
|
||||||
)
|
)
|
||||||
rf_channel_width = forms.IntegerField(
|
rf_channel_width = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Channel width (MHz)')
|
label='Channel width (MHz)'
|
||||||
)
|
)
|
||||||
tx_power = forms.IntegerField(
|
tx_power = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Transmit power (dBm)'),
|
label='Transmit power (dBm)',
|
||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=127
|
max_value=127
|
||||||
)
|
)
|
||||||
vrf_id = DynamicModelMultipleChoiceField(
|
vrf_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('VRF')
|
label='VRF'
|
||||||
)
|
)
|
||||||
l2vpn_id = DynamicModelMultipleChoiceField(
|
l2vpn_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=L2VPN.objects.all(),
|
queryset=L2VPN.objects.all(),
|
||||||
@@ -1322,19 +1257,17 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
('Attributes', ('name', 'label', 'type', 'color')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Cable'), ('cabled', 'occupied')),
|
('Cable', ('cabled', 'occupied')),
|
||||||
)
|
)
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
label=_('Color'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
@@ -1344,18 +1277,16 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
|||||||
model = RearPort
|
model = RearPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
('Attributes', ('name', 'label', 'type', 'color')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
(_('Cable'), ('cabled', 'occupied')),
|
('Cable', ('cabled', 'occupied')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
label=_('Color'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
@@ -1365,13 +1296,12 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
|||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'position')),
|
('Attributes', ('name', 'label', 'position')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
position = forms.CharField(
|
position = forms.CharField(
|
||||||
label=_('Position'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1380,9 +1310,9 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
|||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label')),
|
('Attributes', ('name', 'label')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@@ -1391,9 +1321,9 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
|||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
(_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||||
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
|
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||||
)
|
)
|
||||||
role_id = DynamicModelMultipleChoiceField(
|
role_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
@@ -1407,15 +1337,12 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
|||||||
label=_('Manufacturer')
|
label=_('Manufacturer')
|
||||||
)
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
label=_('Serial'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
asset_tag = forms.CharField(
|
asset_tag = forms.CharField(
|
||||||
label=_('Asset tag'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
discovered = forms.NullBooleanField(
|
discovered = forms.NullBooleanField(
|
||||||
label=_('Discovered'),
|
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BaseVCMemberFormSet',
|
'BaseVCMemberFormSet',
|
||||||
@@ -17,8 +16,6 @@ class BaseVCMemberFormSet(forms.BaseModelFormSet):
|
|||||||
vc_position = form.cleaned_data.get('vc_position')
|
vc_position = form.cleaned_data.get('vc_position')
|
||||||
if vc_position:
|
if vc_position:
|
||||||
if vc_position in vc_position_list:
|
if vc_position in vc_position_list:
|
||||||
error_msg = _("A virtual chassis member already exists in position {vc_position}.").format(
|
error_msg = f"A virtual chassis member already exists in position {vc_position}."
|
||||||
vc_position=vc_position
|
|
||||||
)
|
|
||||||
form.add_error('vc_position', error_msg)
|
form.add_error('vc_position', error_msg)
|
||||||
vc_position_list.append(vc_position)
|
vc_position_list.append(vc_position)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from timezone_field import TimeZoneFormField
|
from timezone_field import TimeZoneFormField
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
@@ -70,14 +70,13 @@ __all__ = (
|
|||||||
|
|
||||||
class RegionForm(NetBoxModelForm):
|
class RegionForm(NetBoxModelForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Region'), (
|
('Region', (
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -91,14 +90,13 @@ class RegionForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class SiteGroupForm(NetBoxModelForm):
|
class SiteGroupForm(NetBoxModelForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Site Group'), (
|
('Site Group', (
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -112,12 +110,10 @@ class SiteGroupForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class SiteForm(TenancyForm, NetBoxModelForm):
|
class SiteForm(TenancyForm, NetBoxModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
label=_('Region'),
|
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
group = DynamicModelChoiceField(
|
group = DynamicModelChoiceField(
|
||||||
label=_('Group'),
|
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -128,18 +124,17 @@ class SiteForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
time_zone = TimeZoneFormField(
|
time_zone = TimeZoneFormField(
|
||||||
label=_('Time zone'),
|
|
||||||
choices=add_blank_choice(TimeZoneFormField().choices),
|
choices=add_blank_choice(TimeZoneFormField().choices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Site'), (
|
('Site', (
|
||||||
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
(_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -164,12 +159,10 @@ class SiteForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class LocationForm(TenancyForm, NetBoxModelForm):
|
class LocationForm(TenancyForm, NetBoxModelForm):
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -179,8 +172,8 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
|
('Location', ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -194,7 +187,7 @@ class RackRoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Rack Role'), (
|
('Rack Role', (
|
||||||
'name', 'slug', 'color', 'description', 'tags',
|
'name', 'slug', 'color', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -208,12 +201,10 @@ class RackRoleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class RackForm(TenancyForm, NetBoxModelForm):
|
class RackForm(TenancyForm, NetBoxModelForm):
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
location = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -221,7 +212,6 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
role = DynamicModelChoiceField(
|
role = DynamicModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -231,33 +221,30 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
|||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
||||||
'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
|
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||||
'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
|
'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RackReservationForm(TenancyForm, NetBoxModelForm):
|
class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||||
rack = DynamicModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
units = NumericArrayField(
|
units = NumericArrayField(
|
||||||
label=_('Units'),
|
|
||||||
base_field=forms.IntegerField(),
|
base_field=forms.IntegerField(),
|
||||||
help_text=_("Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.")
|
help_text=_("Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.")
|
||||||
)
|
)
|
||||||
user = forms.ModelChoiceField(
|
user = forms.ModelChoiceField(
|
||||||
label=_('User'),
|
queryset=User.objects.order_by(
|
||||||
queryset=get_user_model().objects.order_by(
|
|
||||||
'username'
|
'username'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')),
|
('Reservation', ('rack', 'units', 'user', 'description', 'tags')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -271,7 +258,7 @@ class ManufacturerForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Manufacturer'), (
|
('Manufacturer', (
|
||||||
'name', 'slug', 'description', 'tags',
|
'name', 'slug', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -285,26 +272,23 @@ class ManufacturerForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class DeviceTypeForm(NetBoxModelForm):
|
class DeviceTypeForm(NetBoxModelForm):
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all()
|
queryset=Manufacturer.objects.all()
|
||||||
)
|
)
|
||||||
default_platform = DynamicModelChoiceField(
|
default_platform = DynamicModelChoiceField(
|
||||||
label=_('Default platform'),
|
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField(
|
slug = SlugField(
|
||||||
label=_('Slug'),
|
|
||||||
slug_source='model'
|
slug_source='model'
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
|
('Device Type', ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
|
||||||
(_('Chassis'), (
|
('Chassis', (
|
||||||
'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
|
'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
|
||||||
)),
|
)),
|
||||||
(_('Images'), ('front_image', 'rear_image')),
|
('Images', ('front_image', 'rear_image')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -326,14 +310,13 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class ModuleTypeForm(NetBoxModelForm):
|
class ModuleTypeForm(NetBoxModelForm):
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all()
|
queryset=Manufacturer.objects.all()
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')),
|
('Module Type', ('manufacturer', 'model', 'part_number', 'description', 'tags')),
|
||||||
(_('Weight'), ('weight', 'weight_unit'))
|
('Weight', ('weight', 'weight_unit'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -345,14 +328,13 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class DeviceRoleForm(NetBoxModelForm):
|
class DeviceRoleForm(NetBoxModelForm):
|
||||||
config_template = DynamicModelChoiceField(
|
config_template = DynamicModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device Role'), (
|
('Device Role', (
|
||||||
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
|
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -366,39 +348,39 @@ class DeviceRoleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class PlatformForm(NetBoxModelForm):
|
class PlatformForm(NetBoxModelForm):
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
config_template = DynamicModelChoiceField(
|
config_template = DynamicModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField(
|
slug = SlugField(
|
||||||
label=_('Slug'),
|
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
|
('Platform', (
|
||||||
|
'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags',
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
|
'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags',
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
'napalm_args': forms.Textarea(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceForm(TenancyForm, NetBoxModelForm):
|
class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
location = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -409,7 +391,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack = DynamicModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -418,33 +399,29 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
position = forms.DecimalField(
|
position = forms.DecimalField(
|
||||||
label=_('Position'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("The lowest-numbered unit occupied by the device"),
|
help_text=_("The lowest-numbered unit occupied by the device"),
|
||||||
|
localize=True,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
api_url='/api/dcim/racks/{{rack}}/elevation/',
|
api_url='/api/dcim/racks/{{rack}}/elevation/',
|
||||||
attrs={
|
attrs={
|
||||||
'disabled-indicator': 'device',
|
'disabled-indicator': 'device',
|
||||||
'data-dynamic-params': '[{"fieldName":"face","queryParam":"face"}]'
|
'data-dynamic-params': '[{"fieldName":"face","queryParam":"face"}]'
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
label=_('Device type'),
|
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
role = DynamicModelChoiceField(
|
device_role = DynamicModelChoiceField(
|
||||||
label=_('Device role'),
|
|
||||||
queryset=DeviceRole.objects.all()
|
queryset=DeviceRole.objects.all()
|
||||||
)
|
)
|
||||||
platform = DynamicModelChoiceField(
|
platform = DynamicModelChoiceField(
|
||||||
label=_('Platform'),
|
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cluster = DynamicModelChoiceField(
|
cluster = DynamicModelChoiceField(
|
||||||
label=_('Cluster'),
|
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
selector=True
|
selector=True
|
||||||
@@ -455,7 +432,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
label=''
|
label=''
|
||||||
)
|
)
|
||||||
virtual_chassis = DynamicModelChoiceField(
|
virtual_chassis = DynamicModelChoiceField(
|
||||||
label=_('Virtual chassis'),
|
|
||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
selector=True
|
selector=True
|
||||||
@@ -471,7 +447,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
help_text=_("The priority of the device in the virtual chassis")
|
help_text=_("The priority of the device in the virtual chassis")
|
||||||
)
|
)
|
||||||
config_template = DynamicModelChoiceField(
|
config_template = DynamicModelChoiceField(
|
||||||
label=_('Config template'),
|
|
||||||
queryset=ConfigTemplate.objects.all(),
|
queryset=ConfigTemplate.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -479,10 +454,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
||||||
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster',
|
'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', 'tenant_group', 'tenant',
|
||||||
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
|
'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'tags',
|
||||||
'comments', 'tags', 'local_context_data',
|
'local_context_data'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -491,7 +466,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
|
|
||||||
# Compile list of choices for primary IPv4 and IPv6 addresses
|
# Compile list of choices for primary IPv4 and IPv6 addresses
|
||||||
oob_ip_choices = [(None, '---------')]
|
|
||||||
for family in [4, 6]:
|
for family in [4, 6]:
|
||||||
ip_choices = [(None, '---------')]
|
ip_choices = [(None, '---------')]
|
||||||
|
|
||||||
@@ -507,7 +481,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
if interface_ips:
|
if interface_ips:
|
||||||
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
|
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
|
||||||
ip_choices.append(('Interface IPs', ip_list))
|
ip_choices.append(('Interface IPs', ip_list))
|
||||||
oob_ip_choices.extend(ip_list)
|
|
||||||
# Collect NAT IPs
|
# Collect NAT IPs
|
||||||
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
|
||||||
address__family=family,
|
address__family=family,
|
||||||
@@ -518,7 +491,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
|
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
|
||||||
ip_choices.append(('NAT IPs', ip_list))
|
ip_choices.append(('NAT IPs', ip_list))
|
||||||
self.fields['primary_ip{}'.format(family)].choices = ip_choices
|
self.fields['primary_ip{}'.format(family)].choices = ip_choices
|
||||||
self.fields['oob_ip'].choices = oob_ip_choices
|
|
||||||
|
|
||||||
# If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
|
# If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
|
||||||
# can be flipped from one face to another.
|
# can be flipped from one face to another.
|
||||||
@@ -538,8 +510,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
self.fields['primary_ip4'].widget.attrs['readonly'] = True
|
self.fields['primary_ip4'].widget.attrs['readonly'] = True
|
||||||
self.fields['primary_ip6'].choices = []
|
self.fields['primary_ip6'].choices = []
|
||||||
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
||||||
self.fields['oob_ip'].choices = []
|
|
||||||
self.fields['oob_ip'].widget.attrs['readonly'] = True
|
|
||||||
|
|
||||||
# Rack position
|
# Rack position
|
||||||
position = self.data.get('position') or self.initial.get('position')
|
position = self.data.get('position') or self.initial.get('position')
|
||||||
@@ -549,41 +519,36 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
initial_params={
|
initial_params={
|
||||||
'modulebays': '$module_bay'
|
'modulebays': '$module_bay'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
module_bay = DynamicModelChoiceField(
|
module_bay = DynamicModelChoiceField(
|
||||||
label=_('Module bay'),
|
|
||||||
queryset=ModuleBay.objects.all(),
|
queryset=ModuleBay.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': '$device'
|
'device_id': '$device'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
module_type = DynamicModelChoiceField(
|
module_type = DynamicModelChoiceField(
|
||||||
label=_('Module type'),
|
|
||||||
queryset=ModuleType.objects.all(),
|
queryset=ModuleType.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
replicate_components = forms.BooleanField(
|
replicate_components = forms.BooleanField(
|
||||||
label=_('Replicate components'),
|
|
||||||
required=False,
|
required=False,
|
||||||
initial=True,
|
initial=True,
|
||||||
help_text=_("Automatically populate components associated with this module type")
|
help_text=_("Automatically populate components associated with this module type")
|
||||||
)
|
)
|
||||||
adopt_components = forms.BooleanField(
|
adopt_components = forms.BooleanField(
|
||||||
label=_('Adopt components'),
|
|
||||||
required=False,
|
required=False,
|
||||||
initial=False,
|
initial=False,
|
||||||
help_text=_("Adopt already existing components")
|
help_text=_("Adopt already existing components")
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
|
('Module', ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
|
||||||
(_('Hardware'), (
|
('Hardware', (
|
||||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -617,19 +582,17 @@ class CableForm(TenancyForm, NetBoxModelForm):
|
|||||||
]
|
]
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'length': {
|
'length': {
|
||||||
'max_value': _('Maximum length is 32767 (any unit)')
|
'max_value': 'Maximum length is 32767 (any unit)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelForm(NetBoxModelForm):
|
class PowerPanelForm(NetBoxModelForm):
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
location = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
label=_('Location'),
|
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -649,14 +612,12 @@ class PowerPanelForm(NetBoxModelForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
class PowerFeedForm(NetBoxModelForm):
|
||||||
power_panel = DynamicModelChoiceField(
|
power_panel = DynamicModelChoiceField(
|
||||||
label=_('Power panel'),
|
|
||||||
queryset=PowerPanel.objects.all(),
|
queryset=PowerPanel.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
rack = DynamicModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
selector=True
|
selector=True
|
||||||
@@ -664,16 +625,15 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
('Power Feed', ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
||||||
(_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fields = [
|
fields = [
|
||||||
'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||||
'max_utilization', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
|
'max_utilization', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -683,7 +643,6 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class VirtualChassisForm(NetBoxModelForm):
|
class VirtualChassisForm(NetBoxModelForm):
|
||||||
master = forms.ModelChoiceField(
|
master = forms.ModelChoiceField(
|
||||||
label=_('Master'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@@ -747,7 +706,6 @@ class DeviceVCMembershipForm(forms.ModelForm):
|
|||||||
|
|
||||||
class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
'virtual_chassis_id': 'null',
|
'virtual_chassis_id': 'null',
|
||||||
@@ -770,7 +728,6 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
|||||||
|
|
||||||
class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
|
class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
label=_('Device type'),
|
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -784,12 +741,10 @@ class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
class ModularComponentTemplateForm(ComponentTemplateForm):
|
class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
label=_('Device type'),
|
|
||||||
queryset=DeviceType.objects.all().all(),
|
queryset=DeviceType.objects.all().all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
module_type = DynamicModelChoiceField(
|
module_type = DynamicModelChoiceField(
|
||||||
label=_('Module type'),
|
|
||||||
queryset=ModuleType.objects.all(),
|
queryset=ModuleType.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -842,7 +797,6 @@ class PowerPortTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
label=_('Power port'),
|
|
||||||
queryset=PowerPortTemplate.objects.all(),
|
queryset=PowerPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -863,7 +817,6 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class InterfaceTemplateForm(ModularComponentTemplateForm):
|
class InterfaceTemplateForm(ModularComponentTemplateForm):
|
||||||
bridge = DynamicModelChoiceField(
|
bridge = DynamicModelChoiceField(
|
||||||
label=_('Bridge'),
|
|
||||||
queryset=InterfaceTemplate.objects.all(),
|
queryset=InterfaceTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -874,20 +827,18 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
('PoE', ('poe_mode', 'poe_type'))
|
||||||
(_('Wireless'), ('rf_role',)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role',
|
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateForm(ModularComponentTemplateForm):
|
class FrontPortTemplateForm(ModularComponentTemplateForm):
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_port = DynamicModelChoiceField(
|
||||||
label=_('Rear port'),
|
|
||||||
queryset=RearPortTemplate.objects.all(),
|
queryset=RearPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -949,7 +900,6 @@ class DeviceBayTemplateForm(ComponentTemplateForm):
|
|||||||
|
|
||||||
class InventoryItemTemplateForm(ComponentTemplateForm):
|
class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=InventoryItemTemplate.objects.all(),
|
queryset=InventoryItemTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -957,12 +907,10 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
role = DynamicModelChoiceField(
|
role = DynamicModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -998,7 +946,6 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||||||
|
|
||||||
class DeviceComponentForm(NetBoxModelForm):
|
class DeviceComponentForm(NetBoxModelForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
@@ -1013,7 +960,6 @@ class DeviceComponentForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class ModularDeviceComponentForm(DeviceComponentForm):
|
class ModularDeviceComponentForm(DeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
module = DynamicModelChoiceField(
|
||||||
label=_('Module'),
|
|
||||||
queryset=Module.objects.all(),
|
queryset=Module.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -1070,7 +1016,6 @@ class PowerPortForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
class PowerOutletForm(ModularDeviceComponentForm):
|
class PowerOutletForm(ModularDeviceComponentForm):
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
label=_('Power port'),
|
|
||||||
queryset=PowerPort.objects.all(),
|
queryset=PowerPort.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -1097,7 +1042,10 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||||||
vdcs = DynamicModelMultipleChoiceField(
|
vdcs = DynamicModelMultipleChoiceField(
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Virtual device contexts'),
|
label='Virtual Device Contexts',
|
||||||
|
initial_params={
|
||||||
|
'interfaces': '$parent',
|
||||||
|
},
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': '$device',
|
'device_id': '$device',
|
||||||
}
|
}
|
||||||
@@ -1175,13 +1123,13 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
('Interface', ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
||||||
(_('Addressing'), ('vrf', 'mac_address', 'wwn')),
|
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
||||||
(_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
('Operation', ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||||
(_('Related Interfaces'), ('parent', 'bridge', 'lag')),
|
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
('PoE', ('poe_mode', 'poe_type')),
|
||||||
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
||||||
(_('Wireless'), (
|
('Wireless', (
|
||||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -1287,7 +1235,6 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
|||||||
|
|
||||||
class InventoryItemForm(DeviceComponentForm):
|
class InventoryItemForm(DeviceComponentForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=InventoryItem.objects.all(),
|
queryset=InventoryItem.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -1295,12 +1242,10 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
role = DynamicModelChoiceField(
|
role = DynamicModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@@ -1364,8 +1309,8 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
||||||
(_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1416,7 +1361,7 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
) if self.cleaned_data[field]
|
) if self.cleaned_data[field]
|
||||||
]
|
]
|
||||||
if len(selected_objects) > 1:
|
if len(selected_objects) > 1:
|
||||||
raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component."))
|
raise forms.ValidationError("An InventoryItem can only be assigned to a single component.")
|
||||||
elif selected_objects:
|
elif selected_objects:
|
||||||
self.instance.component = self.cleaned_data[selected_objects[0]]
|
self.instance.component = self.cleaned_data[selected_objects[0]]
|
||||||
else:
|
else:
|
||||||
@@ -1430,7 +1375,7 @@ class InventoryItemRoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Inventory Item Role'), (
|
('Inventory Item Role', (
|
||||||
'name', 'slug', 'color', 'description', 'tags',
|
'name', 'slug', 'color', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -1444,13 +1389,12 @@ class InventoryItemRoleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
primary_ip4 = DynamicModelChoiceField(
|
primary_ip4 = DynamicModelChoiceField(
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
label=_('Primary IPv4'),
|
label='Primary IPv4',
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': '$device',
|
'device_id': '$device',
|
||||||
@@ -1459,7 +1403,7 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
primary_ip6 = DynamicModelChoiceField(
|
primary_ip6 = DynamicModelChoiceField(
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
label=_('Primary IPv6'),
|
label='Primary IPv6',
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'device_id': '$device',
|
'device_id': '$device',
|
||||||
@@ -1468,8 +1412,8 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
('Virtual Device Context', ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant'))
|
('Tenancy', ('tenant_group', 'tenant'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
@@ -38,11 +38,8 @@ class ComponentCreateForm(forms.Form):
|
|||||||
Subclass this form when facilitating the creation of one or more component or component template objects based on
|
Subclass this form when facilitating the creation of one or more component or component template objects based on
|
||||||
a name pattern.
|
a name pattern.
|
||||||
"""
|
"""
|
||||||
name = ExpandableNameField(
|
name = ExpandableNameField()
|
||||||
label=_('Name'),
|
|
||||||
)
|
|
||||||
label = ExpandableNameField(
|
label = ExpandableNameField(
|
||||||
label=_('Label'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
|
help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
|
||||||
)
|
)
|
||||||
@@ -55,14 +52,16 @@ class ComponentCreateForm(forms.Form):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate that all replication fields generate an equal number of values
|
# 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:
|
for field_name in self.replication_fields:
|
||||||
value_count = len(self.cleaned_data[field_name])
|
value_count = len(self.cleaned_data[field_name])
|
||||||
if self.cleaned_data[field_name] and value_count != pattern_count:
|
if self.cleaned_data[field_name] and value_count != pattern_count:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
field_name: _(
|
field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
|
||||||
"The provided pattern specifies {value_count} values, but {pattern_count} are expected."
|
f'expected.'
|
||||||
).format(value_count=value_count, pattern_count=pattern_count)
|
|
||||||
}, code='label_pattern_mismatch')
|
}, code='label_pattern_mismatch')
|
||||||
|
|
||||||
|
|
||||||
@@ -226,14 +225,12 @@ class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if 'module' in self.fields:
|
if 'module' in self.fields:
|
||||||
self.fields['name'].help_text += _(
|
self.fields['name'].help_text += ' The string <code>{module}</code> will be replaced with the position ' \
|
||||||
"The string <code>{module}</code> will be replaced with the position of the assigned module, if any."
|
'of the assigned module, if any'
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
label=_('Device'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
selector=True,
|
selector=True,
|
||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
@@ -335,7 +332,6 @@ class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm
|
|||||||
|
|
||||||
class VirtualChassisCreateForm(NetBoxModelForm):
|
class VirtualChassisCreateForm(NetBoxModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
label=_('Region'),
|
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
initial_params={
|
initial_params={
|
||||||
@@ -343,7 +339,6 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
site_group = DynamicModelChoiceField(
|
site_group = DynamicModelChoiceField(
|
||||||
label=_('Site group'),
|
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
initial_params={
|
initial_params={
|
||||||
@@ -351,7 +346,6 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
site = DynamicModelChoiceField(
|
site = DynamicModelChoiceField(
|
||||||
label=_('Site'),
|
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -360,7 +354,6 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack = DynamicModelChoiceField(
|
rack = DynamicModelChoiceField(
|
||||||
label=_('Rack'),
|
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None',
|
null_option='None',
|
||||||
@@ -369,7 +362,6 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
members = DynamicModelMultipleChoiceField(
|
members = DynamicModelMultipleChoiceField(
|
||||||
label=_('Members'),
|
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
@@ -378,7 +370,6 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
initial_position = forms.IntegerField(
|
initial_position = forms.IntegerField(
|
||||||
label=_('Initial position'),
|
|
||||||
initial=1,
|
initial=1,
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Position of the first member device. Increases by one for each additional member.')
|
help_text=_('Position of the first member device. Increases by one for each additional member.')
|
||||||
@@ -395,7 +386,7 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
|||||||
|
|
||||||
if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
|
if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'initial_position': _("A position must be specified for the first VC member.")
|
'initial_position': "A position must be specified for the first VC member."
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
|
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
from wireless.choices import WirelessRoleChoices
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConsolePortTemplateImportForm',
|
'ConsolePortTemplateImportForm',
|
||||||
@@ -57,7 +56,6 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
|
|
||||||
class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
||||||
power_port = forms.ModelChoiceField(
|
power_port = forms.ModelChoiceField(
|
||||||
label=_('Power port'),
|
|
||||||
queryset=PowerPortTemplate.objects.all(),
|
queryset=PowerPortTemplate.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
@@ -86,7 +84,6 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
|
|
||||||
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=InterfaceTypeChoices.CHOICES
|
choices=InterfaceTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
poe_mode = forms.ChoiceField(
|
poe_mode = forms.ChoiceField(
|
||||||
@@ -99,27 +96,19 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('PoE type')
|
label=_('PoE type')
|
||||||
)
|
)
|
||||||
rf_role = forms.ChoiceField(
|
|
||||||
choices=WirelessRoleChoices,
|
|
||||||
required=False,
|
|
||||||
label=_('Wireless role')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'poe_mode',
|
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
||||||
'poe_type', 'rf_role'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PortTypeChoices.CHOICES
|
choices=PortTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
rear_port = forms.ModelChoiceField(
|
rear_port = forms.ModelChoiceField(
|
||||||
label=_('Rear port'),
|
|
||||||
queryset=RearPortTemplate.objects.all(),
|
queryset=RearPortTemplate.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
@@ -147,7 +136,6 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
|
|
||||||
class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_('Type'),
|
|
||||||
choices=PortTypeChoices.CHOICES
|
choices=PortTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,18 +166,15 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
|
|
||||||
class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
|
class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
|
||||||
parent = forms.ModelChoiceField(
|
parent = forms.ModelChoiceField(
|
||||||
label=_('Parent'),
|
|
||||||
queryset=InventoryItemTemplate.objects.all(),
|
queryset=InventoryItemTemplate.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
label=_('Role'),
|
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False
|
required=False
|
||||||
|
|||||||
@@ -277,9 +277,6 @@ class InterfaceTemplateType(ComponentTemplateObjectType):
|
|||||||
def resolve_poe_type(self, info):
|
def resolve_poe_type(self, info):
|
||||||
return self.poe_type or None
|
return self.poe_type or None
|
||||||
|
|
||||||
def resolve_rf_role(self, info):
|
|
||||||
return self.rf_role or None
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemType(ComponentObjectType):
|
class InventoryItemType(ComponentObjectType):
|
||||||
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
|
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
|
||||||
|
|||||||
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,19 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0172_larger_power_draw_values'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='platform',
|
|
||||||
name='napalm_args',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='platform',
|
|
||||||
name='napalm_driver',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 4.1.9 on 2023-05-31 22:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0173_remove_napalm_fields'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='latitude',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=8, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='longitude',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 4.1.9 on 2023-05-31 15:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0174_device_latitude_device_longitude'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rack',
|
|
||||||
name='starting_unit',
|
|
||||||
field=models.PositiveSmallIntegerField(default=1),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 4.1.9 on 2023-07-24 20:29
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('ipam', '0066_iprange_mark_utilized'),
|
|
||||||
('dcim', '0174_rack_starting_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='oob_ip',
|
|
||||||
field=models.OneToOneField(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name='+',
|
|
||||||
to='ipam.ipaddress',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
from django.db.models import Count
|
|
||||||
|
|
||||||
import utilities.fields
|
|
||||||
|
|
||||||
|
|
||||||
def recalculate_device_counts(apps, schema_editor):
|
|
||||||
Device = apps.get_model("dcim", "Device")
|
|
||||||
devices = list(Device.objects.all().annotate(
|
|
||||||
_console_port_count=Count('consoleports', distinct=True),
|
|
||||||
_console_server_port_count=Count('consoleserverports', distinct=True),
|
|
||||||
_power_port_count=Count('powerports', distinct=True),
|
|
||||||
_power_outlet_count=Count('poweroutlets', distinct=True),
|
|
||||||
_interface_count=Count('interfaces', distinct=True),
|
|
||||||
_front_port_count=Count('frontports', distinct=True),
|
|
||||||
_rear_port_count=Count('rearports', distinct=True),
|
|
||||||
_device_bay_count=Count('devicebays', distinct=True),
|
|
||||||
_module_bay_count=Count('modulebays', distinct=True),
|
|
||||||
_inventory_item_count=Count('inventoryitems', distinct=True),
|
|
||||||
))
|
|
||||||
|
|
||||||
for device in devices:
|
|
||||||
device.console_port_count = device._console_port_count
|
|
||||||
device.console_server_port_count = device._console_server_port_count
|
|
||||||
device.power_port_count = device._power_port_count
|
|
||||||
device.power_outlet_count = device._power_outlet_count
|
|
||||||
device.interface_count = device._interface_count
|
|
||||||
device.front_port_count = device._front_port_count
|
|
||||||
device.rear_port_count = device._rear_port_count
|
|
||||||
device.device_bay_count = device._device_bay_count
|
|
||||||
device.module_bay_count = device._module_bay_count
|
|
||||||
device.inventory_item_count = device._inventory_item_count
|
|
||||||
|
|
||||||
Device.objects.bulk_update(devices, [
|
|
||||||
'console_port_count',
|
|
||||||
'console_server_port_count',
|
|
||||||
'power_port_count',
|
|
||||||
'power_outlet_count',
|
|
||||||
'interface_count',
|
|
||||||
'front_port_count',
|
|
||||||
'rear_port_count',
|
|
||||||
'device_bay_count',
|
|
||||||
'module_bay_count',
|
|
||||||
'inventory_item_count',
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0175_device_oob_ip'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='console_port_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.ConsolePort'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='console_server_port_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.ConsoleServerPort'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='power_port_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.PowerPort'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='power_outlet_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.PowerOutlet'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='interface_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.Interface'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='front_port_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.FrontPort'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='rear_port_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.RearPort'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='device_bay_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.DeviceBay'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='module_bay_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.ModuleBay'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='device',
|
|
||||||
name='inventory_item_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device', to_model='dcim.InventoryItem'),
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
recalculate_device_counts,
|
|
||||||
reverse_code=migrations.RunPython.noop
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
from django.db.models import Count
|
|
||||||
|
|
||||||
import utilities.fields
|
|
||||||
|
|
||||||
|
|
||||||
def recalculate_devicetype_template_counts(apps, schema_editor):
|
|
||||||
DeviceType = apps.get_model("dcim", "DeviceType")
|
|
||||||
device_types = list(DeviceType.objects.all().annotate(
|
|
||||||
_console_port_template_count=Count('consoleporttemplates', distinct=True),
|
|
||||||
_console_server_port_template_count=Count('consoleserverporttemplates', distinct=True),
|
|
||||||
_power_port_template_count=Count('powerporttemplates', distinct=True),
|
|
||||||
_power_outlet_template_count=Count('poweroutlettemplates', distinct=True),
|
|
||||||
_interface_template_count=Count('interfacetemplates', distinct=True),
|
|
||||||
_front_port_template_count=Count('frontporttemplates', distinct=True),
|
|
||||||
_rear_port_template_count=Count('rearporttemplates', distinct=True),
|
|
||||||
_device_bay_template_count=Count('devicebaytemplates', distinct=True),
|
|
||||||
_module_bay_template_count=Count('modulebaytemplates', distinct=True),
|
|
||||||
_inventory_item_template_count=Count('inventoryitemtemplates', distinct=True),
|
|
||||||
))
|
|
||||||
|
|
||||||
for devicetype in device_types:
|
|
||||||
devicetype.console_port_template_count = devicetype._console_port_template_count
|
|
||||||
devicetype.console_server_port_template_count = devicetype._console_server_port_template_count
|
|
||||||
devicetype.power_port_template_count = devicetype._power_port_template_count
|
|
||||||
devicetype.power_outlet_template_count = devicetype._power_outlet_template_count
|
|
||||||
devicetype.interface_template_count = devicetype._interface_template_count
|
|
||||||
devicetype.front_port_template_count = devicetype._front_port_template_count
|
|
||||||
devicetype.rear_port_template_count = devicetype._rear_port_template_count
|
|
||||||
devicetype.device_bay_template_count = devicetype._device_bay_template_count
|
|
||||||
devicetype.module_bay_template_count = devicetype._module_bay_template_count
|
|
||||||
devicetype.inventory_item_template_count = devicetype._inventory_item_template_count
|
|
||||||
|
|
||||||
DeviceType.objects.bulk_update(device_types, [
|
|
||||||
'console_port_template_count',
|
|
||||||
'console_server_port_template_count',
|
|
||||||
'power_port_template_count',
|
|
||||||
'power_outlet_template_count',
|
|
||||||
'interface_template_count',
|
|
||||||
'front_port_template_count',
|
|
||||||
'rear_port_template_count',
|
|
||||||
'device_bay_template_count',
|
|
||||||
'module_bay_template_count',
|
|
||||||
'inventory_item_template_count',
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0176_device_component_counters'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='console_port_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ConsolePortTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='console_server_port_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ConsoleServerPortTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='power_port_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.PowerPortTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='power_outlet_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.PowerOutletTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='interface_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.InterfaceTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='front_port_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.FrontPortTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='rear_port_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.RearPortTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='device_bay_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.DeviceBayTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='module_bay_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.ModuleBayTemplate'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='devicetype',
|
|
||||||
name='inventory_item_template_count',
|
|
||||||
field=utilities.fields.CounterCacheField(default=0, to_field='device_type', to_model='dcim.InventoryItemTemplate'),
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
recalculate_devicetype_template_counts,
|
|
||||||
reverse_code=migrations.RunPython.noop
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
from django.db.models import Count
|
|
||||||
|
|
||||||
import utilities.fields
|
|
||||||
|
|
||||||
|
|
||||||
def populate_virtualchassis_members(apps, schema_editor):
|
|
||||||
VirtualChassis = apps.get_model('dcim', 'VirtualChassis')
|
|
||||||
|
|
||||||
vcs = list(VirtualChassis.objects.annotate(_member_count=Count('members', distinct=True)))
|
|
||||||
|
|
||||||
for vc in vcs:
|
|
||||||
vc.member_count = vc._member_count
|
|
||||||
|
|
||||||
VirtualChassis.objects.bulk_update(vcs, ['member_count'])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0177_devicetype_component_counters'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='virtualchassis',
|
|
||||||
name='member_count',
|
|
||||||
field=utilities.fields.CounterCacheField(
|
|
||||||
default=0, to_field='virtual_chassis', to_model='dcim.Device'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
code=populate_virtualchassis_members,
|
|
||||||
reverse_code=migrations.RunPython.noop
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.2 on 2023-07-18 07:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0178_virtual_chassis_member_counter'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='interfacetemplate',
|
|
||||||
name='rf_role',
|
|
||||||
field=models.CharField(blank=True, max_length=30),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 4.1.8 on 2023-07-29 11:29
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('tenancy', '0010_tenant_relax_uniqueness'),
|
|
||||||
('dcim', '0179_interfacetemplate_rf_role'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='powerfeed',
|
|
||||||
name='tenant',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='power_feeds', to='tenancy.tenant'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def update_table_configs(apps, schema_editor):
|
|
||||||
"""
|
|
||||||
Replace the `device_role` column in DeviceTable configs with `role`.
|
|
||||||
"""
|
|
||||||
UserConfig = apps.get_model('users', 'UserConfig')
|
|
||||||
|
|
||||||
for table in ('DeviceTable', 'DeviceBayTable'):
|
|
||||||
for config in UserConfig.objects.filter(**{f'data__tables__{table}__columns__contains': 'device_role'}):
|
|
||||||
config.data['tables'][table]['columns'] = [
|
|
||||||
'role' if x == 'device_role' else x
|
|
||||||
for x in config.data['tables'][table]['columns']
|
|
||||||
]
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('dcim', '0180_powerfeed_tenant'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='device',
|
|
||||||
old_name='device_role',
|
|
||||||
new_name='role',
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
code=update_table_configs,
|
|
||||||
reverse_code=migrations.RunPython.noop
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -8,7 +8,6 @@ from django.db import models
|
|||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
@@ -41,13 +40,11 @@ class Cable(PrimaryModel):
|
|||||||
A physical connection between two endpoints.
|
A physical connection between two endpoints.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=CableTypeChoices,
|
choices=CableTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=LinkStatusChoices,
|
choices=LinkStatusChoices,
|
||||||
default=LinkStatusChoices.STATUS_CONNECTED
|
default=LinkStatusChoices.STATUS_CONNECTED
|
||||||
@@ -60,23 +57,19 @@ class Cable(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
label = models.CharField(
|
label = models.CharField(
|
||||||
verbose_name=_('label'),
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
length = models.DecimalField(
|
length = models.DecimalField(
|
||||||
verbose_name=_('length'),
|
|
||||||
max_digits=8,
|
max_digits=8,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
length_unit = models.CharField(
|
length_unit = models.CharField(
|
||||||
verbose_name=_('length unit'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=CableLengthUnitChoices,
|
choices=CableLengthUnitChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -242,7 +235,7 @@ class CableTermination(ChangeLoggedModel):
|
|||||||
cable_end = models.CharField(
|
cable_end = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
choices=CableEndChoices,
|
choices=CableEndChoices,
|
||||||
verbose_name=_('end')
|
verbose_name='End'
|
||||||
)
|
)
|
||||||
termination_type = models.ForeignKey(
|
termination_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
@@ -366,7 +359,6 @@ class CableTermination(ChangeLoggedModel):
|
|||||||
# Circuit terminations
|
# Circuit terminations
|
||||||
elif getattr(self.termination, 'site', None):
|
elif getattr(self.termination, 'site', None):
|
||||||
self._site = self.termination.site
|
self._site = self.termination.site
|
||||||
cache_related_objects.alters_data = True
|
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
def to_objectchange(self, action):
|
||||||
objectchange = super().to_objectchange(action)
|
objectchange = super().to_objectchange(action)
|
||||||
@@ -410,19 +402,15 @@ class CablePath(models.Model):
|
|||||||
`_nodes` retains a flattened list of all nodes within the path to enable simple filtering.
|
`_nodes` retains a flattened list of all nodes within the path to enable simple filtering.
|
||||||
"""
|
"""
|
||||||
path = models.JSONField(
|
path = models.JSONField(
|
||||||
verbose_name=_('path'),
|
|
||||||
default=list
|
default=list
|
||||||
)
|
)
|
||||||
is_active = models.BooleanField(
|
is_active = models.BooleanField(
|
||||||
verbose_name=_('is active'),
|
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
is_complete = models.BooleanField(
|
is_complete = models.BooleanField(
|
||||||
verbose_name=_('is complete'),
|
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
is_split = models.BooleanField(
|
is_split = models.BooleanField(
|
||||||
verbose_name=_('is split'),
|
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
_nodes = PathField()
|
_nodes = PathField()
|
||||||
@@ -649,7 +637,6 @@ class CablePath(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
else:
|
else:
|
||||||
self.delete()
|
self.delete()
|
||||||
retrace.alters_data = True
|
|
||||||
|
|
||||||
def _get_path(self):
|
def _get_path(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
@@ -12,8 +12,6 @@ from netbox.models import ChangeLoggedModel
|
|||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
from utilities.tracking import TrackingModelMixin
|
|
||||||
from wireless.choices import WirelessRoleChoices
|
|
||||||
from .device_components import (
|
from .device_components import (
|
||||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||||
RearPort,
|
RearPort,
|
||||||
@@ -34,18 +32,17 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
|
class ComponentTemplateModel(ChangeLoggedModel):
|
||||||
device_type = models.ForeignKey(
|
device_type = models.ForeignKey(
|
||||||
to='dcim.DeviceType',
|
to='dcim.DeviceType',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='%(class)ss'
|
related_name='%(class)ss'
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=64,
|
max_length=64,
|
||||||
help_text=_(
|
help_text="""
|
||||||
"{module} is accepted as a substitution for the module bay position when attached to a module type."
|
{module} is accepted as a substitution for the module bay position when attached to a module type.
|
||||||
)
|
"""
|
||||||
)
|
)
|
||||||
_name = NaturalOrderingField(
|
_name = NaturalOrderingField(
|
||||||
target_field='name',
|
target_field='name',
|
||||||
@@ -53,13 +50,11 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
label = models.CharField(
|
label = models.CharField(
|
||||||
verbose_name=_('label'),
|
|
||||||
max_length=64,
|
max_length=64,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Physical label')
|
help_text=_("Physical label")
|
||||||
)
|
)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
verbose_name=_('description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
@@ -101,7 +96,7 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
|
|||||||
|
|
||||||
if self.pk is not None and self._original_device_type != self.device_type_id:
|
if self.pk is not None and self._original_device_type != self.device_type_id:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"device_type": _("Component templates cannot be moved to a different device type.")
|
"device_type": "Component templates cannot be moved to a different device type."
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -152,11 +147,11 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
|||||||
# A component template must belong to a DeviceType *or* to a ModuleType
|
# A component template must belong to a DeviceType *or* to a ModuleType
|
||||||
if self.device_type and self.module_type:
|
if self.device_type and self.module_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("A component template cannot be associated with both a device type and a module type.")
|
"A component template cannot be associated with both a device type and a module type."
|
||||||
)
|
)
|
||||||
if not self.device_type and not self.module_type:
|
if not self.device_type and not self.module_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("A component template must be associated with either a device type or a module type.")
|
"A component template must be associated with either a device type or a module type."
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_name(self, module):
|
def resolve_name(self, module):
|
||||||
@@ -175,7 +170,6 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
|
|||||||
A template for a ConsolePort to be created for a new Device.
|
A template for a ConsolePort to be created for a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
@@ -205,7 +199,6 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
|||||||
A template for a ConsoleServerPort to be created for a new Device.
|
A template for a ConsoleServerPort to be created for a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
@@ -220,7 +213,6 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
|||||||
type=self.type,
|
type=self.type,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -236,24 +228,21 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
|||||||
A template for a PowerPort to be created for a new Device.
|
A template for a PowerPort to be created for a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
maximum_draw = models.PositiveIntegerField(
|
maximum_draw = models.PositiveIntegerField(
|
||||||
verbose_name=_('maximum draw'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text=_('Maximum power draw (watts)')
|
help_text=_("Maximum power draw (watts)")
|
||||||
)
|
)
|
||||||
allocated_draw = models.PositiveIntegerField(
|
allocated_draw = models.PositiveIntegerField(
|
||||||
verbose_name=_('allocated draw'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text=_('Allocated power draw (watts)')
|
help_text=_("Allocated power draw (watts)")
|
||||||
)
|
)
|
||||||
|
|
||||||
component_model = PowerPort
|
component_model = PowerPort
|
||||||
@@ -267,7 +256,6 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
|||||||
allocated_draw=self.allocated_draw,
|
allocated_draw=self.allocated_draw,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
@@ -275,7 +263,7 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
|||||||
if self.maximum_draw is not None and self.allocated_draw is not None:
|
if self.maximum_draw is not None and self.allocated_draw is not None:
|
||||||
if self.allocated_draw > self.maximum_draw:
|
if self.allocated_draw > self.maximum_draw:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'allocated_draw': _("Allocated draw cannot exceed the maximum draw ({maximum_draw}W).").format(maximum_draw=self.maximum_draw)
|
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
|
||||||
})
|
})
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
@@ -294,7 +282,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
|||||||
A template for a PowerOutlet to be created for a new Device.
|
A template for a PowerOutlet to be created for a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
@@ -307,11 +294,10 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
|||||||
related_name='poweroutlet_templates'
|
related_name='poweroutlet_templates'
|
||||||
)
|
)
|
||||||
feed_leg = models.CharField(
|
feed_leg = models.CharField(
|
||||||
verbose_name=_('feed leg'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Phase (for three-phase feeds)')
|
help_text=_("Phase (for three-phase feeds)")
|
||||||
)
|
)
|
||||||
|
|
||||||
component_model = PowerOutlet
|
component_model = PowerOutlet
|
||||||
@@ -323,11 +309,11 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
|||||||
if self.power_port:
|
if self.power_port:
|
||||||
if self.device_type and self.power_port.device_type != self.device_type:
|
if self.device_type and self.power_port.device_type != self.device_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Parent power port ({power_port}) must belong to the same device type").format(power_port=self.power_port)
|
f"Parent power port ({self.power_port}) must belong to the same device type"
|
||||||
)
|
)
|
||||||
if self.module_type and self.power_port.module_type != self.module_type:
|
if self.module_type and self.power_port.module_type != self.module_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Parent power port ({power_port}) must belong to the same module type").format(power_port=self.power_port)
|
f"Parent power port ({self.power_port}) must belong to the same module type"
|
||||||
)
|
)
|
||||||
|
|
||||||
def instantiate(self, **kwargs):
|
def instantiate(self, **kwargs):
|
||||||
@@ -344,7 +330,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
|||||||
feed_leg=self.feed_leg,
|
feed_leg=self.feed_leg,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -369,17 +354,15 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfaceTypeChoices
|
choices=InterfaceTypeChoices
|
||||||
)
|
)
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
verbose_name=_('enabled'),
|
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
mgmt_only = models.BooleanField(
|
mgmt_only = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_('management only')
|
verbose_name='Management only'
|
||||||
)
|
)
|
||||||
bridge = models.ForeignKey(
|
bridge = models.ForeignKey(
|
||||||
to='self',
|
to='self',
|
||||||
@@ -387,25 +370,19 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
related_name='bridge_interfaces',
|
related_name='bridge_interfaces',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('bridge interface')
|
verbose_name='Bridge interface'
|
||||||
)
|
)
|
||||||
poe_mode = models.CharField(
|
poe_mode = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfacePoEModeChoices,
|
choices=InterfacePoEModeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('PoE mode')
|
verbose_name='PoE mode'
|
||||||
)
|
)
|
||||||
poe_type = models.CharField(
|
poe_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfacePoETypeChoices,
|
choices=InterfacePoETypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('PoE type')
|
verbose_name='PoE type'
|
||||||
)
|
|
||||||
rf_role = models.CharField(
|
|
||||||
max_length=30,
|
|
||||||
choices=WirelessRoleChoices,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_('wireless role')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
component_model = Interface
|
component_model = Interface
|
||||||
@@ -415,19 +392,14 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
|
|
||||||
if self.bridge:
|
if self.bridge:
|
||||||
if self.pk and self.bridge_id == self.pk:
|
if self.pk and self.bridge_id == self.pk:
|
||||||
raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
|
raise ValidationError({'bridge': "An interface cannot be bridged to itself."})
|
||||||
if self.device_type and self.device_type != self.bridge.device_type:
|
if self.device_type and self.device_type != self.bridge.device_type:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'bridge': _("Bridge interface ({bridge}) must belong to the same device type").format(bridge=self.bridge)
|
'bridge': f"Bridge interface ({self.bridge}) must belong to the same device type"
|
||||||
})
|
})
|
||||||
if self.module_type and self.module_type != self.bridge.module_type:
|
if self.module_type and self.module_type != self.bridge.module_type:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'bridge': _("Bridge interface ({bridge}) must belong to the same module type").format(bridge=self.bridge)
|
'bridge': f"Bridge interface ({self.bridge}) must belong to the same module type"
|
||||||
})
|
|
||||||
|
|
||||||
if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
|
|
||||||
raise ValidationError({
|
|
||||||
'rf_role': "Wireless role may be set only on wireless interfaces."
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def instantiate(self, **kwargs):
|
def instantiate(self, **kwargs):
|
||||||
@@ -439,10 +411,8 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
mgmt_only=self.mgmt_only,
|
mgmt_only=self.mgmt_only,
|
||||||
poe_mode=self.poe_mode,
|
poe_mode=self.poe_mode,
|
||||||
poe_type=self.poe_type,
|
poe_type=self.poe_type,
|
||||||
rf_role=self.rf_role,
|
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -455,7 +425,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
'bridge': self.bridge.name if self.bridge else None,
|
'bridge': self.bridge.name if self.bridge else None,
|
||||||
'poe_mode': self.poe_mode,
|
'poe_mode': self.poe_mode,
|
||||||
'poe_type': self.poe_type,
|
'poe_type': self.poe_type,
|
||||||
'rf_role': self.rf_role,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -464,12 +433,10 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
Template for a pass-through port on the front of a new Device.
|
Template for a pass-through port on the front of a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PortTypeChoices
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
rear_port = models.ForeignKey(
|
rear_port = models.ForeignKey(
|
||||||
@@ -478,7 +445,6 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
related_name='frontport_templates'
|
related_name='frontport_templates'
|
||||||
)
|
)
|
||||||
rear_port_position = models.PositiveSmallIntegerField(
|
rear_port_position = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('rear port position'),
|
|
||||||
default=1,
|
default=1,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(REARPORT_POSITIONS_MIN),
|
MinValueValidator(REARPORT_POSITIONS_MIN),
|
||||||
@@ -512,13 +478,13 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
# Validate rear port assignment
|
# Validate rear port assignment
|
||||||
if self.rear_port.device_type != self.device_type:
|
if self.rear_port.device_type != self.device_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Rear port ({}) must belong to the same device type").format(self.rear_port)
|
"Rear port ({}) must belong to the same device type".format(self.rear_port)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate rear port position assignment
|
# Validate rear port position assignment
|
||||||
if self.rear_port_position > self.rear_port.positions:
|
if self.rear_port_position > self.rear_port.positions:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Invalid rear port position ({}); rear port {} has only {} positions").format(
|
"Invalid rear port position ({}); rear port {} has only {} positions".format(
|
||||||
self.rear_port_position, self.rear_port.name, self.rear_port.positions
|
self.rear_port_position, self.rear_port.name, self.rear_port.positions
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -541,7 +507,6 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
rear_port_position=self.rear_port_position,
|
rear_port_position=self.rear_port_position,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -560,16 +525,13 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
|||||||
Template for a pass-through port on the rear of a new Device.
|
Template for a pass-through port on the rear of a new Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PortTypeChoices
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
positions = models.PositiveSmallIntegerField(
|
positions = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('positions'),
|
|
||||||
default=1,
|
default=1,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(REARPORT_POSITIONS_MIN),
|
MinValueValidator(REARPORT_POSITIONS_MIN),
|
||||||
@@ -588,7 +550,6 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
|||||||
positions=self.positions,
|
positions=self.positions,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -606,7 +567,6 @@ class ModuleBayTemplate(ComponentTemplateModel):
|
|||||||
A template for a ModuleBay to be created for a new parent Device.
|
A template for a ModuleBay to be created for a new parent Device.
|
||||||
"""
|
"""
|
||||||
position = models.CharField(
|
position = models.CharField(
|
||||||
verbose_name=_('position'),
|
|
||||||
max_length=30,
|
max_length=30,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Identifier to reference when renaming installed components')
|
help_text=_('Identifier to reference when renaming installed components')
|
||||||
@@ -621,7 +581,6 @@ class ModuleBayTemplate(ComponentTemplateModel):
|
|||||||
label=self.label,
|
label=self.label,
|
||||||
position=self.position
|
position=self.position
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return {
|
return {
|
||||||
@@ -644,12 +603,11 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|||||||
name=self.name,
|
name=self.name,
|
||||||
label=self.label
|
label=self.label
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT:
|
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Subdevice role of device type ({device_type}) must be set to \"parent\" to allow device bays.").format(device_type=self.device_type)
|
f"Subdevice role of device type ({self.device_type}) must be set to \"parent\" to allow device bays."
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
@@ -704,7 +662,7 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
part_id = models.CharField(
|
part_id = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
verbose_name=_('part ID'),
|
verbose_name='Part ID',
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Manufacturer-assigned part identifier')
|
help_text=_('Manufacturer-assigned part identifier')
|
||||||
)
|
)
|
||||||
@@ -738,4 +696,3 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
|
|||||||
part_id=self.part_id,
|
part_id=self.part_id,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
@@ -19,7 +19,6 @@ from utilities.fields import ColorField, NaturalOrderingField
|
|||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
from utilities.query_functions import CollateAsChar
|
from utilities.query_functions import CollateAsChar
|
||||||
from utilities.tracking import TrackingModelMixin
|
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
from wireless.utils import get_channel_attr
|
from wireless.utils import get_channel_attr
|
||||||
|
|
||||||
@@ -52,7 +51,6 @@ class ComponentModel(NetBoxModel):
|
|||||||
related_name='%(class)ss'
|
related_name='%(class)ss'
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
_name = NaturalOrderingField(
|
_name = NaturalOrderingField(
|
||||||
@@ -61,13 +59,11 @@ class ComponentModel(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
label = models.CharField(
|
label = models.CharField(
|
||||||
verbose_name=_('label'),
|
|
||||||
max_length=64,
|
max_length=64,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Physical label')
|
help_text=_("Physical label")
|
||||||
)
|
)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
verbose_name=_('description'),
|
|
||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
@@ -104,7 +100,7 @@ class ComponentModel(NetBoxModel):
|
|||||||
# Check list of Modules that allow device field to be changed
|
# Check list of Modules that allow device field to be changed
|
||||||
if (type(self) not in [InventoryItem]) and (self.pk is not None) and (self._original_device != self.device_id):
|
if (type(self) not in [InventoryItem]) and (self.pk is not None) and (self._original_device != self.device_id):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"device": _("Components cannot be moved to a different device.")
|
"device": "Components cannot be moved to a different device."
|
||||||
})
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -143,15 +139,13 @@ class CabledObjectModel(models.Model):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
cable_end = models.CharField(
|
cable_end = models.CharField(
|
||||||
verbose_name=_('cable end'),
|
|
||||||
max_length=1,
|
max_length=1,
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=CableEndChoices
|
choices=CableEndChoices
|
||||||
)
|
)
|
||||||
mark_connected = models.BooleanField(
|
mark_connected = models.BooleanField(
|
||||||
verbose_name=_('mark connected'),
|
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('Treat as if a cable is connected')
|
help_text=_("Treat as if a cable is connected")
|
||||||
)
|
)
|
||||||
|
|
||||||
cable_terminations = GenericRelation(
|
cable_terminations = GenericRelation(
|
||||||
@@ -169,15 +163,15 @@ class CabledObjectModel(models.Model):
|
|||||||
|
|
||||||
if self.cable and not self.cable_end:
|
if self.cable and not self.cable_end:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"cable_end": _("Must specify cable end (A or B) when attaching a cable.")
|
"cable_end": "Must specify cable end (A or B) when attaching a cable."
|
||||||
})
|
})
|
||||||
if self.cable_end and not self.cable:
|
if self.cable_end and not self.cable:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"cable_end": _("Cable end must not be set without a cable.")
|
"cable_end": "Cable end must not be set without a cable."
|
||||||
})
|
})
|
||||||
if self.mark_connected and self.cable:
|
if self.mark_connected and self.cable:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"mark_connected": _("Cannot mark as connected with a cable attached.")
|
"mark_connected": "Cannot mark as connected with a cable attached."
|
||||||
})
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -200,9 +194,7 @@ class CabledObjectModel(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_object(self):
|
def parent_object(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(f"{self.__class__.__name__} models must declare a parent_object property")
|
||||||
_("{class_name} models must declare a parent_object property").format(class_name=self.__class__.__name__)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opposite_cable_end(self):
|
def opposite_cable_end(self):
|
||||||
@@ -277,19 +269,17 @@ class PathEndpoint(models.Model):
|
|||||||
# Console components
|
# Console components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, TrackingModelMixin):
|
class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Physical port type')
|
help_text=_('Physical port type')
|
||||||
)
|
)
|
||||||
speed = models.PositiveIntegerField(
|
speed = models.PositiveIntegerField(
|
||||||
verbose_name=_('speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -302,19 +292,17 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
|
|||||||
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
|
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, TrackingModelMixin):
|
class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Physical port type')
|
help_text=_('Physical port type')
|
||||||
)
|
)
|
||||||
speed = models.PositiveIntegerField(
|
speed = models.PositiveIntegerField(
|
||||||
verbose_name=_('speed'),
|
|
||||||
choices=ConsolePortSpeedChoices,
|
choices=ConsolePortSpeedChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -331,30 +319,27 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint,
|
|||||||
# Power components
|
# Power components
|
||||||
#
|
#
|
||||||
|
|
||||||
class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, TrackingModelMixin):
|
class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Physical port type')
|
help_text=_('Physical port type')
|
||||||
)
|
)
|
||||||
maximum_draw = models.PositiveIntegerField(
|
maximum_draw = models.PositiveIntegerField(
|
||||||
verbose_name=_('maximum draw'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text=_("Maximum power draw (watts)")
|
help_text=_("Maximum power draw (watts)")
|
||||||
)
|
)
|
||||||
allocated_draw = models.PositiveIntegerField(
|
allocated_draw = models.PositiveIntegerField(
|
||||||
verbose_name=_('allocated draw'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
help_text=_('Allocated power draw (watts)')
|
help_text=_("Allocated power draw (watts)")
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
|
clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
|
||||||
@@ -368,9 +353,7 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
|
|||||||
if self.maximum_draw is not None and self.allocated_draw is not None:
|
if self.maximum_draw is not None and self.allocated_draw is not None:
|
||||||
if self.allocated_draw > self.maximum_draw:
|
if self.allocated_draw > self.maximum_draw:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'allocated_draw': _(
|
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
|
||||||
"Allocated draw cannot exceed the maximum draw ({maximum_draw}W)."
|
|
||||||
).format(maximum_draw=self.maximum_draw)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_downstream_powerports(self, leg=None):
|
def get_downstream_powerports(self, leg=None):
|
||||||
@@ -445,12 +428,11 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, TrackingModelMixin):
|
class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -464,11 +446,10 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
|
|||||||
related_name='poweroutlets'
|
related_name='poweroutlets'
|
||||||
)
|
)
|
||||||
feed_leg = models.CharField(
|
feed_leg = models.CharField(
|
||||||
verbose_name=_('feed leg'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Phase (for three-phase feeds)')
|
help_text=_("Phase (for three-phase feeds)")
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
|
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
|
||||||
@@ -481,9 +462,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
|
|||||||
|
|
||||||
# Validate power port assignment
|
# Validate power port assignment
|
||||||
if self.power_port and self.power_port.device != self.device:
|
if self.power_port and self.power_port.device != self.device:
|
||||||
raise ValidationError(
|
raise ValidationError(f"Parent power port ({self.power_port}) must belong to the same device")
|
||||||
_("Parent power port ({power_port}) must belong to the same device").format(power_port=self.power_port)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -495,13 +474,12 @@ class BaseInterface(models.Model):
|
|||||||
Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
|
Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
|
||||||
"""
|
"""
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
verbose_name=_('enabled'),
|
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
mac_address = MACAddressField(
|
mac_address = MACAddressField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('MAC address')
|
verbose_name='MAC Address'
|
||||||
)
|
)
|
||||||
mtu = models.PositiveIntegerField(
|
mtu = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -510,14 +488,13 @@ class BaseInterface(models.Model):
|
|||||||
MinValueValidator(INTERFACE_MTU_MIN),
|
MinValueValidator(INTERFACE_MTU_MIN),
|
||||||
MaxValueValidator(INTERFACE_MTU_MAX)
|
MaxValueValidator(INTERFACE_MTU_MAX)
|
||||||
],
|
],
|
||||||
verbose_name=_('MTU')
|
verbose_name='MTU'
|
||||||
)
|
)
|
||||||
mode = models.CharField(
|
mode = models.CharField(
|
||||||
verbose_name=_('mode'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfaceModeChoices,
|
choices=InterfaceModeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('IEEE 802.1Q tagging strategy')
|
help_text=_("IEEE 802.1Q tagging strategy")
|
||||||
)
|
)
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
to='self',
|
to='self',
|
||||||
@@ -525,7 +502,7 @@ class BaseInterface(models.Model):
|
|||||||
related_name='child_interfaces',
|
related_name='child_interfaces',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('parent interface')
|
verbose_name='Parent interface'
|
||||||
)
|
)
|
||||||
bridge = models.ForeignKey(
|
bridge = models.ForeignKey(
|
||||||
to='self',
|
to='self',
|
||||||
@@ -533,7 +510,7 @@ class BaseInterface(models.Model):
|
|||||||
related_name='bridge_interfaces',
|
related_name='bridge_interfaces',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('bridge interface')
|
verbose_name='Bridge interface'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -560,7 +537,7 @@ class BaseInterface(models.Model):
|
|||||||
return self.fhrp_group_assignments.count()
|
return self.fhrp_group_assignments.count()
|
||||||
|
|
||||||
|
|
||||||
class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin):
|
class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
||||||
"""
|
"""
|
||||||
@@ -581,25 +558,23 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
related_name='member_interfaces',
|
related_name='member_interfaces',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('parent LAG')
|
verbose_name='Parent LAG'
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfaceTypeChoices
|
choices=InterfaceTypeChoices
|
||||||
)
|
)
|
||||||
mgmt_only = models.BooleanField(
|
mgmt_only = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_('management only'),
|
verbose_name='Management only',
|
||||||
help_text=_('This interface is used only for out-of-band management')
|
help_text=_('This interface is used only for out-of-band management')
|
||||||
)
|
)
|
||||||
speed = models.PositiveIntegerField(
|
speed = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('speed (Kbps)')
|
verbose_name='Speed (Kbps)'
|
||||||
)
|
)
|
||||||
duplex = models.CharField(
|
duplex = models.CharField(
|
||||||
verbose_name=_('duplex'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -608,27 +583,27 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
wwn = WWNField(
|
wwn = WWNField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('WWN'),
|
verbose_name='WWN',
|
||||||
help_text=_('64-bit World Wide Name')
|
help_text=_('64-bit World Wide Name')
|
||||||
)
|
)
|
||||||
rf_role = models.CharField(
|
rf_role = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=WirelessRoleChoices,
|
choices=WirelessRoleChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('wireless role')
|
verbose_name='Wireless role'
|
||||||
)
|
)
|
||||||
rf_channel = models.CharField(
|
rf_channel = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=WirelessChannelChoices,
|
choices=WirelessChannelChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('wireless channel')
|
verbose_name='Wireless channel'
|
||||||
)
|
)
|
||||||
rf_channel_frequency = models.DecimalField(
|
rf_channel_frequency = models.DecimalField(
|
||||||
max_digits=7,
|
max_digits=7,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('channel frequency (MHz)'),
|
verbose_name='Channel frequency (MHz)',
|
||||||
help_text=_("Populated by selected channel (if set)")
|
help_text=_("Populated by selected channel (if set)")
|
||||||
)
|
)
|
||||||
rf_channel_width = models.DecimalField(
|
rf_channel_width = models.DecimalField(
|
||||||
@@ -636,26 +611,26 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
decimal_places=3,
|
decimal_places=3,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=('channel width (MHz)'),
|
verbose_name='Channel width (MHz)',
|
||||||
help_text=_("Populated by selected channel (if set)")
|
help_text=_("Populated by selected channel (if set)")
|
||||||
)
|
)
|
||||||
tx_power = models.PositiveSmallIntegerField(
|
tx_power = models.PositiveSmallIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=(MaxValueValidator(127),),
|
validators=(MaxValueValidator(127),),
|
||||||
verbose_name=_('transmit power (dBm)')
|
verbose_name='Transmit power (dBm)'
|
||||||
)
|
)
|
||||||
poe_mode = models.CharField(
|
poe_mode = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfacePoEModeChoices,
|
choices=InterfacePoEModeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('PoE mode')
|
verbose_name='PoE mode'
|
||||||
)
|
)
|
||||||
poe_type = models.CharField(
|
poe_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfacePoETypeChoices,
|
choices=InterfacePoETypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('PoE type')
|
verbose_name='PoE type'
|
||||||
)
|
)
|
||||||
wireless_link = models.ForeignKey(
|
wireless_link = models.ForeignKey(
|
||||||
to='wireless.WirelessLink',
|
to='wireless.WirelessLink',
|
||||||
@@ -668,7 +643,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
to='wireless.WirelessLAN',
|
to='wireless.WirelessLAN',
|
||||||
related_name='interfaces',
|
related_name='interfaces',
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('wireless LANs')
|
verbose_name='Wireless LANs'
|
||||||
)
|
)
|
||||||
untagged_vlan = models.ForeignKey(
|
untagged_vlan = models.ForeignKey(
|
||||||
to='ipam.VLAN',
|
to='ipam.VLAN',
|
||||||
@@ -676,13 +651,13 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
related_name='interfaces_as_untagged',
|
related_name='interfaces_as_untagged',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('untagged VLAN')
|
verbose_name='Untagged VLAN'
|
||||||
)
|
)
|
||||||
tagged_vlans = models.ManyToManyField(
|
tagged_vlans = models.ManyToManyField(
|
||||||
to='ipam.VLAN',
|
to='ipam.VLAN',
|
||||||
related_name='interfaces_as_tagged',
|
related_name='interfaces_as_tagged',
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('tagged VLANs')
|
verbose_name='Tagged VLANs'
|
||||||
)
|
)
|
||||||
vrf = models.ForeignKey(
|
vrf = models.ForeignKey(
|
||||||
to='ipam.VRF',
|
to='ipam.VRF',
|
||||||
@@ -690,7 +665,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
related_name='interfaces',
|
related_name='interfaces',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('VRF')
|
verbose_name='VRF'
|
||||||
)
|
)
|
||||||
ip_addresses = GenericRelation(
|
ip_addresses = GenericRelation(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
@@ -728,98 +703,77 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
# Virtual Interfaces cannot have a Cable attached
|
# Virtual Interfaces cannot have a Cable attached
|
||||||
if self.is_virtual and self.cable:
|
if self.is_virtual and self.cable:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'type': _("{display_type} interfaces cannot have a cable attached.").format(
|
'type': f"{self.get_type_display()} interfaces cannot have a cable attached."
|
||||||
display_type=self.get_type_display()
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Virtual Interfaces cannot be marked as connected
|
# Virtual Interfaces cannot be marked as connected
|
||||||
if self.is_virtual and self.mark_connected:
|
if self.is_virtual and self.mark_connected:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'mark_connected': _("{display_type} interfaces cannot be marked as connected.".format(
|
'mark_connected': f"{self.get_type_display()} interfaces cannot be marked as connected."
|
||||||
display_type=self.get_type_display())
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Parent validation
|
# Parent validation
|
||||||
|
|
||||||
# An interface cannot be its own parent
|
# An interface cannot be its own parent
|
||||||
if self.pk and self.parent_id == self.pk:
|
if self.pk and self.parent_id == self.pk:
|
||||||
raise ValidationError({'parent': _("An interface cannot be its own parent.")})
|
raise ValidationError({'parent': "An interface cannot be its own parent."})
|
||||||
|
|
||||||
# A physical interface cannot have a parent interface
|
# A physical interface cannot have a parent interface
|
||||||
if self.type != InterfaceTypeChoices.TYPE_VIRTUAL and self.parent is not None:
|
if self.type != InterfaceTypeChoices.TYPE_VIRTUAL and self.parent is not None:
|
||||||
raise ValidationError({'parent': _("Only virtual interfaces may be assigned to a parent interface.")})
|
raise ValidationError({'parent': "Only virtual interfaces may be assigned to a parent interface."})
|
||||||
|
|
||||||
# An interface's parent must belong to the same device or virtual chassis
|
# An interface's parent must belong to the same device or virtual chassis
|
||||||
if self.parent and self.parent.device != self.device:
|
if self.parent and self.parent.device != self.device:
|
||||||
if self.device.virtual_chassis is None:
|
if self.device.virtual_chassis is None:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'parent': _(
|
'parent': f"The selected parent interface ({self.parent}) belongs to a different device "
|
||||||
"The selected parent interface ({interface}) belongs to a different device ({device})"
|
f"({self.parent.device})."
|
||||||
).format(interface=self.parent, device=self.parent.device)
|
|
||||||
})
|
})
|
||||||
elif self.parent.device.virtual_chassis != self.parent.virtual_chassis:
|
elif self.parent.device.virtual_chassis != self.parent.virtual_chassis:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'parent': _(
|
'parent': f"The selected parent interface ({self.parent}) belongs to {self.parent.device}, which "
|
||||||
"The selected parent interface ({interface}) belongs to {device}, which is not part of "
|
f"is not part of virtual chassis {self.device.virtual_chassis}."
|
||||||
"virtual chassis {virtual_chassis}."
|
|
||||||
).format(
|
|
||||||
interface=self.parent,
|
|
||||||
device=self.parent_device,
|
|
||||||
virtual_chassis=self.device.virtual_chassis
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Bridge validation
|
# Bridge validation
|
||||||
|
|
||||||
# An interface cannot be bridged to itself
|
# An interface cannot be bridged to itself
|
||||||
if self.pk and self.bridge_id == self.pk:
|
if self.pk and self.bridge_id == self.pk:
|
||||||
raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
|
raise ValidationError({'bridge': "An interface cannot be bridged to itself."})
|
||||||
|
|
||||||
# A bridged interface belong to the same device or virtual chassis
|
# A bridged interface belong to the same device or virtual chassis
|
||||||
if self.bridge and self.bridge.device != self.device:
|
if self.bridge and self.bridge.device != self.device:
|
||||||
if self.device.virtual_chassis is None:
|
if self.device.virtual_chassis is None:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'bridge': _("""
|
'bridge': f"The selected bridge interface ({self.bridge}) belongs to a different device "
|
||||||
The selected bridge interface ({bridge}) belongs to a different device
|
f"({self.bridge.device})."
|
||||||
({device}).""").format(bridge=self.bridge, device=self.bridge.device)
|
|
||||||
})
|
})
|
||||||
elif self.bridge.device.virtual_chassis != self.device.virtual_chassis:
|
elif self.bridge.device.virtual_chassis != self.device.virtual_chassis:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'bridge': _(
|
'bridge': f"The selected bridge interface ({self.bridge}) belongs to {self.bridge.device}, which "
|
||||||
"The selected bridge interface ({interface}) belongs to {device}, which is not part of virtual "
|
f"is not part of virtual chassis {self.device.virtual_chassis}."
|
||||||
"chassis {virtual_chassis}."
|
|
||||||
).format(
|
|
||||||
interface=self.bridge, device=self.bridge.device, virtual_chassis=self.device.virtual_chassis
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# LAG validation
|
# LAG validation
|
||||||
|
|
||||||
# A virtual interface cannot have a parent LAG
|
# A virtual interface cannot have a parent LAG
|
||||||
if self.type == InterfaceTypeChoices.TYPE_VIRTUAL and self.lag is not None:
|
if self.type == InterfaceTypeChoices.TYPE_VIRTUAL and self.lag is not None:
|
||||||
raise ValidationError({'lag': _("Virtual interfaces cannot have a parent LAG interface.")})
|
raise ValidationError({'lag': "Virtual interfaces cannot have a parent LAG interface."})
|
||||||
|
|
||||||
# A LAG interface cannot be its own parent
|
# A LAG interface cannot be its own parent
|
||||||
if self.pk and self.lag_id == self.pk:
|
if self.pk and self.lag_id == self.pk:
|
||||||
raise ValidationError({'lag': _("A LAG interface cannot be its own parent.")})
|
raise ValidationError({'lag': "A LAG interface cannot be its own parent."})
|
||||||
|
|
||||||
# An interface's LAG must belong to the same device or virtual chassis
|
# An interface's LAG must belong to the same device or virtual chassis
|
||||||
if self.lag and self.lag.device != self.device:
|
if self.lag and self.lag.device != self.device:
|
||||||
if self.device.virtual_chassis is None:
|
if self.device.virtual_chassis is None:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'lag': _(
|
'lag': f"The selected LAG interface ({self.lag}) belongs to a different device ({self.lag.device})."
|
||||||
"The selected LAG interface ({lag}) belongs to a different device ({device})."
|
|
||||||
).format(lag=self.lag, device=self.lag.device)
|
|
||||||
})
|
})
|
||||||
elif self.lag.device.virtual_chassis != self.device.virtual_chassis:
|
elif self.lag.device.virtual_chassis != self.device.virtual_chassis:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'lag': _(
|
'lag': f"The selected LAG interface ({self.lag}) belongs to {self.lag.device}, which is not part "
|
||||||
"The selected LAG interface ({lag}) belongs to {device}, which is not part of virtual chassis "
|
f"of virtual chassis {self.device.virtual_chassis}."
|
||||||
"{virtual_chassis}.".format(
|
|
||||||
lag=self.lag, device=self.lag.device, virtual_chassis=self.device.virtual_chassis)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# PoE validation
|
# PoE validation
|
||||||
@@ -827,54 +781,52 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
# Only physical interfaces may have a PoE mode/type assigned
|
# Only physical interfaces may have a PoE mode/type assigned
|
||||||
if self.poe_mode and self.is_virtual:
|
if self.poe_mode and self.is_virtual:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'poe_mode': _("Virtual interfaces cannot have a PoE mode.")
|
'poe_mode': "Virtual interfaces cannot have a PoE mode."
|
||||||
})
|
})
|
||||||
if self.poe_type and self.is_virtual:
|
if self.poe_type and self.is_virtual:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'poe_type': _("Virtual interfaces cannot have a PoE type.")
|
'poe_type': "Virtual interfaces cannot have a PoE type."
|
||||||
})
|
})
|
||||||
|
|
||||||
# An interface with a PoE type set must also specify a mode
|
# An interface with a PoE type set must also specify a mode
|
||||||
if self.poe_type and not self.poe_mode:
|
if self.poe_type and not self.poe_mode:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'poe_type': _("Must specify PoE mode when designating a PoE type.")
|
'poe_type': "Must specify PoE mode when designating a PoE type."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Wireless validation
|
# Wireless validation
|
||||||
|
|
||||||
# RF role & channel may only be set for wireless interfaces
|
# RF role & channel may only be set for wireless interfaces
|
||||||
if self.rf_role and not self.is_wireless:
|
if self.rf_role and not self.is_wireless:
|
||||||
raise ValidationError({'rf_role': _("Wireless role may be set only on wireless interfaces.")})
|
raise ValidationError({'rf_role': "Wireless role may be set only on wireless interfaces."})
|
||||||
if self.rf_channel and not self.is_wireless:
|
if self.rf_channel and not self.is_wireless:
|
||||||
raise ValidationError({'rf_channel': _("Channel may be set only on wireless interfaces.")})
|
raise ValidationError({'rf_channel': "Channel may be set only on wireless interfaces."})
|
||||||
|
|
||||||
# Validate channel frequency against interface type and selected channel (if any)
|
# Validate channel frequency against interface type and selected channel (if any)
|
||||||
if self.rf_channel_frequency:
|
if self.rf_channel_frequency:
|
||||||
if not self.is_wireless:
|
if not self.is_wireless:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'rf_channel_frequency': _("Channel frequency may be set only on wireless interfaces."),
|
'rf_channel_frequency': "Channel frequency may be set only on wireless interfaces.",
|
||||||
})
|
})
|
||||||
if self.rf_channel and self.rf_channel_frequency != get_channel_attr(self.rf_channel, 'frequency'):
|
if self.rf_channel and self.rf_channel_frequency != get_channel_attr(self.rf_channel, 'frequency'):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'rf_channel_frequency': _("Cannot specify custom frequency with channel selected."),
|
'rf_channel_frequency': "Cannot specify custom frequency with channel selected.",
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate channel width against interface type and selected channel (if any)
|
# Validate channel width against interface type and selected channel (if any)
|
||||||
if self.rf_channel_width:
|
if self.rf_channel_width:
|
||||||
if not self.is_wireless:
|
if not self.is_wireless:
|
||||||
raise ValidationError({'rf_channel_width': _("Channel width may be set only on wireless interfaces.")})
|
raise ValidationError({'rf_channel_width': "Channel width may be set only on wireless interfaces."})
|
||||||
if self.rf_channel and self.rf_channel_width != get_channel_attr(self.rf_channel, 'width'):
|
if self.rf_channel and self.rf_channel_width != get_channel_attr(self.rf_channel, 'width'):
|
||||||
raise ValidationError({'rf_channel_width': _("Cannot specify custom width with channel selected.")})
|
raise ValidationError({'rf_channel_width': "Cannot specify custom width with channel selected."})
|
||||||
|
|
||||||
# VLAN validation
|
# VLAN validation
|
||||||
|
|
||||||
# Validate untagged VLAN
|
# Validate untagged VLAN
|
||||||
if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]:
|
if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'untagged_vlan': _("""
|
'untagged_vlan': f"The untagged VLAN ({self.untagged_vlan}) must belong to the same site as the "
|
||||||
The untagged VLAN ({untagged_vlan}) must belong to the same site as the
|
f"interface's parent device, or it must be global."
|
||||||
interface's parent device, or it must be global.
|
|
||||||
""").format(untagged_vlan=self.untagged_vlan)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -936,17 +888,15 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
# Pass-through ports
|
# Pass-through ports
|
||||||
#
|
#
|
||||||
|
|
||||||
class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
class FrontPort(ModularComponentModel, CabledObjectModel):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the front of a Device.
|
A pass-through port on the front of a Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PortTypeChoices
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
rear_port = models.ForeignKey(
|
rear_port = models.ForeignKey(
|
||||||
@@ -955,7 +905,6 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
|||||||
related_name='frontports'
|
related_name='frontports'
|
||||||
)
|
)
|
||||||
rear_port_position = models.PositiveSmallIntegerField(
|
rear_port_position = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('rear port position'),
|
|
||||||
default=1,
|
default=1,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(REARPORT_POSITIONS_MIN),
|
MinValueValidator(REARPORT_POSITIONS_MIN),
|
||||||
@@ -989,40 +938,29 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
|||||||
# Validate rear port assignment
|
# Validate rear port assignment
|
||||||
if self.rear_port.device != self.device:
|
if self.rear_port.device != self.device:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"rear_port": _(
|
"rear_port": f"Rear port ({self.rear_port}) must belong to the same device"
|
||||||
"Rear port ({rear_port}) must belong to the same device"
|
|
||||||
).format(rear_port=self.rear_port)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate rear port position assignment
|
# Validate rear port position assignment
|
||||||
if self.rear_port_position > self.rear_port.positions:
|
if self.rear_port_position > self.rear_port.positions:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"rear_port_position": _(
|
"rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port "
|
||||||
"Invalid rear port position ({rear_port_position}): Rear port {name} has only {positions} "
|
f"{self.rear_port.name} has only {self.rear_port.positions} positions"
|
||||||
"positions."
|
|
||||||
).format(
|
|
||||||
rear_port_position=self.rear_port_position,
|
|
||||||
name=self.rear_port.name,
|
|
||||||
positions=self.rear_port.positions
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
class RearPort(ModularComponentModel, CabledObjectModel):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the rear of a Device.
|
A pass-through port on the rear of a Device.
|
||||||
"""
|
"""
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PortTypeChoices
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
positions = models.PositiveSmallIntegerField(
|
positions = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('positions'),
|
|
||||||
default=1,
|
default=1,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(REARPORT_POSITIONS_MIN),
|
MinValueValidator(REARPORT_POSITIONS_MIN),
|
||||||
@@ -1043,9 +981,8 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
|||||||
frontport_count = self.frontports.count()
|
frontport_count = self.frontports.count()
|
||||||
if self.positions < frontport_count:
|
if self.positions < frontport_count:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"positions": _("""
|
"positions": f"The number of positions cannot be less than the number of mapped front ports "
|
||||||
The number of positions cannot be less than the number of mapped front ports
|
f"({frontport_count})"
|
||||||
({frontport_count})""").format(frontport_count=frontport_count)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -1053,12 +990,11 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
|
|||||||
# Bays
|
# Bays
|
||||||
#
|
#
|
||||||
|
|
||||||
class ModuleBay(ComponentModel, TrackingModelMixin):
|
class ModuleBay(ComponentModel):
|
||||||
"""
|
"""
|
||||||
An empty space within a Device which can house a child device
|
An empty space within a Device which can house a child device
|
||||||
"""
|
"""
|
||||||
position = models.CharField(
|
position = models.CharField(
|
||||||
verbose_name=_('position'),
|
|
||||||
max_length=30,
|
max_length=30,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Identifier to reference when renaming installed components')
|
help_text=_('Identifier to reference when renaming installed components')
|
||||||
@@ -1070,14 +1006,14 @@ class ModuleBay(ComponentModel, TrackingModelMixin):
|
|||||||
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
|
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class DeviceBay(ComponentModel, TrackingModelMixin):
|
class DeviceBay(ComponentModel):
|
||||||
"""
|
"""
|
||||||
An empty space within a Device which can house a child device
|
An empty space within a Device which can house a child device
|
||||||
"""
|
"""
|
||||||
installed_device = models.OneToOneField(
|
installed_device = models.OneToOneField(
|
||||||
to='dcim.Device',
|
to='dcim.Device',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name=_('parent_bay'),
|
related_name='parent_bay',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
@@ -1092,22 +1028,22 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
|
|||||||
|
|
||||||
# Validate that the parent Device can have DeviceBays
|
# Validate that the parent Device can have DeviceBays
|
||||||
if not self.device.device_type.is_parent_device:
|
if not self.device.device_type.is_parent_device:
|
||||||
raise ValidationError(_("This type of device ({device_type}) does not support device bays.").format(
|
raise ValidationError("This type of device ({}) does not support device bays.".format(
|
||||||
device_type=self.device.device_type
|
self.device.device_type
|
||||||
))
|
))
|
||||||
|
|
||||||
# Cannot install a device into itself, obviously
|
# Cannot install a device into itself, obviously
|
||||||
if self.device == self.installed_device:
|
if self.device == self.installed_device:
|
||||||
raise ValidationError(_("Cannot install a device into itself."))
|
raise ValidationError("Cannot install a device into itself.")
|
||||||
|
|
||||||
# Check that the installed device is not already installed elsewhere
|
# Check that the installed device is not already installed elsewhere
|
||||||
if self.installed_device:
|
if self.installed_device:
|
||||||
current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first()
|
current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first()
|
||||||
if current_bay and current_bay != self:
|
if current_bay and current_bay != self:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'installed_device': _(
|
'installed_device': "Cannot install the specified device; device is already installed in {}".format(
|
||||||
"Cannot install the specified device; device is already installed in {bay}."
|
current_bay
|
||||||
).format(bay=current_bay)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -1121,7 +1057,6 @@ class InventoryItemRole(OrganizationalModel):
|
|||||||
Inventory items may optionally be assigned a functional role.
|
Inventory items may optionally be assigned a functional role.
|
||||||
"""
|
"""
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
default=ColorChoices.COLOR_GREY
|
default=ColorChoices.COLOR_GREY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1129,7 +1064,7 @@ class InventoryItemRole(OrganizationalModel):
|
|||||||
return reverse('dcim:inventoryitemrole', args=[self.pk])
|
return reverse('dcim:inventoryitemrole', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
class InventoryItem(MPTTModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
|
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
|
||||||
InventoryItems are used only for inventory purposes.
|
InventoryItems are used only for inventory purposes.
|
||||||
@@ -1174,13 +1109,13 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
)
|
)
|
||||||
part_id = models.CharField(
|
part_id = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
verbose_name=_('part ID'),
|
verbose_name='Part ID',
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Manufacturer-assigned part identifier')
|
help_text=_('Manufacturer-assigned part identifier')
|
||||||
)
|
)
|
||||||
serial = models.CharField(
|
serial = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
verbose_name=_('serial number'),
|
verbose_name='Serial number',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
asset_tag = models.CharField(
|
asset_tag = models.CharField(
|
||||||
@@ -1188,11 +1123,10 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
unique=True,
|
unique=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('asset tag'),
|
verbose_name='Asset tag',
|
||||||
help_text=_('A unique tag used to identify this item')
|
help_text=_('A unique tag used to identify this item')
|
||||||
)
|
)
|
||||||
discovered = models.BooleanField(
|
discovered = models.BooleanField(
|
||||||
verbose_name=_('discovered'),
|
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('This item was automatically discovered')
|
help_text=_('This item was automatically discovered')
|
||||||
)
|
)
|
||||||
@@ -1219,7 +1153,7 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
# An InventoryItem cannot be its own parent
|
# An InventoryItem cannot be its own parent
|
||||||
if self.pk and self.parent_id == self.pk:
|
if self.pk and self.parent_id == self.pk:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"parent": _("Cannot assign self as parent.")
|
"parent": "Cannot assign self as parent."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validation for moving InventoryItems
|
# Validation for moving InventoryItems
|
||||||
@@ -1227,13 +1161,13 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
# Cannot move an InventoryItem to another device if it has a parent
|
# Cannot move an InventoryItem to another device if it has a parent
|
||||||
if self.parent and self.parent.device != self.device:
|
if self.parent and self.parent.device != self.device:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"parent": _("Parent inventory item does not belong to the same device.")
|
"parent": "Parent inventory item does not belong to the same device."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Prevent moving InventoryItems with children
|
# Prevent moving InventoryItems with children
|
||||||
first_child = self.get_children().first()
|
first_child = self.get_children().first()
|
||||||
if first_child and first_child.device != self.device:
|
if first_child and first_child.device != self.device:
|
||||||
raise ValidationError(_("Cannot move an inventory item with dependent children"))
|
raise ValidationError("Cannot move an inventory item with dependent children")
|
||||||
|
|
||||||
# When moving an InventoryItem to another device, remove any associated component
|
# When moving an InventoryItem to another device, remove any associated component
|
||||||
if self.component and self.component.device != self.device:
|
if self.component and self.component.device != self.device:
|
||||||
@@ -1241,5 +1175,5 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
|
|||||||
else:
|
else:
|
||||||
if self.component and self.component.device != self.device:
|
if self.component and self.component.device != self.device:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"device": _("Cannot assign inventory item to component on another device")
|
"device": "Cannot assign inventory item to component on another device"
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.db.models.functions import Lower
|
|||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
@@ -21,8 +21,7 @@ from extras.querysets import ConfigContextModelQuerySet
|
|||||||
from netbox.config import ConfigItem
|
from netbox.config import ConfigItem
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.tracking import TrackingModelMixin
|
|
||||||
from .device_components import *
|
from .device_components import *
|
||||||
from .mixins import WeightMixin
|
from .mixins import WeightMixin
|
||||||
|
|
||||||
@@ -78,11 +77,9 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
related_name='device_types'
|
related_name='device_types'
|
||||||
)
|
)
|
||||||
model = models.CharField(
|
model = models.CharField(
|
||||||
verbose_name=_('model'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
verbose_name=_('slug'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
default_platform = models.ForeignKey(
|
default_platform = models.ForeignKey(
|
||||||
@@ -91,10 +88,9 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('default platform')
|
verbose_name='Default platform'
|
||||||
)
|
)
|
||||||
part_number = models.CharField(
|
part_number = models.CharField(
|
||||||
verbose_name=_('part number'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Discrete part number (optional)')
|
help_text=_('Discrete part number (optional)')
|
||||||
@@ -103,23 +99,22 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
max_digits=4,
|
max_digits=4,
|
||||||
decimal_places=1,
|
decimal_places=1,
|
||||||
default=1.0,
|
default=1.0,
|
||||||
verbose_name=_('height (U)')
|
verbose_name='Height (U)'
|
||||||
)
|
)
|
||||||
is_full_depth = models.BooleanField(
|
is_full_depth = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
verbose_name=_('is full depth'),
|
verbose_name='Is full depth',
|
||||||
help_text=_('Device consumes both front and rear rack faces')
|
help_text=_('Device consumes both front and rear rack faces')
|
||||||
)
|
)
|
||||||
subdevice_role = models.CharField(
|
subdevice_role = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=SubdeviceRoleChoices,
|
choices=SubdeviceRoleChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('parent/child status'),
|
verbose_name='Parent/child status',
|
||||||
help_text=_('Parent devices house child devices in device bays. Leave blank '
|
help_text=_('Parent devices house child devices in device bays. Leave blank '
|
||||||
'if this device type is neither a parent nor a child.')
|
'if this device type is neither a parent nor a child.')
|
||||||
)
|
)
|
||||||
airflow = models.CharField(
|
airflow = models.CharField(
|
||||||
verbose_name=_('airflow'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=DeviceAirflowChoices,
|
choices=DeviceAirflowChoices,
|
||||||
blank=True
|
blank=True
|
||||||
@@ -133,55 +128,12 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
console_port_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.ConsolePortTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
console_server_port_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.ConsoleServerPortTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
power_port_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.PowerPortTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
power_outlet_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.PowerOutletTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
interface_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.InterfaceTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
front_port_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.FrontPortTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
rear_port_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.RearPortTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
device_bay_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.DeviceBayTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
module_bay_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.ModuleBayTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
inventory_item_template_count = CounterCacheField(
|
|
||||||
to_model='dcim.InventoryItemTemplate',
|
|
||||||
to_field='device_type'
|
|
||||||
)
|
|
||||||
|
|
||||||
images = GenericRelation(
|
images = GenericRelation(
|
||||||
to='extras.ImageAttachment'
|
to='extras.ImageAttachment'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
|
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
||||||
'weight_unit',
|
|
||||||
)
|
)
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'dcim.Manufacturer',
|
'dcim.Manufacturer',
|
||||||
@@ -282,7 +234,7 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
# U height must be divisible by 0.5
|
# U height must be divisible by 0.5
|
||||||
if decimal.Decimal(self.u_height) % decimal.Decimal(0.5):
|
if decimal.Decimal(self.u_height) % decimal.Decimal(0.5):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': _("U height must be in increments of 0.5 rack units.")
|
'u_height': "U height must be in increments of 0.5 rack units."
|
||||||
})
|
})
|
||||||
|
|
||||||
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
|
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
|
||||||
@@ -298,8 +250,8 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
)
|
)
|
||||||
if d.position not in u_available:
|
if d.position not in u_available:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': _("Device {} in rack {} does not have sufficient space to accommodate a height of "
|
'u_height': "Device {} in rack {} does not have sufficient space to accommodate a height of "
|
||||||
"{}U").format(d, d.rack, self.u_height)
|
"{}U".format(d, d.rack, self.u_height)
|
||||||
})
|
})
|
||||||
|
|
||||||
# If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position.
|
# If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position.
|
||||||
@@ -311,23 +263,23 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
if racked_instance_count:
|
if racked_instance_count:
|
||||||
url = f"{reverse('dcim:device_list')}?manufactuer_id={self.manufacturer_id}&device_type_id={self.pk}"
|
url = f"{reverse('dcim:device_list')}?manufactuer_id={self.manufacturer_id}&device_type_id={self.pk}"
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': mark_safe(_(
|
'u_height': mark_safe(
|
||||||
'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already '
|
f'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already '
|
||||||
'mounted within racks.'
|
f'mounted within racks.'
|
||||||
).format(url=url, racked_instance_count=racked_instance_count))
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
|
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
|
||||||
) and self.pk and self.devicebaytemplates.count():
|
) and self.pk and self.devicebaytemplates.count():
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'subdevice_role': _("Must delete all device bay templates associated with this device before "
|
'subdevice_role': "Must delete all device bay templates associated with this device before "
|
||||||
"declassifying it as a parent device.")
|
"declassifying it as a parent device."
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
|
if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': _("Child device types must be 0U.")
|
'u_height': "Child device types must be 0U."
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -372,11 +324,9 @@ class ModuleType(PrimaryModel, WeightMixin):
|
|||||||
related_name='module_types'
|
related_name='module_types'
|
||||||
)
|
)
|
||||||
model = models.CharField(
|
model = models.CharField(
|
||||||
verbose_name=_('model'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
part_number = models.CharField(
|
part_number = models.CharField(
|
||||||
verbose_name=_('part number'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Discrete part number (optional)')
|
help_text=_('Discrete part number (optional)')
|
||||||
@@ -461,12 +411,11 @@ class DeviceRole(OrganizationalModel):
|
|||||||
virtual machines as well.
|
virtual machines as well.
|
||||||
"""
|
"""
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
verbose_name=_('color'),
|
|
||||||
default=ColorChoices.COLOR_GREY
|
default=ColorChoices.COLOR_GREY
|
||||||
)
|
)
|
||||||
vm_role = models.BooleanField(
|
vm_role = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
verbose_name=_('VM role'),
|
verbose_name='VM Role',
|
||||||
help_text=_('Virtual machines may be assigned to this role')
|
help_text=_('Virtual machines may be assigned to this role')
|
||||||
)
|
)
|
||||||
config_template = models.ForeignKey(
|
config_template = models.ForeignKey(
|
||||||
@@ -483,8 +432,9 @@ class DeviceRole(OrganizationalModel):
|
|||||||
|
|
||||||
class Platform(OrganizationalModel):
|
class Platform(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". A
|
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
||||||
Platform may optionally be associated with a particular Manufacturer.
|
NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by
|
||||||
|
specifying a NAPALM driver.
|
||||||
"""
|
"""
|
||||||
manufacturer = models.ForeignKey(
|
manufacturer = models.ForeignKey(
|
||||||
to='dcim.Manufacturer',
|
to='dcim.Manufacturer',
|
||||||
@@ -501,6 +451,18 @@ class Platform(OrganizationalModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
napalm_driver = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
verbose_name='NAPALM driver',
|
||||||
|
help_text=_('The name of the NAPALM driver to use when interacting with devices')
|
||||||
|
)
|
||||||
|
napalm_args = models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name='NAPALM arguments',
|
||||||
|
help_text=_('Additional arguments to pass when initiating the NAPALM driver (JSON format)')
|
||||||
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:platform', args=[self.pk])
|
return reverse('dcim:platform', args=[self.pk])
|
||||||
@@ -520,7 +482,7 @@ def update_interface_bridges(device, interface_templates, module=None):
|
|||||||
interface.save()
|
interface.save()
|
||||||
|
|
||||||
|
|
||||||
class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
class Device(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
||||||
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
||||||
@@ -537,7 +499,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='instances'
|
related_name='instances'
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
device_role = models.ForeignKey(
|
||||||
to='dcim.DeviceRole',
|
to='dcim.DeviceRole',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='devices',
|
related_name='devices',
|
||||||
@@ -558,7 +520,6 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=64,
|
max_length=64,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
@@ -572,7 +533,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
serial = models.CharField(
|
serial = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('serial number'),
|
verbose_name='Serial number',
|
||||||
help_text=_("Chassis serial number, assigned by the manufacturer")
|
help_text=_("Chassis serial number, assigned by the manufacturer")
|
||||||
)
|
)
|
||||||
asset_tag = models.CharField(
|
asset_tag = models.CharField(
|
||||||
@@ -580,7 +541,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('asset tag'),
|
verbose_name='Asset tag',
|
||||||
help_text=_('A unique tag used to identify this device')
|
help_text=_('A unique tag used to identify this device')
|
||||||
)
|
)
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
@@ -608,23 +569,21 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(RACK_U_HEIGHT_MAX + 0.5)],
|
validators=[MinValueValidator(1), MaxValueValidator(RACK_U_HEIGHT_MAX + 0.5)],
|
||||||
verbose_name=_('position (U)'),
|
verbose_name='Position (U)',
|
||||||
help_text=_('The lowest-numbered unit occupied by the device')
|
help_text=_('The lowest-numbered unit occupied by the device')
|
||||||
)
|
)
|
||||||
face = models.CharField(
|
face = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=DeviceFaceChoices,
|
choices=DeviceFaceChoices,
|
||||||
verbose_name=_('rack face')
|
verbose_name='Rack face'
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=DeviceStatusChoices,
|
choices=DeviceStatusChoices,
|
||||||
default=DeviceStatusChoices.STATUS_ACTIVE
|
default=DeviceStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
airflow = models.CharField(
|
airflow = models.CharField(
|
||||||
verbose_name=_('airflow'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=DeviceAirflowChoices,
|
choices=DeviceAirflowChoices,
|
||||||
blank=True
|
blank=True
|
||||||
@@ -635,7 +594,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('primary IPv4')
|
verbose_name='Primary IPv4'
|
||||||
)
|
)
|
||||||
primary_ip6 = models.OneToOneField(
|
primary_ip6 = models.OneToOneField(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
@@ -643,15 +602,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('primary IPv6')
|
verbose_name='Primary IPv6'
|
||||||
)
|
|
||||||
oob_ip = models.OneToOneField(
|
|
||||||
to='ipam.IPAddress',
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='+',
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name=_('out-of-band IP')
|
|
||||||
)
|
)
|
||||||
cluster = models.ForeignKey(
|
cluster = models.ForeignKey(
|
||||||
to='virtualization.Cluster',
|
to='virtualization.Cluster',
|
||||||
@@ -668,14 +619,12 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
vc_position = models.PositiveSmallIntegerField(
|
vc_position = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('VC position'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MaxValueValidator(255)],
|
validators=[MaxValueValidator(255)],
|
||||||
help_text=_('Virtual chassis position')
|
help_text=_('Virtual chassis position')
|
||||||
)
|
)
|
||||||
vc_priority = models.PositiveSmallIntegerField(
|
vc_priority = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('VC priority'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MaxValueValidator(255)],
|
validators=[MaxValueValidator(255)],
|
||||||
@@ -688,64 +637,6 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
latitude = models.DecimalField(
|
|
||||||
verbose_name=_('latitude'),
|
|
||||||
max_digits=8,
|
|
||||||
decimal_places=6,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
|
|
||||||
)
|
|
||||||
longitude = models.DecimalField(
|
|
||||||
verbose_name=_('longitude'),
|
|
||||||
max_digits=9,
|
|
||||||
decimal_places=6,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
console_port_count = CounterCacheField(
|
|
||||||
to_model='dcim.ConsolePort',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
console_server_port_count = CounterCacheField(
|
|
||||||
to_model='dcim.ConsoleServerPort',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
power_port_count = CounterCacheField(
|
|
||||||
to_model='dcim.PowerPort',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
power_outlet_count = CounterCacheField(
|
|
||||||
to_model='dcim.PowerOutlet',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
interface_count = CounterCacheField(
|
|
||||||
to_model='dcim.Interface',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
front_port_count = CounterCacheField(
|
|
||||||
to_model='dcim.FrontPort',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
rear_port_count = CounterCacheField(
|
|
||||||
to_model='dcim.RearPort',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
device_bay_count = CounterCacheField(
|
|
||||||
to_model='dcim.DeviceBay',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
module_bay_count = CounterCacheField(
|
|
||||||
to_model='dcim.ModuleBay',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
inventory_item_count = CounterCacheField(
|
|
||||||
to_model='dcim.InventoryItem',
|
|
||||||
to_field='device'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generic relations
|
# Generic relations
|
||||||
contacts = GenericRelation(
|
contacts = GenericRelation(
|
||||||
@@ -758,7 +649,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
objects = ConfigContextModelQuerySet.as_manager()
|
objects = ConfigContextModelQuerySet.as_manager()
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'device_type', 'role', 'tenant', 'platform', 'site', 'location', 'rack', 'face', 'status', 'airflow',
|
'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'face', 'status', 'airflow',
|
||||||
'cluster', 'virtual_chassis',
|
'cluster', 'virtual_chassis',
|
||||||
)
|
)
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
@@ -778,7 +669,7 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
Lower('name'), 'site',
|
Lower('name'), 'site',
|
||||||
name='%(app_label)s_%(class)s_unique_name_site',
|
name='%(app_label)s_%(class)s_unique_name_site',
|
||||||
condition=Q(tenant__isnull=True),
|
condition=Q(tenant__isnull=True),
|
||||||
violation_error_message=_("Device name must be unique per site.")
|
violation_error_message="Device name must be unique per site."
|
||||||
),
|
),
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=('rack', 'position', 'face'),
|
fields=('rack', 'position', 'face'),
|
||||||
@@ -808,68 +699,48 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:device', args=[self.pk])
|
return reverse('dcim:device', args=[self.pk])
|
||||||
|
|
||||||
@property
|
|
||||||
def device_role(self):
|
|
||||||
"""
|
|
||||||
For backwards compatibility with pre-v3.6 code expecting a device_role to be present on Device.
|
|
||||||
"""
|
|
||||||
return self.role
|
|
||||||
|
|
||||||
@device_role.setter
|
|
||||||
def device_role(self, value):
|
|
||||||
"""
|
|
||||||
For backwards compatibility with pre-v3.6 code expecting a device_role to be present on Device.
|
|
||||||
"""
|
|
||||||
self.role = value
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate site/location/rack combination
|
# Validate site/location/rack combination
|
||||||
if self.rack and self.site != self.rack.site:
|
if self.rack and self.site != self.rack.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'rack': _("Rack {rack} does not belong to site {site}.").format(rack=self.rack, site=self.site),
|
'rack': f"Rack {self.rack} does not belong to site {self.site}.",
|
||||||
})
|
})
|
||||||
if self.location and self.site != self.location.site:
|
if self.location and self.site != self.location.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'location': _(
|
'location': f"Location {self.location} does not belong to site {self.site}.",
|
||||||
"Location {location} does not belong to site {site}."
|
|
||||||
).format(location=self.location, site=self.site)
|
|
||||||
})
|
})
|
||||||
if self.rack and self.location and self.rack.location != self.location:
|
if self.rack and self.location and self.rack.location != self.location:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'rack': _(
|
'rack': f"Rack {self.rack} does not belong to location {self.location}.",
|
||||||
"Rack {rack} does not belong to location {location}."
|
|
||||||
).format(rack=self.rack, location=self.location)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.rack is None:
|
if self.rack is None:
|
||||||
if self.face:
|
if self.face:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'face': _("Cannot select a rack face without assigning a rack."),
|
'face': "Cannot select a rack face without assigning a rack.",
|
||||||
})
|
})
|
||||||
if self.position:
|
if self.position:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'position': _("Cannot select a rack position without assigning a rack."),
|
'position': "Cannot select a rack position without assigning a rack.",
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate rack position and face
|
# Validate rack position and face
|
||||||
if self.position and self.position % decimal.Decimal(0.5):
|
if self.position and self.position % decimal.Decimal(0.5):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'position': _("Position must be in increments of 0.5 rack units.")
|
'position': "Position must be in increments of 0.5 rack units."
|
||||||
})
|
})
|
||||||
if self.position and not self.face:
|
if self.position and not self.face:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'face': _("Must specify rack face when defining rack position."),
|
'face': "Must specify rack face when defining rack position.",
|
||||||
})
|
})
|
||||||
|
|
||||||
# Prevent 0U devices from being assigned to a specific position
|
# Prevent 0U devices from being assigned to a specific position
|
||||||
if hasattr(self, 'device_type'):
|
if hasattr(self, 'device_type'):
|
||||||
if self.position and self.device_type.u_height == 0:
|
if self.position and self.device_type.u_height == 0:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'position': _(
|
'position': f"A U0 device type ({self.device_type}) cannot be assigned to a rack position."
|
||||||
"A U0 device type ({device_type}) cannot be assigned to a rack position."
|
|
||||||
).format(device_type=self.device_type)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.rack:
|
if self.rack:
|
||||||
@@ -878,17 +749,13 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
# Child devices cannot be assigned to a rack face/unit
|
# Child devices cannot be assigned to a rack face/unit
|
||||||
if self.device_type.is_child_device and self.face:
|
if self.device_type.is_child_device and self.face:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'face': _(
|
'face': "Child device types cannot be assigned to a rack face. This is an attribute of the "
|
||||||
"Child device types cannot be assigned to a rack face. This is an attribute of the parent "
|
"parent device."
|
||||||
"device."
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
if self.device_type.is_child_device and self.position:
|
if self.device_type.is_child_device and self.position:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'position': _(
|
'position': "Child device types cannot be assigned to a rack position. This is an attribute of "
|
||||||
"Child device types cannot be assigned to a rack position. This is an attribute of the "
|
"the parent device."
|
||||||
"parent device."
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate rack space
|
# Validate rack space
|
||||||
@@ -899,23 +766,19 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
)
|
)
|
||||||
if self.position and self.position not in available_units:
|
if self.position and self.position not in available_units:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'position': _(
|
'position': f"U{self.position} is already occupied or does not have sufficient space to "
|
||||||
"U{position} is already occupied or does not have sufficient space to accommodate this "
|
f"accommodate this device type: {self.device_type} ({self.device_type.u_height}U)"
|
||||||
"device type: {device_type} ({u_height}U)"
|
|
||||||
).format(
|
|
||||||
position=self.position, device_type=self.device_type, u_height=self.device_type.u_height
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
except DeviceType.DoesNotExist:
|
except DeviceType.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Validate primary & OOB IP addresses
|
# Validate primary IP addresses
|
||||||
vc_interfaces = self.vc_interfaces(if_master=False)
|
vc_interfaces = self.vc_interfaces(if_master=False)
|
||||||
if self.primary_ip4:
|
if self.primary_ip4:
|
||||||
if self.primary_ip4.family != 4:
|
if self.primary_ip4.family != 4:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'primary_ip4': _("{primary_ip4} is not an IPv4 address.").format(primary_ip4=self.primary_ip4)
|
'primary_ip4': f"{self.primary_ip4} is not an IPv4 address."
|
||||||
})
|
})
|
||||||
if self.primary_ip4.assigned_object in vc_interfaces:
|
if self.primary_ip4.assigned_object in vc_interfaces:
|
||||||
pass
|
pass
|
||||||
@@ -923,14 +786,12 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'primary_ip4': _(
|
'primary_ip4': f"The specified IP address ({self.primary_ip4}) is not assigned to this device."
|
||||||
"The specified IP address ({primary_ip4}) is not assigned to this device."
|
|
||||||
).format(primary_ip4=self.primary_ip4)
|
|
||||||
})
|
})
|
||||||
if self.primary_ip6:
|
if self.primary_ip6:
|
||||||
if self.primary_ip6.family != 6:
|
if self.primary_ip6.family != 6:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'primary_ip6': _("{primary_ip6} is not an IPv6 address.").format(primary_ip6=self.primary_ip6m)
|
'primary_ip6': f"{self.primary_ip6} is not an IPv6 address."
|
||||||
})
|
})
|
||||||
if self.primary_ip6.assigned_object in vc_interfaces:
|
if self.primary_ip6.assigned_object in vc_interfaces:
|
||||||
pass
|
pass
|
||||||
@@ -938,43 +799,27 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'primary_ip6': _(
|
'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
|
||||||
"The specified IP address ({primary_ip6}) is not assigned to this device."
|
|
||||||
).format(primary_ip6=self.primary_ip6)
|
|
||||||
})
|
|
||||||
if self.oob_ip:
|
|
||||||
if self.oob_ip.assigned_object in vc_interfaces:
|
|
||||||
pass
|
|
||||||
elif self.oob_ip.nat_inside is not None and self.oob_ip.nat_inside.assigned_object in vc_interfaces:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValidationError({
|
|
||||||
'oob_ip': f"The specified IP address ({self.oob_ip}) is not assigned to this device."
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate manufacturer/platform
|
# Validate manufacturer/platform
|
||||||
if hasattr(self, 'device_type') and self.platform:
|
if hasattr(self, 'device_type') and self.platform:
|
||||||
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
|
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'platform': _(
|
'platform': f"The assigned platform is limited to {self.platform.manufacturer} device types, but "
|
||||||
"The assigned platform is limited to {platform_manufacturer} device types, but this device's "
|
f"this device's type belongs to {self.device_type.manufacturer}."
|
||||||
"type belongs to {device_type_manufacturer}."
|
|
||||||
).format(
|
|
||||||
platform_manufacturer=self.platform.manufacturer,
|
|
||||||
device_type_manufacturer=self.device_type.manufacturer
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# A Device can only be assigned to a Cluster in the same Site (or no Site)
|
# A Device can only be assigned to a Cluster in the same Site (or no Site)
|
||||||
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
|
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'cluster': _("The assigned cluster belongs to a different site ({})").format(self.cluster.site)
|
'cluster': "The assigned cluster belongs to a different site ({})".format(self.cluster.site)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate virtual chassis assignment
|
# Validate virtual chassis assignment
|
||||||
if self.virtual_chassis and self.vc_position is None:
|
if self.virtual_chassis and self.vc_position is None:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'vc_position': _("A device assigned to a virtual chassis must have its position defined.")
|
'vc_position': "A device assigned to a virtual chassis must have its position defined."
|
||||||
})
|
})
|
||||||
|
|
||||||
def _instantiate_components(self, queryset, bulk_create=True):
|
def _instantiate_components(self, queryset, bulk_create=True):
|
||||||
@@ -1077,8 +922,8 @@ class Device(PrimaryModel, ConfigContextModel, TrackingModelMixin):
|
|||||||
"""
|
"""
|
||||||
if self.config_template:
|
if self.config_template:
|
||||||
return self.config_template
|
return self.config_template
|
||||||
if self.role.config_template:
|
if self.device_role.config_template:
|
||||||
return self.role.config_template
|
return self.device_role.config_template
|
||||||
if self.platform and self.platform.config_template:
|
if self.platform and self.platform.config_template:
|
||||||
return self.platform.config_template
|
return self.platform.config_template
|
||||||
|
|
||||||
@@ -1159,7 +1004,6 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
related_name='instances'
|
related_name='instances'
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ModuleStatusChoices,
|
choices=ModuleStatusChoices,
|
||||||
default=ModuleStatusChoices.STATUS_ACTIVE
|
default=ModuleStatusChoices.STATUS_ACTIVE
|
||||||
@@ -1167,14 +1011,14 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
serial = models.CharField(
|
serial = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_('serial number')
|
verbose_name='Serial number'
|
||||||
)
|
)
|
||||||
asset_tag = models.CharField(
|
asset_tag = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('asset tag'),
|
verbose_name='Asset tag',
|
||||||
help_text=_('A unique tag used to identify this device')
|
help_text=_('A unique tag used to identify this device')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1197,9 +1041,7 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
|
|
||||||
if hasattr(self, "module_bay") and (self.module_bay.device != self.device):
|
if hasattr(self, "module_bay") and (self.module_bay.device != self.device):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Module must be installed within a module bay belonging to the assigned device ({device}).").format(
|
f"Module must be installed within a module bay belonging to the assigned device ({self.device})."
|
||||||
device=self.device
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -1297,21 +1139,13 @@ class VirtualChassis(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
domain = models.CharField(
|
domain = models.CharField(
|
||||||
verbose_name=_('domain'),
|
|
||||||
max_length=30,
|
max_length=30,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Counter fields
|
|
||||||
member_count = CounterCacheField(
|
|
||||||
to_model='dcim.Device',
|
|
||||||
to_field='virtual_chassis'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
verbose_name_plural = 'virtual chassis'
|
verbose_name_plural = 'virtual chassis'
|
||||||
@@ -1329,9 +1163,7 @@ class VirtualChassis(PrimaryModel):
|
|||||||
# VirtualChassis.)
|
# VirtualChassis.)
|
||||||
if self.pk and self.master and self.master not in self.members.all():
|
if self.pk and self.master and self.master not in self.members.all():
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'master': _("The selected master ({master}) is not assigned to this virtual chassis.").format(
|
'master': f"The selected master ({self.master}) is not assigned to this virtual chassis."
|
||||||
master=self.master
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
@@ -1344,10 +1176,10 @@ class VirtualChassis(PrimaryModel):
|
|||||||
lag__device=F('device')
|
lag__device=F('device')
|
||||||
)
|
)
|
||||||
if interfaces:
|
if interfaces:
|
||||||
raise ProtectedError(_(
|
raise ProtectedError(
|
||||||
"Unable to delete virtual chassis {self}. There are member interfaces which form a cross-chassis LAG "
|
f"Unable to delete virtual chassis {self}. There are member interfaces which form a cross-chassis LAG",
|
||||||
"interfaces."
|
interfaces
|
||||||
).format(self=self, interfaces=InterfaceSpeedChoices))
|
)
|
||||||
|
|
||||||
return super().delete(*args, **kwargs)
|
return super().delete(*args, **kwargs)
|
||||||
|
|
||||||
@@ -1361,17 +1193,14 @@ class VirtualDeviceContext(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=VirtualDeviceContextStatusChoices,
|
choices=VirtualDeviceContextStatusChoices,
|
||||||
)
|
)
|
||||||
identifier = models.PositiveSmallIntegerField(
|
identifier = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('identifier'),
|
help_text='Numeric identifier unique to the parent device',
|
||||||
help_text=_('Numeric identifier unique to the parent device'),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
@@ -1381,7 +1210,7 @@ class VirtualDeviceContext(PrimaryModel):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('primary IPv4')
|
verbose_name='Primary IPv4'
|
||||||
)
|
)
|
||||||
primary_ip6 = models.OneToOneField(
|
primary_ip6 = models.OneToOneField(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
@@ -1389,7 +1218,7 @@ class VirtualDeviceContext(PrimaryModel):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('primary IPv6')
|
verbose_name='Primary IPv6'
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
@@ -1399,7 +1228,6 @@ class VirtualDeviceContext(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
comments = models.TextField(
|
comments = models.TextField(
|
||||||
verbose_name=_('comments'),
|
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1445,9 +1273,7 @@ class VirtualDeviceContext(PrimaryModel):
|
|||||||
continue
|
continue
|
||||||
if primary_ip.family != family:
|
if primary_ip.family != family:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
f'primary_ip{family}': _(
|
f'primary_ip{family}': f"{primary_ip} is not an IPv{family} address."
|
||||||
"{primary_ip} is not an IPv{family} address."
|
|
||||||
).format(family=family, primary_ip=primary_ip)
|
|
||||||
})
|
})
|
||||||
device_interfaces = self.device.vc_interfaces(if_master=False)
|
device_interfaces = self.device.vc_interfaces(if_master=False)
|
||||||
if primary_ip.assigned_object not in device_interfaces:
|
if primary_ip.assigned_object not in device_interfaces:
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from utilities.utils import to_grams
|
from utilities.utils import to_grams
|
||||||
|
|
||||||
|
|
||||||
class WeightMixin(models.Model):
|
class WeightMixin(models.Model):
|
||||||
weight = models.DecimalField(
|
weight = models.DecimalField(
|
||||||
verbose_name=_('weight'),
|
|
||||||
max_digits=8,
|
max_digits=8,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
weight_unit = models.CharField(
|
weight_unit = models.CharField(
|
||||||
verbose_name=_('weight unit'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=WeightUnitChoices,
|
choices=WeightUnitChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -43,4 +40,4 @@ class WeightMixin(models.Model):
|
|||||||
|
|
||||||
# Validate weight and weight_unit
|
# Validate weight and weight_unit
|
||||||
if self.weight and not self.weight_unit:
|
if self.weight and not self.weight_unit:
|
||||||
raise ValidationError(_("Must specify a unit when setting a weight"))
|
raise ValidationError("Must specify a unit when setting a weight")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from netbox.config import ConfigItem
|
from netbox.config import ConfigItem
|
||||||
@@ -36,7 +36,6 @@ class PowerPanel(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,8 +72,7 @@ class PowerPanel(PrimaryModel):
|
|||||||
# Location must belong to assigned Site
|
# Location must belong to assigned Site
|
||||||
if self.location and self.location.site != self.site:
|
if self.location and self.location.site != self.site:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Location {location} ({location_site}) is in a different site than {site}").format(
|
f"Location {self.location} ({self.location.site}) is in a different site than {self.site}"
|
||||||
location=self.location, location_site=self.location.site, site=self.site)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -94,65 +92,49 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
verbose_name=_('status'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerFeedStatusChoices,
|
choices=PowerFeedStatusChoices,
|
||||||
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerFeedTypeChoices,
|
choices=PowerFeedTypeChoices,
|
||||||
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
supply = models.CharField(
|
supply = models.CharField(
|
||||||
verbose_name=_('supply'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerFeedSupplyChoices,
|
choices=PowerFeedSupplyChoices,
|
||||||
default=PowerFeedSupplyChoices.SUPPLY_AC
|
default=PowerFeedSupplyChoices.SUPPLY_AC
|
||||||
)
|
)
|
||||||
phase = models.CharField(
|
phase = models.CharField(
|
||||||
verbose_name=_('phase'),
|
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerFeedPhaseChoices,
|
choices=PowerFeedPhaseChoices,
|
||||||
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
||||||
)
|
)
|
||||||
voltage = models.SmallIntegerField(
|
voltage = models.SmallIntegerField(
|
||||||
verbose_name=_('voltage'),
|
|
||||||
default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'),
|
default=ConfigItem('POWERFEED_DEFAULT_VOLTAGE'),
|
||||||
validators=[ExclusionValidator([0])]
|
validators=[ExclusionValidator([0])]
|
||||||
)
|
)
|
||||||
amperage = models.PositiveSmallIntegerField(
|
amperage = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('amperage'),
|
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE')
|
default=ConfigItem('POWERFEED_DEFAULT_AMPERAGE')
|
||||||
)
|
)
|
||||||
max_utilization = models.PositiveSmallIntegerField(
|
max_utilization = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_('max utilization'),
|
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
validators=[MinValueValidator(1), MaxValueValidator(100)],
|
||||||
default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
|
default=ConfigItem('POWERFEED_DEFAULT_MAX_UTILIZATION'),
|
||||||
help_text=_("Maximum permissible draw (percentage)")
|
help_text=_("Maximum permissible draw (percentage)")
|
||||||
)
|
)
|
||||||
available_power = models.PositiveIntegerField(
|
available_power = models.PositiveIntegerField(
|
||||||
verbose_name=_('available power'),
|
|
||||||
default=0,
|
default=0,
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
|
||||||
to='tenancy.Tenant',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='power_feeds',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||||
'max_utilization', 'tenant',
|
'max_utilization',
|
||||||
)
|
)
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'dcim.PowerPanel',
|
'dcim.PowerPanel',
|
||||||
@@ -178,14 +160,14 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
|
|||||||
|
|
||||||
# Rack must belong to same Site as PowerPanel
|
# Rack must belong to same Site as PowerPanel
|
||||||
if self.rack and self.rack.site != self.power_panel.site:
|
if self.rack and self.rack.site != self.power_panel.site:
|
||||||
raise ValidationError(_("Rack {} ({}) and power panel {} ({}) are in different sites").format(
|
raise ValidationError("Rack {} ({}) and power panel {} ({}) are in different sites".format(
|
||||||
self.rack, self.rack.site, self.power_panel, self.power_panel.site
|
self.rack, self.rack.site, self.power_panel, self.power_panel.site
|
||||||
))
|
))
|
||||||
|
|
||||||
# AC voltage cannot be negative
|
# AC voltage cannot be negative
|
||||||
if self.voltage < 0 and self.supply == PowerFeedSupplyChoices.SUPPLY_AC:
|
if self.voltage < 0 and self.supply == PowerFeedSupplyChoices.SUPPLY_AC:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
"voltage": _("Voltage cannot be negative for AC supply")
|
"voltage": "Voltage cannot be negative for AC supply"
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user