mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-27 15:47:46 -06:00
Compare commits
654 Commits
v2.10-beta
...
v2.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8ae65a762 | ||
|
|
efea511211 | ||
|
|
2564818c3e | ||
|
|
e6930d9601 | ||
|
|
4e405ce530 | ||
|
|
88ffc9b145 | ||
|
|
9ed76400de | ||
|
|
497e50c559 | ||
|
|
7cf9e202a3 | ||
|
|
620d222f98 | ||
|
|
a2d16143e3 | ||
|
|
97c087ef5f | ||
|
|
6bd4b3c167 | ||
|
|
89350a80ad | ||
|
|
f26227ecec | ||
|
|
c002501c2e | ||
|
|
8af78c6d84 | ||
|
|
7e8ffd8390 | ||
|
|
5bd30060e7 | ||
|
|
c24cac9a44 | ||
|
|
2bc9730b1d | ||
|
|
428858dc75 | ||
|
|
8a425a93ae | ||
|
|
0bb2a051b6 | ||
|
|
ef78a7d41b | ||
|
|
6c1c695616 | ||
|
|
8bdab347b6 | ||
|
|
ae349d4424 | ||
|
|
3e49745257 | ||
|
|
c2b7226205 | ||
|
|
f67369a9a9 | ||
|
|
14d2a49942 | ||
|
|
92df40a6a0 | ||
|
|
a2203da1c6 | ||
|
|
0fb8291b8b | ||
|
|
b393ec2856 | ||
|
|
664ba55460 | ||
|
|
3d0882856f | ||
|
|
e849d28276 | ||
|
|
18a691482d | ||
|
|
7c48e3632d | ||
|
|
8701b20899 | ||
|
|
e3f5062583 | ||
|
|
d427e0522e | ||
|
|
46e144f647 | ||
|
|
6ad20c53d9 | ||
|
|
cc433388f5 | ||
|
|
1fba4b7e32 | ||
|
|
c249cd4ffd | ||
|
|
d54bf5f75e | ||
|
|
9cbe3ff551 | ||
|
|
b1d20d3228 | ||
|
|
e5602abee0 | ||
|
|
a296a9e109 | ||
|
|
e5bbf47ab9 | ||
|
|
9bda2a44ae | ||
|
|
a1d32c3a21 | ||
|
|
b4b68c0b00 | ||
|
|
608bf30bda | ||
|
|
d0be5f09a5 | ||
|
|
3d8a3a2204 | ||
|
|
b19734004a | ||
|
|
f503c72782 | ||
|
|
f8903a8183 | ||
|
|
d43d5a6cb6 | ||
|
|
99f4b2cf95 | ||
|
|
b493d739bd | ||
|
|
92fb43a455 | ||
|
|
7c29fb449e | ||
|
|
7ddcec3a0d | ||
|
|
18f206747c | ||
|
|
0bce1da4e3 | ||
|
|
65ed047084 | ||
|
|
b0573f88e6 | ||
|
|
348fca7e28 | ||
|
|
cc9b750eff | ||
|
|
701ad8a4a9 | ||
|
|
2cc088c633 | ||
|
|
4dfba3a2ad | ||
|
|
a3721a94ce | ||
|
|
7439faad34 | ||
|
|
6efe54aa88 | ||
|
|
9e62d1ad8f | ||
|
|
e69251b21a | ||
|
|
f096c4a5d0 | ||
|
|
03b3f5937f | ||
|
|
696b5c80a7 | ||
|
|
d6fcd22752 | ||
|
|
54d9ca8ed8 | ||
|
|
05d8a06cd5 | ||
|
|
4f7626828a | ||
|
|
81193eb550 | ||
|
|
38b09dc610 | ||
|
|
ae3527df16 | ||
|
|
85d0270af0 | ||
|
|
59e185b781 | ||
|
|
b5ad29e3f2 | ||
|
|
f1e2b99456 | ||
|
|
0635e7ae1e | ||
|
|
8f674aede9 | ||
|
|
838200219f | ||
|
|
e543b305c3 | ||
|
|
d42b0691b2 | ||
|
|
71022d58d3 | ||
|
|
a313b675a6 | ||
|
|
ae18693715 | ||
|
|
3ad7622bf0 | ||
|
|
19cb575b90 | ||
|
|
3cfab25f8a | ||
|
|
85359bb10f | ||
|
|
aa2beb1d78 | ||
|
|
2e07ac5a47 | ||
|
|
d844ee2c79 | ||
|
|
266482649f | ||
|
|
83496c66d1 | ||
|
|
374cb74978 | ||
|
|
f0018dcba9 | ||
|
|
7949a5e1fd | ||
|
|
0a1531ce8a | ||
|
|
7b8bd2d4ce | ||
|
|
4883bc3dd4 | ||
|
|
72a115b434 | ||
|
|
f28edd0864 | ||
|
|
d69ec7f8e4 | ||
|
|
779837389b | ||
|
|
ea9e9d7273 | ||
|
|
237dfce8a0 | ||
|
|
2b0ccf3acd | ||
|
|
e4f3b3447e | ||
|
|
eb8e4f64fc | ||
|
|
6287f75e67 | ||
|
|
d82f2e289a | ||
|
|
73e9842877 | ||
|
|
a86178f19b | ||
|
|
b77c228853 | ||
|
|
5e4432b9ad | ||
|
|
e7f10fdaea | ||
|
|
8713ed5d73 | ||
|
|
cd64fcac8d | ||
|
|
96759af86f | ||
|
|
d57222328b | ||
|
|
83c4577f6d | ||
|
|
7bd853e87b | ||
|
|
861a52d27c | ||
|
|
9df2130e11 | ||
|
|
6242e195be | ||
|
|
f2f0ea8d04 | ||
|
|
b7309d5c69 | ||
|
|
613e0d10b3 | ||
|
|
04fc3a5a9e | ||
|
|
6ec8ac7597 | ||
|
|
9a5f54bdaf | ||
|
|
ad5e167ad7 | ||
|
|
d33d9522cc | ||
|
|
a292ff5cc0 | ||
|
|
b91e5763e2 | ||
|
|
e1e840eb69 | ||
|
|
0eb9f41470 | ||
|
|
34457ea1b4 | ||
|
|
878154c305 | ||
|
|
9ede726eea | ||
|
|
bfa95c16e3 | ||
|
|
7885ec5511 | ||
|
|
be3d33eebd | ||
|
|
c7040fd418 | ||
|
|
eac53a779b | ||
|
|
0986fd1081 | ||
|
|
1e3c7e1a87 | ||
|
|
3869028d53 | ||
|
|
12945704e9 | ||
|
|
cd629fc737 | ||
|
|
b070be1c41 | ||
|
|
8fa37d3ec8 | ||
|
|
3135dc42c3 | ||
|
|
08ef5c9a02 | ||
|
|
b1935ddcab | ||
|
|
93353e94a2 | ||
|
|
30e4504ee5 | ||
|
|
a9716af0fa | ||
|
|
042f3590c8 | ||
|
|
2c9b791b85 | ||
|
|
7396975500 | ||
|
|
fe4bf62793 | ||
|
|
8a8342b106 | ||
|
|
e52702f6c2 | ||
|
|
0fae7504b3 | ||
|
|
ab612c1ca6 | ||
|
|
0364d8cd43 | ||
|
|
1544823d73 | ||
|
|
b793ee3aff | ||
|
|
36c903da04 | ||
|
|
981e7017bb | ||
|
|
2820d26a0f | ||
|
|
b7e44a744d | ||
|
|
bb00f2ff46 | ||
|
|
65d90aa8a3 | ||
|
|
3d286fbdc3 | ||
|
|
c8eae3a5c3 | ||
|
|
afc58e6bff | ||
|
|
f59b5119e5 | ||
|
|
8eca7377a5 | ||
|
|
c828806b67 | ||
|
|
e639de9861 | ||
|
|
9a4d885a9c | ||
|
|
d28cc4fdb5 | ||
|
|
54a979c936 | ||
|
|
d18652f726 | ||
|
|
ac1083f65d | ||
|
|
bcfc2e5f74 | ||
|
|
a135396d7b | ||
|
|
ef987bd008 | ||
|
|
a2fef1df83 | ||
|
|
016c7d4e6f | ||
|
|
0a60bcdb8d | ||
|
|
5374e6c881 | ||
|
|
ab65ab860f | ||
|
|
ad2742dbb2 | ||
|
|
997077fc5d | ||
|
|
c8042ad511 | ||
|
|
b51d66ea72 | ||
|
|
0321ae0a7f | ||
|
|
e4ecd2dae9 | ||
|
|
b9176adca4 | ||
|
|
b6f6293b76 | ||
|
|
d45edcd216 | ||
|
|
89c487de65 | ||
|
|
d45a17247d | ||
|
|
2e97bf48c5 | ||
|
|
872e936924 | ||
|
|
574a43fff7 | ||
|
|
6ff8a267e9 | ||
|
|
335cfcde57 | ||
|
|
433c48a1a3 | ||
|
|
889316085c | ||
|
|
91fe80f73c | ||
|
|
7e437455c1 | ||
|
|
9a68a61ad3 | ||
|
|
7e65a3d3b4 | ||
|
|
82fbd975f1 | ||
|
|
bd95d2b852 | ||
|
|
956e2728c2 | ||
|
|
7f1d9aeaf8 | ||
|
|
f2c079de87 | ||
|
|
8be4fbbce3 | ||
|
|
64d11d32c8 | ||
|
|
1f1a62da67 | ||
|
|
e97adcb614 | ||
|
|
2bb4a81e23 | ||
|
|
46a024441c | ||
|
|
a694dbb020 | ||
|
|
f74c88027e | ||
|
|
c64f96d65b | ||
|
|
fb48c1f6dd | ||
|
|
ee7f7c877a | ||
|
|
f64f205e81 | ||
|
|
10778f8479 | ||
|
|
bb6360cad4 | ||
|
|
c0c4eed3a8 | ||
|
|
f9f1a4439f | ||
|
|
68082a88a7 | ||
|
|
1aa22d1ba8 | ||
|
|
61d23df83a | ||
|
|
fca5accba8 | ||
|
|
947da63d56 | ||
|
|
a94e5c7403 | ||
|
|
c061f7021e | ||
|
|
ad43373f5b | ||
|
|
720b88048d | ||
|
|
d58291d119 | ||
|
|
132b1ff479 | ||
|
|
0115a61ab7 | ||
|
|
fadf15dbc0 | ||
|
|
cb9478e0ea | ||
|
|
04964cc52b | ||
|
|
6ab1c06036 | ||
|
|
d3fee54644 | ||
|
|
f4e49495e2 | ||
|
|
c072ba088f | ||
|
|
4397d3da02 | ||
|
|
fc5bb966f0 | ||
|
|
20f60cd99d | ||
|
|
ac0c54cce4 | ||
|
|
19b78e63ce | ||
|
|
653af17755 | ||
|
|
bac2387f50 | ||
|
|
9990fd25d4 | ||
|
|
e9930854c4 | ||
|
|
11ac4cd2c2 | ||
|
|
5b07c77708 | ||
|
|
c3ad2d0a80 | ||
|
|
4dae781be0 | ||
|
|
1cc82f2ab7 | ||
|
|
c6641ec1de | ||
|
|
c083b862a7 | ||
|
|
6ffadb501b | ||
|
|
38ded66c4e | ||
|
|
358d7ac562 | ||
|
|
cae784ff52 | ||
|
|
835e11d10f | ||
|
|
e7157973e7 | ||
|
|
5b5110acbf | ||
|
|
61e5eff666 | ||
|
|
16a3d1339a | ||
|
|
759190d8ba | ||
|
|
f8e2cb06d1 | ||
|
|
37aa10c5a5 | ||
|
|
ad9ed13b9a | ||
|
|
d9f8d809c8 | ||
|
|
03054b88aa | ||
|
|
a72911b527 | ||
|
|
8e4da3faf4 | ||
|
|
46945aa98d | ||
|
|
939224d0af | ||
|
|
14bc3a3cf8 | ||
|
|
79b19821f6 | ||
|
|
8d15f79f1e | ||
|
|
b079cc12f4 | ||
|
|
1c66733b8a | ||
|
|
2ef85ea195 | ||
|
|
69a3d14b53 | ||
|
|
d74c07e1a2 | ||
|
|
e1a86139dc | ||
|
|
8e1fe6339e | ||
|
|
7a5cf80412 | ||
|
|
5406e8e2e7 | ||
|
|
d3974c9f44 | ||
|
|
0015c8dfa2 | ||
|
|
ad1da22257 | ||
|
|
e703d9ff78 | ||
|
|
32501c96e5 | ||
|
|
6149ba6e36 | ||
|
|
0d44a320a0 | ||
|
|
a8a272b068 | ||
|
|
20a85c1ef2 | ||
|
|
23b58ccbe8 | ||
|
|
9c967ee3ea | ||
|
|
f495addb1e | ||
|
|
d750b690e7 | ||
|
|
fdb3e3f9a4 | ||
|
|
a17018a875 | ||
|
|
8de20fcd1f | ||
|
|
51120ccf31 | ||
|
|
8dd7123923 | ||
|
|
6ed2e7b636 | ||
|
|
fa8e70fe26 | ||
|
|
a87f34ebb3 | ||
|
|
75d875615b | ||
|
|
07e6abdac4 | ||
|
|
1ddc1a6781 | ||
|
|
3f216fa4a3 | ||
|
|
4fb79e6e2a | ||
|
|
fdda30704b | ||
|
|
5418520252 | ||
|
|
9d526b0907 | ||
|
|
9db492eb07 | ||
|
|
d6ee4d58ba | ||
|
|
f63cf6a7a4 | ||
|
|
18ab1144bb | ||
|
|
a9b216d212 | ||
|
|
6b19a1ece9 | ||
|
|
0dd6d552a8 | ||
|
|
cfddf570b9 | ||
|
|
6a9b50f95d | ||
|
|
1dcd857ca6 | ||
|
|
7e6cb9d186 | ||
|
|
cf78307577 | ||
|
|
12fbd34962 | ||
|
|
ed059d80d6 | ||
|
|
664a39911c | ||
|
|
d6cf385a3c | ||
|
|
cef8204f40 | ||
|
|
42e82f0ead | ||
|
|
2a517cde9f | ||
|
|
992657cbe0 | ||
|
|
d2c8aae59c | ||
|
|
3208c8317d | ||
|
|
bec7ea7072 | ||
|
|
0a6ebdee48 | ||
|
|
b1cd634ab4 | ||
|
|
47abd62c55 | ||
|
|
db781437fc | ||
|
|
a40f52ee62 | ||
|
|
89e6fd68e5 | ||
|
|
ecf0f15c17 | ||
|
|
04a6e2de9d | ||
|
|
0cd29daea2 | ||
|
|
b392502b9b | ||
|
|
a301c974e4 | ||
|
|
4c7c2edf9a | ||
|
|
3d3748d6f5 | ||
|
|
fa3199d41c | ||
|
|
efbda6d5af | ||
|
|
8640f500d1 | ||
|
|
3d90e3aee9 | ||
|
|
6c676d21c3 | ||
|
|
7e6af88966 | ||
|
|
3b53cf5e84 | ||
|
|
713f02ca3f | ||
|
|
19844e81d1 | ||
|
|
5fbe766a0a | ||
|
|
1430c0a6e6 | ||
|
|
e3e928f1c4 | ||
|
|
e155acbbd4 | ||
|
|
be1b6b6aa3 | ||
|
|
b4ba5cbb7a | ||
|
|
f28474d86e | ||
|
|
1964073072 | ||
|
|
856d2e3176 | ||
|
|
3409a1bfba | ||
|
|
fc8f02c180 | ||
|
|
03e48161a1 | ||
|
|
def63329f0 | ||
|
|
0680b01a96 | ||
|
|
592e788a7d | ||
|
|
aabc1a8265 | ||
|
|
a23ff4e519 | ||
|
|
edc015d9bf | ||
|
|
d5a0e12283 | ||
|
|
90e8f26cd4 | ||
|
|
d4e83ca1c0 | ||
|
|
137aa9da2c | ||
|
|
87c600aa7c | ||
|
|
08dfe64301 | ||
|
|
0e48ee5f9e | ||
|
|
b6e532f01d | ||
|
|
60baa5e59e | ||
|
|
9eb64dc6a4 | ||
|
|
3de04094fb | ||
|
|
02e8979178 | ||
|
|
5e962719ca | ||
|
|
fefc623343 | ||
|
|
5c40081d84 | ||
|
|
e739d6aa05 | ||
|
|
0994719b91 | ||
|
|
f469920759 | ||
|
|
3c9be8cd08 | ||
|
|
a0e82e1817 | ||
|
|
69bf451b20 | ||
|
|
58699a220b | ||
|
|
3f70f685bb | ||
|
|
d838a76461 | ||
|
|
e13d96a6f2 | ||
|
|
1e1e2d5f54 | ||
|
|
c51d2a56ac | ||
|
|
e9d888bf63 | ||
|
|
47b7ec8d00 | ||
|
|
c8f09f28b1 | ||
|
|
5a32b9599a | ||
|
|
a6cb7965dc | ||
|
|
601cbd2306 | ||
|
|
a77658a6bf | ||
|
|
4a2d2882c6 | ||
|
|
0accaedad0 | ||
|
|
aa10430c7b | ||
|
|
98983e7e1a | ||
|
|
3441216aca | ||
|
|
d16a7e108c | ||
|
|
359ae5d116 | ||
|
|
a9a2509d39 | ||
|
|
e73c225965 | ||
|
|
39e6872288 | ||
|
|
af3c4905ea | ||
|
|
7873952e7a | ||
|
|
d989ce2b70 | ||
|
|
249948e174 | ||
|
|
8ae3331d04 | ||
|
|
b2e05aafc1 | ||
|
|
cc1a43e5d9 | ||
|
|
6f39e6599d | ||
|
|
1fe5857411 | ||
|
|
fce61295c9 | ||
|
|
396b0dace8 | ||
|
|
fe2e33a9e1 | ||
|
|
8d9d4cec05 | ||
|
|
ddd10ba8af | ||
|
|
e4f22bc494 | ||
|
|
09633ee11b | ||
|
|
dc6dbdf3c4 | ||
|
|
8f4197c020 | ||
|
|
5fe5fd71b5 | ||
|
|
77e2b0e4ba | ||
|
|
e196d82e76 | ||
|
|
23f334e5df | ||
|
|
c5a265e828 | ||
|
|
278c881059 | ||
|
|
b09112941a | ||
|
|
158ed5765b | ||
|
|
1e5778e6db | ||
|
|
442dee72b5 | ||
|
|
f2d028ba29 | ||
|
|
b1402f3bae | ||
|
|
6ca9e2a777 | ||
|
|
dac93b9840 | ||
|
|
34a0e2e72d | ||
|
|
af2777c580 | ||
|
|
4a066a1913 | ||
|
|
91083fd6c5 | ||
|
|
202e7d0ebf | ||
|
|
7449f36f22 | ||
|
|
dc6e7cd56c | ||
|
|
26426c1431 | ||
|
|
2fba20acda | ||
|
|
3c887b0dd9 | ||
|
|
52c9e3ecef | ||
|
|
2ffdc46f81 | ||
|
|
577c86fb80 | ||
|
|
4ce7dfa55e | ||
|
|
bd144c996a | ||
|
|
2c1a60b965 | ||
|
|
2dad44c93f | ||
|
|
c835ec5102 | ||
|
|
e2d17b1999 | ||
|
|
3c2ddf1f69 | ||
|
|
d64fc261e2 | ||
|
|
34643f536e | ||
|
|
6a3dab0142 | ||
|
|
3c2b2f26b9 | ||
|
|
67f9e16905 | ||
|
|
14a46f82ee | ||
|
|
974500c281 | ||
|
|
1f4954bf0e | ||
|
|
9615dc0638 | ||
|
|
c1e56a3717 | ||
|
|
91ea12344c | ||
|
|
f572f66426 | ||
|
|
872ba89cad | ||
|
|
1f2daaad59 | ||
|
|
230b503b7a | ||
|
|
a371c08c86 | ||
|
|
55e8d37fb0 | ||
|
|
3f5a2b9c7a | ||
|
|
a589022fcc | ||
|
|
31fc8076e3 | ||
|
|
53f330dbe8 | ||
|
|
13a13f3943 | ||
|
|
00ca227292 | ||
|
|
5c8ed73608 | ||
|
|
5df01ed3e6 | ||
|
|
43d7d5570e | ||
|
|
e062cbb79f | ||
|
|
27e27788cd | ||
|
|
cc5c000a6d | ||
|
|
2c7fc5df75 | ||
|
|
f7e61cc153 | ||
|
|
e7f64334c0 | ||
|
|
584b8109a0 | ||
|
|
d75696b30a | ||
|
|
22bb700f94 | ||
|
|
a4b2e1f2e2 | ||
|
|
6ee161bdc6 | ||
|
|
13349dab6d | ||
|
|
1159976d99 | ||
|
|
407b16c98c | ||
|
|
96ce9122f4 | ||
|
|
a80f13243e | ||
|
|
c166852054 | ||
|
|
ba00d57d37 | ||
|
|
f5133c6737 | ||
|
|
5a4234fcb9 | ||
|
|
3cbe8ddf02 | ||
|
|
8461832a7b | ||
|
|
f504d1aa99 | ||
|
|
2b0ddd2a98 | ||
|
|
cc271aefe1 | ||
|
|
0b57389af6 | ||
|
|
ef77961cf4 | ||
|
|
915cf3e715 | ||
|
|
cadba74b1f | ||
|
|
23b305847e | ||
|
|
f55e966c8f | ||
|
|
77bbe5730b | ||
|
|
f8b523b97b | ||
|
|
d9c503a9ec | ||
|
|
52aa123a9f | ||
|
|
4e25c70f96 | ||
|
|
d250fd91a9 | ||
|
|
6a90295505 | ||
|
|
b4c7727a82 | ||
|
|
9ac64d57d0 | ||
|
|
6f68be5aa7 | ||
|
|
b68c79be6b | ||
|
|
b3d674e170 | ||
|
|
bff9bd5dbd | ||
|
|
e7e63796ac | ||
|
|
e1911a624b | ||
|
|
825cc9a86f | ||
|
|
869c87e396 | ||
|
|
e9fb91b197 | ||
|
|
a081d0d93a | ||
|
|
54af29eb40 | ||
|
|
6cae4d7ee4 | ||
|
|
65d2994823 | ||
|
|
8e0a6479ca | ||
|
|
98f2d2501b | ||
|
|
a05fe69043 | ||
|
|
6fb080ff4c | ||
|
|
9923548628 | ||
|
|
181f5c991b | ||
|
|
3dd2e4c84c | ||
|
|
83f7f9f0e5 | ||
|
|
507fb5387b | ||
|
|
1d7bee5eee | ||
|
|
1d922ee813 | ||
|
|
d237b97776 | ||
|
|
790e1ee2e4 | ||
|
|
e5caea04c1 | ||
|
|
2f1ca902f7 | ||
|
|
d5d87e0fdd | ||
|
|
3e7cf416f1 | ||
|
|
e2d2ff8586 | ||
|
|
11fa348575 | ||
|
|
58668a6807 | ||
|
|
6d379ad029 | ||
|
|
a05fc091c8 | ||
|
|
f2bc6f0f96 | ||
|
|
8ba50d0cf2 | ||
|
|
6dcb2de28f | ||
|
|
1bbfc6da25 | ||
|
|
eecd51e08b | ||
|
|
26abd1f4e2 | ||
|
|
28925c12eb | ||
|
|
d0ac4332ab | ||
|
|
8e5aa69321 | ||
|
|
848cfeb353 | ||
|
|
7b05a18173 | ||
|
|
2ce99929e2 | ||
|
|
f1e82a3647 | ||
|
|
1c5af01a82 | ||
|
|
bac3ace8fc | ||
|
|
68599351aa | ||
|
|
86755029ef | ||
|
|
c507ab30e9 | ||
|
|
7d1614b933 | ||
|
|
a77d1e502c | ||
|
|
d79ed76d80 | ||
|
|
ccf8059452 | ||
|
|
3d3d1bc623 | ||
|
|
e66d065b6d | ||
|
|
c1ef87e009 | ||
|
|
3c249a40a0 | ||
|
|
5092641157 | ||
|
|
472a45ddec | ||
|
|
120cbb0159 | ||
|
|
68fbd9b017 | ||
|
|
3143f75a38 | ||
|
|
e13d4ffe60 | ||
|
|
295d4f0394 | ||
|
|
ea91e09a1b | ||
|
|
946779000f | ||
|
|
5c07b6dc1d | ||
|
|
25c3c1b431 | ||
|
|
a0ae7a227d | ||
|
|
50df3acd26 | ||
|
|
425670f52a |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: [jeremystretch]
|
||||
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,42 +0,0 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report a reproducible bug in the current release of NetBox
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
||||
|
||||
This form is only for reproducible bugs. If you need assistance with
|
||||
NetBox installation, or if you have a general question, DO NOT open an
|
||||
issue. Instead, post to our mailing list:
|
||||
|
||||
https://groups.google.com/g/netbox-discuss
|
||||
|
||||
Please describe the environment in which you are running NetBox. Be sure
|
||||
that you are running an unmodified instance of the latest stable release
|
||||
before submitting a bug report, and that any plugins have been disabled.
|
||||
-->
|
||||
### Environment
|
||||
* Python version:
|
||||
* NetBox version:
|
||||
|
||||
<!--
|
||||
Describe in detail the exact steps that someone else can take to reproduce
|
||||
this bug using the current stable release of NetBox. Begin with the
|
||||
creation of any necessary database objects and call out every operation
|
||||
being performed explicitly. If reporting a bug in the REST API, be sure to
|
||||
reconstruct the raw HTTP request(s) being made: Don't rely on a client
|
||||
library such as pynetbox.
|
||||
-->
|
||||
### Steps to Reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
<!-- What did you expect to happen? -->
|
||||
### Expected Behavior
|
||||
|
||||
|
||||
<!-- What happened instead? -->
|
||||
### Observed Behavior
|
||||
58
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
description: Report a reproducible bug in the current release of NetBox
|
||||
labels: ["type: bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**NOTE:** This form is only for reporting _reproducible bugs_ in a
|
||||
current NetBox installation. If you're having trouble with installation or just
|
||||
looking for assistance with using NetBox, please visit our
|
||||
[discussion forum](https://github.com/netbox-community/netbox/discussions) instead."
|
||||
- type: input
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: "What version of NetBox are you currently running?"
|
||||
placeholder: v2.10.4
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Python version
|
||||
description: "What version of Python are you currently running?"
|
||||
options:
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
- 3.9
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: "Describe in detail the exact steps that someone else can take to
|
||||
reproduce this bug using the current stable release of NetBox. Begin with the
|
||||
creation of any necessary database objects and call out every operation being
|
||||
performed explicitly. If reporting a bug in the REST API, be sure to reconstruct
|
||||
the raw HTTP request(s) being made: Don't rely on a client library such as
|
||||
pynetbox."
|
||||
placeholder: |
|
||||
1. Click on "create widget"
|
||||
2. Set foo to 12 and bar to G
|
||||
3. Click the "create" button
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: "What did you expect to happen?"
|
||||
placeholder: "A new widget should have been created with the specified attributes"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Observed Behavior
|
||||
description: "What happened instead?"
|
||||
placeholder: "A TypeError exception was raised"
|
||||
validations:
|
||||
required: true
|
||||
28
.github/ISSUE_TEMPLATE/documentation_change.md
vendored
28
.github/ISSUE_TEMPLATE/documentation_change.md
vendored
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: 📖 Documentation Change
|
||||
about: Suggest an addition or modification to the NetBox documentation
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
||||
|
||||
Please indicate the nature of the change by placing an X in one of the
|
||||
boxes below.
|
||||
-->
|
||||
### Change Type
|
||||
[ ] Addition
|
||||
[ ] Correction
|
||||
[ ] Deprecation
|
||||
[ ] Cleanup (formatting, typos, etc.)
|
||||
|
||||
### Area
|
||||
[ ] Installation instructions
|
||||
[ ] Configuration parameters
|
||||
[ ] Functionality/features
|
||||
[ ] REST API
|
||||
[ ] Administration/development
|
||||
[ ] Other
|
||||
|
||||
<!-- Describe the proposed change(s). -->
|
||||
### Proposed Changes
|
||||
35
.github/ISSUE_TEMPLATE/documentation_change.yaml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/documentation_change.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: 📖 Documentation Change
|
||||
description: Suggest an addition or modification to the NetBox documentation
|
||||
labels: ["type: documentation"]
|
||||
body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Change Type
|
||||
description: What type of change are you proposing?
|
||||
options:
|
||||
- Addition
|
||||
- Correction
|
||||
- Removal
|
||||
- Cleanup (formatting, typos, etc.)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Area
|
||||
description: To what section of the documentation does this change primarily pertain?
|
||||
options:
|
||||
- Installation instructions
|
||||
- Configuration parameters
|
||||
- Functionality/features
|
||||
- REST API
|
||||
- Administration/development
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposed Changes
|
||||
description: "Describe the proposed changes and why they are necessary"
|
||||
validations:
|
||||
required: true
|
||||
56
.github/ISSUE_TEMPLATE/feature_request.md
vendored
56
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: ✨ Feature Request
|
||||
about: Propose a new NetBox feature or enhancement
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
||||
|
||||
This form is only for proposing specific new features or enhancements.
|
||||
If you have a general idea or question, please post to our mailing list
|
||||
instead of opening an issue:
|
||||
|
||||
https://groups.google.com/g/netbox-discuss
|
||||
|
||||
NOTE: Due to an excessive backlog of feature requests, we are not currently
|
||||
accepting any proposals which significantly extend NetBox's feature scope.
|
||||
|
||||
Please describe the environment in which you are running NetBox. Be sure
|
||||
that you are running an unmodified instance of the latest stable release
|
||||
before submitting a bug report.
|
||||
-->
|
||||
### Environment
|
||||
* Python version:
|
||||
* NetBox version:
|
||||
|
||||
<!--
|
||||
Describe in detail the new functionality you are proposing. Include any
|
||||
specific changes to work flows, data models, or the user interface.
|
||||
-->
|
||||
### Proposed Functionality
|
||||
|
||||
|
||||
<!--
|
||||
Convey an example use case for your proposed feature. Write from the
|
||||
perspective of a NetBox user who would benefit from the proposed
|
||||
functionality and describe how.
|
||||
--->
|
||||
### Use Case
|
||||
|
||||
|
||||
<!--
|
||||
Note any changes to the database schema necessary to support the new
|
||||
feature. For example, does the proposal require adding a new model or
|
||||
field? (Not all new features require database changes.)
|
||||
--->
|
||||
### Database Changes
|
||||
|
||||
|
||||
<!--
|
||||
List any new dependencies on external libraries or services that this new
|
||||
feature would introduce. For example, does the proposal require the
|
||||
installation of a new Python package? (Not all new features introduce new
|
||||
dependencies.)
|
||||
-->
|
||||
### External Dependencies
|
||||
53
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
53
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: ✨ Feature Request
|
||||
description: Propose a new NetBox feature or enhancement
|
||||
labels: ["type: feature"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**NOTE:** This form is only for submitting well-formed proposals to extend or
|
||||
modify NetBox in some way. If you're trying to solve a problem but can't figure out how,
|
||||
or if you still need time to work on the details of a proposed new feature, please start
|
||||
a [discussion](https://github.com/netbox-community/netbox/discussions) instead."
|
||||
- type: input
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: "What version of NetBox are you currently running?"
|
||||
placeholder: v2.10.4
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Feature type
|
||||
options:
|
||||
- Data model extension
|
||||
- New functionality
|
||||
- Change to existing functionality
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposed functionality
|
||||
description: "Describe in detail the new feature or behavior you'd like to propose.
|
||||
Include any specific changes to work flows, data models, or the user interface."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Use case
|
||||
description: "Explain how adding this functionality would benefit NetBox users. What
|
||||
need does it address?"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Database changes
|
||||
description: "Note any changes to the database schema necessary to support the new
|
||||
feature. For example, does the proposal require adding a new model or field? (Not
|
||||
all new features require database changes.)"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: External dependencies
|
||||
description: "List any new dependencies on external libraries or services that this
|
||||
new feature would introduce. For example, does the proposal require the installation
|
||||
of a new Python package? (Not all new features introduce new dependencies.)"
|
||||
16
.github/ISSUE_TEMPLATE/housekeeping.md
vendored
16
.github/ISSUE_TEMPLATE/housekeeping.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: 🏡 Housekeeping
|
||||
about: A change pertaining to the codebase itself (developers only)
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
NOTE: This template is for use by maintainers only. Please do not submit
|
||||
an issue using this template unless you have been specifically asked to
|
||||
do so.
|
||||
-->
|
||||
### Proposed Changes
|
||||
|
||||
|
||||
<!-- Provide justification for the proposed change(s). -->
|
||||
### Justification
|
||||
22
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: 🏡 Housekeeping
|
||||
description: A change pertaining to the codebase itself (developers only)
|
||||
labels: ["type: housekeeping"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "**NOTE:** This template is for use by maintainers only. Please do not submit
|
||||
an issue using this template unless you have been specifically asked to do so."
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposed Changes
|
||||
description: "Describe in detail the new feature or behavior you'd like to propose.
|
||||
Include any specific changes to work flows, data models, or the user interface."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Justification
|
||||
description: "Please provide justification for the proposed change(s)."
|
||||
validations:
|
||||
required: true
|
||||
33
.github/stale.yml
vendored
33
.github/stale.yml
vendored
@@ -1,33 +0,0 @@
|
||||
# Configuration for Stale (https://github.com/apps/stale)
|
||||
|
||||
# Pull requests are exempt from being marked as stale
|
||||
only: issues
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 45
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 15
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "status: accepted"
|
||||
- "status: blocked"
|
||||
- "status: needs milestone"
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "pending closure"
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. NetBox
|
||||
is governed by a small group of core maintainers which means not all opened
|
||||
issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed due to lack of activity. In an
|
||||
effort to reduce noise, please do not comment any further. Note that the
|
||||
core maintainers may elect to reopen this issue at a later date if deemed
|
||||
necessary.
|
||||
50
.github/workflows/ci.yml
vendored
Normal file
50
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8]
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_USER: netbox
|
||||
POSTGRES_PASSWORD: netbox
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies & set up configuration
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pycodestyle coverage
|
||||
ln -s configuration.testing.py netbox/netbox/configuration.py
|
||||
|
||||
- name: Check PEP8 compliance
|
||||
run: pycodestyle --ignore=W504,E501 netbox/
|
||||
|
||||
- name: Run tests
|
||||
run: coverage run --source="netbox/" netbox/manage.py test netbox/
|
||||
|
||||
- name: Show coverage report
|
||||
run: coverage report --skip-covered --omit *migrations*
|
||||
34
.github/workflows/stale.yml
vendored
Normal file
34
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# close-stale-issues (https://github.com/marketplace/actions/close-stale-issues)
|
||||
name: 'Close stale issues/PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
close-issue-message: >
|
||||
This issue has been automatically closed due to lack of activity. In an
|
||||
effort to reduce noise, please do not comment any further. Note that the
|
||||
core maintainers may elect to reopen this issue at a later date if deemed
|
||||
necessary.
|
||||
close-pr-message: >
|
||||
This PR has been automatically closed due to lack of activity.
|
||||
days-before-stale: 45
|
||||
days-before-close: 15
|
||||
exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone'
|
||||
remove-stale-when-updated: false
|
||||
stale-issue-label: 'pending closure'
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. NetBox
|
||||
is governed by a small group of core maintainers which means not all opened
|
||||
issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
|
||||
stale-pr-label: 'pending closure'
|
||||
stale-pr-message: >
|
||||
This PR has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed automatically if no further action is
|
||||
taken.
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,20 +0,0 @@
|
||||
sudo: required
|
||||
services:
|
||||
- postgresql
|
||||
- redis-server
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install pycodestyle
|
||||
- pip install coverage
|
||||
before_script:
|
||||
- psql --version
|
||||
- psql -U postgres -c 'SELECT version();'
|
||||
script:
|
||||
- ./scripts/cibuild.sh
|
||||
@@ -4,15 +4,31 @@ If you encounter any issues installing or using NetBox, try one of the
|
||||
following resources to get assistance. Please **do not** open a GitHub issue
|
||||
except to report bugs or request features.
|
||||
|
||||
### GitHub Discussions
|
||||
|
||||
GitHub's discussions are the best place to get help or propose rough ideas for
|
||||
new functionality. Their integration with GitHub allows for easily cross-
|
||||
referencing and converting posts to issues as needed. There are several
|
||||
categories for discussions:
|
||||
|
||||
* **General** - General community discussion
|
||||
* **Ideas** - Ideas for new functionality that isn't yet ready for a formal
|
||||
feature request
|
||||
* **Q&A** - Request help with installing or using NetBox
|
||||
|
||||
### Mailing List
|
||||
|
||||
We have established a Google Groups Mailing List for issues and general
|
||||
discussion. This is the best forum for obtaining assistance with NetBox
|
||||
installation. You can find us [here](https://groups.google.com/g/netbox-discuss).
|
||||
We also have a Google Groups [mailing list](https://groups.google.com/g/netbox-discuss)
|
||||
for general discussion, however we're encouraging people to use GitHub
|
||||
discussions where possible, as it's much easier for newcomers to review past
|
||||
discussions.
|
||||
|
||||
### Slack
|
||||
|
||||
For real-time discussion, you can join the #netbox Slack channel on [NetworkToCode](https://slack.networktocode.com/).
|
||||
For real-time chat, you can join the **#netbox** Slack channel on [NetDev Community](https://slack.netbox.dev/).
|
||||
Unfortunately, the Slack channel does not provide long-term retention of chat
|
||||
history, so try to avoid it for any discussions would benefit from being
|
||||
preserved for future reference.
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
@@ -93,6 +109,10 @@ appropriate labels will be applied for categorization.
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
* If you're interested in contributing to NetBox, be sure to check out our
|
||||
[getting started](https://netbox.readthedocs.io/en/stable/development/getting-started/)
|
||||
documentation for tips on setting up your development environment.
|
||||
|
||||
* Be sure to open an issue **before** starting work on a pull request, and
|
||||
discuss your idea with the NetBox maintainers before beginning work. This will
|
||||
help prevent wasting time on something that might we might not be able to
|
||||
@@ -165,11 +185,5 @@ overlooked.
|
||||
sync to review agenda items. This meeting provides opportunity to present and
|
||||
discuss pressing topics. Meetings are held as virtual audio/video conferences.
|
||||
|
||||
* Official channels for communication include:
|
||||
|
||||
* GitHub issues/pull requests
|
||||
* The [netbox-discuss](https://groups.google.com/g/netbox-discuss) mailing list
|
||||
* The **#netbox** channel on [NetworkToCode Slack](https://networktocode.slack.com/)
|
||||
|
||||
* Maintainers with no substantial recorded activity in a 60-day period will be
|
||||
removed from the project.
|
||||
|
||||
32
README.md
32
README.md
@@ -7,20 +7,23 @@ to address the needs of network and infrastructure engineers. It is intended to
|
||||
function as a domain-specific source of truth for network operations.
|
||||
|
||||
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/)
|
||||
Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a
|
||||
Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a
|
||||
complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox).
|
||||
|
||||
The complete documentation for NetBox can be found at [Read the Docs](http://netbox.readthedocs.io/en/stable/).
|
||||
The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/).
|
||||
|
||||
Questions? Comments? Please subscribe to [the netbox-discuss mailing list](https://groups.google.com/g/netbox-discuss),
|
||||
or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode.slack.com/)!
|
||||
### Discussion
|
||||
|
||||
* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - Discussion forum hosted by GitHub; ideal for Q&A and other structured discussions
|
||||
* [Slack](https://slack.netbox.dev/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out
|
||||
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being replaced by GitHub discussions
|
||||
|
||||
### Build Status
|
||||
|
||||
| | status |
|
||||
|-------------|------------|
|
||||
| **master** | [](https://travis-ci.com/netbox-community/netbox/) |
|
||||
| **develop** | [](https://travis-ci.com/netbox-community/netbox/) |
|
||||
| **master** |  |
|
||||
| **develop** |  |
|
||||
|
||||
### Screenshots
|
||||
|
||||
@@ -36,19 +39,22 @@ or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode
|
||||
|
||||
## Installation
|
||||
|
||||
Please see [the documentation](http://netbox.readthedocs.io/en/stable/) for
|
||||
instructions on installing NetBox. To upgrade NetBox, please download the [latest release](https://github.com/netbox-community/netbox/releases)
|
||||
and run `upgrade.sh`.
|
||||
Please see [the documentation](https://netbox.readthedocs.io/en/stable/) for
|
||||
instructions on installing NetBox. To upgrade NetBox, please download the
|
||||
[latest release](https://github.com/netbox-community/netbox/releases) and
|
||||
run `upgrade.sh`.
|
||||
|
||||
## Providing Feedback
|
||||
|
||||
Feature requests and bug reports must be submitted as GiHub issues. (Please be
|
||||
sure to use the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose).)
|
||||
For general discussion, please consider joining our [mailing list](https://groups.google.com/g/netbox-discuss).
|
||||
The best platform for general feedback, assistance, and other discussion is our
|
||||
[GitHub discussions](https://github.com/netbox-community/netbox/discussions).
|
||||
To report a bug or request a specific feature, please open a GitHub issue using
|
||||
the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose).
|
||||
|
||||
If you are interested in contributing to the development of NetBox, please read
|
||||
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
||||
|
||||
## Related projects
|
||||
|
||||
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for a list of relevant community projects.
|
||||
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions)
|
||||
for a list of relevant community projects.
|
||||
|
||||
@@ -93,3 +93,7 @@ redis
|
||||
# SVG image rendering (used for rack elevations)
|
||||
# https://github.com/mozman/svgwrite
|
||||
svgwrite
|
||||
|
||||
# Tabular dataset library (for table-based exports)
|
||||
# https://github.com/jazzband/tablib
|
||||
tablib
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Caching
|
||||
|
||||
NetBox supports database query caching using [django-cacheops](https://github.com/Suor/django-cacheops) and Redis. When a query is made, the results are cached in Redis for a short period of time, as defined by the [CACHE_TIMEOUT](../../configuration/optional-settings/#cache_timeout) parameter (15 minutes by default). Within that time, all recurrences of that specific query will return the pre-fetched results from the cache.
|
||||
NetBox supports database query caching using [django-cacheops](https://github.com/Suor/django-cacheops) and Redis. When a query is made, the results are cached in Redis for a short period of time, as defined by the [CACHE_TIMEOUT](../configuration/optional-settings.md#cache_timeout) parameter (15 minutes by default). Within that time, all recurrences of that specific query will return the pre-fetched results from the cache.
|
||||
|
||||
If a change is made to any of the objects returned by the query within that time, or if the timeout expires, the results are automatically invalidated and the next request for those results will be sent to the database.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Change Logging
|
||||
|
||||
Every time an object in NetBox is created, updated, or deleted, a serialized copy of that object is saved to the database, along with meta data including the current time and the user associated with the change. These records form a persistent record of changes both for each individual object as well as NetBox as a whole. The global change log can be viewed by navigating to Other > Change Log.
|
||||
Every time an object in NetBox is created, updated, or deleted, a serialized copy of that object taken both before and after the change is saved to the database, along with meta data including the current time and the user associated with the change. These records form a persistent record of changes both for each individual object as well as NetBox as a whole. The global change log can be viewed by navigating to Other > Change Log.
|
||||
|
||||
A serialized representation of the instance being modified is included in JSON format. This is similar to how objects are conveyed within the REST API, but does not include any nested representations. For instance, the `tenant` field of a site will record only the tenant's ID, not a representation of the tenant.
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ Custom fields must be created through the admin UI under Extras > Custom Fields.
|
||||
* Date: A date in ISO 8601 format (YYYY-MM-DD)
|
||||
* URL: This will be presented as a link in the web UI
|
||||
* Selection: A selection of one of several pre-defined custom choices
|
||||
* Multiple selection: A selection field which supports the assignment of multiple values
|
||||
|
||||
Each custom field must have a name; this should be a simple database-friendly string, e.g. `tps_report`. You may also assign a corresponding human-friendly label (e.g. "TPS report"); the label will be displayed on web forms. A weight is also required: Higher-weight fields will be ordered lower within a form. (The default weight is 100.) If a description is provided, it will appear beneath the field in a form.
|
||||
|
||||
@@ -37,7 +38,13 @@ NetBox supports limited custom validation for custom field values. Following are
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Custom Fields in Templates
|
||||
|
||||
Several features within NetBox, such as export templates and webhooks, utilize Jinja2 templating. For convenience, objects which support custom field assignment expose custom field data through the `cf` property. This is a bit cleaner than accessing custom field data through the actual field (`custom_field_data`).
|
||||
|
||||
For example, a custom field named `foo123` on the Site model is accessible on an instance as `{{ site.cf.foo123 }}`.
|
||||
|
||||
## Custom Fields and the REST API
|
||||
|
||||
|
||||
@@ -170,18 +170,13 @@ Similar to `ChoiceVar`, but allows for the selection of multiple choices.
|
||||
A particular object within NetBox. Each ObjectVar must specify a particular model, and allows the user to select one of the available instances. ObjectVar accepts several arguments, listed below.
|
||||
|
||||
* `model` - The model class
|
||||
* `display_field` - The name of the REST API object field to display in the selection list (default: `'name'`)
|
||||
* `display_field` - The name of the REST API object field to display in the selection list (default: `'display'`)
|
||||
* `query_params` - A dictionary of query parameters to use when retrieving available options (optional)
|
||||
* `null_option` - A label representing a "null" or empty choice (optional)
|
||||
|
||||
The `display_field` argument is useful when referencing a model which does not have a `name` field. For example, when displaying a list of device types, you would likely use the `model` field:
|
||||
|
||||
```python
|
||||
device_type = ObjectVar(
|
||||
model=DeviceType,
|
||||
display_field='model'
|
||||
)
|
||||
```
|
||||
!!! warning
|
||||
The `display_field` parameter is now deprecated, and will be removed in NetBox v2.12. All ObjectVar instances will
|
||||
instead use the new standard `display` field for all serializers (introduced in NetBox v2.11).
|
||||
|
||||
To limit the selections available within the list, additional query parameters can be passed as the `query_params` dictionary. For example, to show only devices with an "active" status:
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
NetBox allows users to define custom templates that can be used when exporting objects. To create an export template, navigate to Extras > Export Templates under the admin interface.
|
||||
|
||||
Each export template is associated with a certain type of object. For instance, if you create an export template for VLANs, your custom template will appear under the "Export" button on the VLANs list.
|
||||
Each export template is associated with a certain type of object. For instance, if you create an export template for VLANs, your custom template will appear under the "Export" button on the VLANs list. Each export template must have a name, and may optionally designate a specific export [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) and/or file extension.
|
||||
|
||||
Export templates may be written in Jinja2 or [Django's template language](https://docs.djangoproject.com/en/stable/ref/templates/language/), which is very similar to Jinja2.
|
||||
!!! note
|
||||
The name `table` is reserved for internal use.
|
||||
|
||||
!!! warning
|
||||
Support for Django's native templating logic will be removed in NetBox v2.10.
|
||||
Export templates must be written in [Jinja2](https://jinja.palletsprojects.com/).
|
||||
|
||||
The list of objects returned from the database when rendering an export template is stored in the `queryset` variable, which you'll typically want to iterate through using a `for` loop. Object properties can be access by name. For example:
|
||||
|
||||
@@ -21,6 +21,16 @@ Height: {{ rack.u_height }}U
|
||||
|
||||
To access custom fields of an object within a template, use the `cf` attribute. For example, `{{ obj.cf.color }}` will return the value (if any) for a custom field named `color` on `obj`.
|
||||
|
||||
If you need to use the config context data in an export template, you'll should use the function `get_config_context` to get all the config context data. For example:
|
||||
```
|
||||
{% for server in queryset %}
|
||||
{% set data = server.get_config_context() %}
|
||||
{{ data.syslog }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
The `as_attachment` attribute of an export template controls its behavior when rendered. If true, the rendered content will be returned to the user as a downloadable file. If false, it will be displayed within the browser. (This may be handy e.g. for generating HTML content.)
|
||||
|
||||
A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`.
|
||||
|
||||
## Example
|
||||
|
||||
5
docs/additional-features/journaling.md
Normal file
5
docs/additional-features/journaling.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Journaling
|
||||
|
||||
All primary objects in NetBox support journaling. A journal is a collection of human-generated notes and comments about an object maintained for historical context. It supplements NetBox's change log to provide additional information about why changes have been made or to convey events which occur outside NetBox. Unlike the change log, in which records typically expire after a configurable period of time, journal entries persist for the life of their associated object.
|
||||
|
||||
Each journal entry has a selectable kind (info, success, warning, or danger) and a user-populated `comments` field. Each entry automatically records the date, time, and associated user upon being created.
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to serve a proxy for operational data, fetching live data from network devices and returning it to a requester via its REST API. Note that NetBox does not store any NAPALM data locally.
|
||||
|
||||
The NetBox UI will display tabs for status, LLDP neighbors, and configuration under the device view if the following conditions are met:
|
||||
|
||||
* Device status is "Active"
|
||||
* A primary IP has been assigned to the device
|
||||
* A platform with a NAPALM driver has been assigned
|
||||
* The authenticated user has the `dcim.napalm_read_device` permission
|
||||
|
||||
!!! note
|
||||
To enable this integration, the NAPALM library must be installed. See [installation steps](../../installation/3-netbox/#napalm) for more information.
|
||||
|
||||
@@ -22,7 +29,7 @@ GET /api/dcim/devices/1/napalm/?method=get_environment
|
||||
|
||||
## Authentication
|
||||
|
||||
By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) configuration parameters are used for NAPALM authentication. They can be overridden for an individual API call by specifying the `X-NAPALM-Username` and `X-NAPALM-Password` headers.
|
||||
By default, the [`NAPALM_USERNAME`](../configuration/optional-settings.md#napalm_username) and [`NAPALM_PASSWORD`](../configuration/optional-settings.md#napalm_password) configuration parameters are used for NAPALM authentication. They can be overridden for an individual API call by specifying the `X-NAPALM-Username` and `X-NAPALM-Password` headers.
|
||||
|
||||
```
|
||||
$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
|
||||
|
||||
@@ -26,4 +26,4 @@ For the exhaustive list of exposed metrics, visit the `/metrics` endpoint on you
|
||||
When deploying NetBox in a multiprocess manner (e.g. running multiple Gunicorn workers) the Prometheus client library requires the use of a shared directory to collect metrics from all worker processes. To configure this, first create or designate a local directory to which the worker processes have read and write access, and then configure your WSGI service (e.g. Gunicorn) to define this path as the `prometheus_multiproc_dir` environment variable.
|
||||
|
||||
!!! warning
|
||||
If having accurate long-term metrics in a multiprocess environment is crucial to your deployment, it's recommended you use the `uwsgi` library instead of `gunicorn`. The issue lies in the way `gunicorn` tracks worker processes (vs `uwsgi`) which helps manage the metrics files created by the above configurations. If you're using Netbox with gunicorn in a containerized enviroment following the one-process-per-container methodology, then you will likely not need to change to `uwsgi`. More details can be found in [issue #3779](https://github.com/netbox-community/netbox/issues/3779#issuecomment-590547562).
|
||||
If having accurate long-term metrics in a multiprocess environment is crucial to your deployment, it's recommended you use the `uwsgi` library instead of `gunicorn`. The issue lies in the way `gunicorn` tracks worker processes (vs `uwsgi`) which helps manage the metrics files created by the above configurations. If you're using NetBox with gunicorn in a containerized enviroment following the one-process-per-container methodology, then you will likely not need to change to `uwsgi`. More details can be found in [issue #3779](https://github.com/netbox-community/netbox/issues/3779#issuecomment-590547562).
|
||||
|
||||
@@ -12,7 +12,7 @@ A NetBox report is a mechanism for validating the integrity of data within NetBo
|
||||
|
||||
## Writing Reports
|
||||
|
||||
Reports must be saved as files in the [`REPORTS_ROOT`](../../configuration/optional-settings/#reports_root) path (which defaults to `netbox/reports/`). Each file created within this path is considered a separate module. Each module holds one or more reports (Python classes), each of which performs a certain function. The logic of each report is broken into discrete test methods, each of which applies a small portion of the logic comprising the overall test.
|
||||
Reports must be saved as files in the [`REPORTS_ROOT`](../configuration/optional-settings.md#reports_root) path (which defaults to `netbox/reports/`). Each file created within this path is considered a separate module. Each module holds one or more reports (Python classes), each of which performs a certain function. The logic of each report is broken into discrete test methods, each of which applies a small portion of the logic comprising the overall test.
|
||||
|
||||
!!! warning
|
||||
The reports path includes a file named `__init__.py`, which registers the path as a Python module. Do not delete this file.
|
||||
@@ -66,7 +66,7 @@ class DeviceConnectionsReport(Report):
|
||||
for power_port in PowerPort.objects.filter(device=device):
|
||||
if power_port.connected_endpoint is not None:
|
||||
connected_ports += 1
|
||||
if not power_port.connection_status:
|
||||
if not power_port.path.is_active:
|
||||
self.log_warning(
|
||||
device,
|
||||
"Power connection for {} marked as planned".format(power_port.name)
|
||||
|
||||
@@ -38,7 +38,8 @@ The following data is available as context for Jinja2 templates:
|
||||
* `timestamp` - The time at which the event occurred (in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format).
|
||||
* `username` - The name of the user account associated with the change.
|
||||
* `request_id` - The unique request ID. This may be used to correlate multiple changes associated with a single request.
|
||||
* `data` - A serialized representation of the object _after_ the change was made. This is typically equivalent to the model's representation in NetBox's REST API.
|
||||
* `data` - A detailed representation of the object in its current state. This is typically equivalent to the model's representation in NetBox's REST API.
|
||||
* `snapshots` - Minimal "snapshots" of the object state both before and after the change was made; provided ass a dictionary with keys named `prechange` and `postchange`. These are not as extensive as the fully serialized representation, but contain enough information to convey what has changed.
|
||||
|
||||
### Default Request Body
|
||||
|
||||
@@ -47,7 +48,7 @@ If no body template is specified, the request body will be populated with a JSON
|
||||
```no-highlight
|
||||
{
|
||||
"event": "created",
|
||||
"timestamp": "2020-02-25 15:10:26.010582+00:00",
|
||||
"timestamp": "2021-03-09 17:55:33.968016+00:00",
|
||||
"model": "site",
|
||||
"username": "jstretch",
|
||||
"request_id": "fdbca812-3142-4783-b364-2e2bd5c16c6a",
|
||||
@@ -62,13 +63,24 @@ If no body template is specified, the request body will be populated with a JSON
|
||||
},
|
||||
"region": null,
|
||||
...
|
||||
},
|
||||
"snapshots": {
|
||||
"prechange": null,
|
||||
"postchange": {
|
||||
"created": "2021-03-09",
|
||||
"last_updated": "2021-03-09T17:55:33.851Z",
|
||||
"name": "Site 1",
|
||||
"slug": "site-1",
|
||||
"status": "active",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Processing
|
||||
|
||||
When a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected in the admin UI under Django RQ > Queues.
|
||||
When a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected in the admin UI under System > Background Tasks.
|
||||
|
||||
A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be retried manually via the admin UI.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ NetBox v2.9 introduced a new object-based permissions framework, which replace's
|
||||
| ----------- | ----------- |
|
||||
| `{"status": "active"}` | Status is active |
|
||||
| `{"status__in": ["planned", "reserved"]}` | Status is active **OR** reserved |
|
||||
| `{"status": "active", "role": "testing"}` | Status is active **OR** role is testing |
|
||||
| `{"status": "active", "role": "testing"}` | Status is active **AND** role is testing |
|
||||
| `{"name__startswith": "Foo"}` | Name starts with "Foo" (case-sensitive) |
|
||||
| `{"name__iendswith": "bar"}` | Name ends with "bar" (case-insensitive) |
|
||||
| `{"vid__gte": 100, "vid__lt": 200}` | VLAN ID is greater than or equal to 100 **AND** less than 200 |
|
||||
|
||||
@@ -12,13 +12,16 @@ NetBox employs a [PostgreSQL](https://www.postgresql.org/) database, so general
|
||||
Use the `pg_dump` utility to export the entire database to a file:
|
||||
|
||||
```no-highlight
|
||||
pg_dump netbox > netbox.sql
|
||||
pg_dump --username netbox --password --host localhost netbox > netbox.sql
|
||||
```
|
||||
|
||||
!!! note
|
||||
You may need to change the username, host, and/or database in the command above to match your installation.
|
||||
|
||||
When replicating a production database for development purposes, you may find it convenient to exclude changelog data, which can easily account for the bulk of a database's size. To do this, exclude the `extras_objectchange` table data from the export. The table will still be included in the output file, but will not be populated with any data.
|
||||
|
||||
```no-highlight
|
||||
pg_dump --exclude-table-data=extras_objectchange netbox > netbox.sql
|
||||
pg_dump ... --exclude-table-data=extras_objectchange netbox > netbox.sql
|
||||
```
|
||||
|
||||
### Load an Exported Database
|
||||
@@ -41,7 +44,7 @@ Keep in mind that PostgreSQL user accounts and permissions are not included with
|
||||
If you want to export only the database schema, and not the data itself (e.g. for development reference), do the following:
|
||||
|
||||
```no-highlight
|
||||
pg_dump -s netbox > netbox_schema.sql
|
||||
pg_dump --username netbox --password --host localhost -s netbox > netbox_schema.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -44,7 +44,7 @@ This defines custom content to be displayed on the login page above the login fo
|
||||
|
||||
Default: None
|
||||
|
||||
The base URL path to use when accessing NetBox. Do not include the scheme or domain name. For example, if installed at http://example.com/netbox/, set:
|
||||
The base URL path to use when accessing NetBox. Do not include the scheme or domain name. For example, if installed at https://example.com/netbox/, set:
|
||||
|
||||
```python
|
||||
BASE_PATH = 'netbox/'
|
||||
@@ -56,7 +56,7 @@ BASE_PATH = 'netbox/'
|
||||
|
||||
Default: 900
|
||||
|
||||
The number of seconds to cache entries will be retained before expiring.
|
||||
The number of seconds that cache entries will be retained before expiring.
|
||||
|
||||
---
|
||||
|
||||
@@ -277,7 +277,15 @@ The lifetime (in seconds) of the authentication cookie issued to a NetBox user u
|
||||
|
||||
Default: False
|
||||
|
||||
Setting this to True will display a "maintenance mode" banner at the top of every page.
|
||||
Setting this to True will display a "maintenance mode" banner at the top of every page. Additionally, NetBox will no longer update a user's "last active" time upon login. This is to allow new logins when the database is in a read-only state. Recording of login times will resume when maintenance mode is disabled.
|
||||
|
||||
---
|
||||
|
||||
## MAPS_URL
|
||||
|
||||
Default: `https://maps.google.com/?q=` (Google Maps)
|
||||
|
||||
This specifies the URL to use when presenting a map of a physical location by street address or GPS coordinates. The URL must accept either a free-form street address or a comma-separated pair of numeric coordinates appended to it.
|
||||
|
||||
---
|
||||
|
||||
@@ -301,7 +309,7 @@ The file path to the location where media files (such as image attachments) are
|
||||
|
||||
Default: False
|
||||
|
||||
Toggle the availability Prometheus-compatible metrics at `/metrics`. See the [Prometheus Metrics](../../additional-features/prometheus-metrics/) documentation for more details.
|
||||
Toggle the availability Prometheus-compatible metrics at `/metrics`. See the [Prometheus Metrics](../additional-features/prometheus-metrics.md) documentation for more details.
|
||||
|
||||
---
|
||||
|
||||
@@ -318,7 +326,7 @@ NetBox will use these credentials when authenticating to remote devices via the
|
||||
|
||||
## NAPALM_ARGS
|
||||
|
||||
A dictionary of optional arguments to pass to NAPALM when instantiating a network driver. See the NAPALM documentation for a [complete list of optional arguments](http://napalm.readthedocs.io/en/latest/support/#optional-arguments). An example:
|
||||
A dictionary of optional arguments to pass to NAPALM when instantiating a network driver. See the NAPALM documentation for a [complete list of optional arguments](https://napalm.readthedocs.io/en/latest/support/#optional-arguments). An example:
|
||||
|
||||
```python
|
||||
NAPALM_ARGS = {
|
||||
@@ -460,7 +468,7 @@ NetBox can be configured to support remote user authentication by inferring user
|
||||
|
||||
Default: `'HTTP_REMOTE_USER'`
|
||||
|
||||
When remote user authentication is in use, this is the name of the HTTP header which informs NetBox of the currently authenticated user. (Requires `REMOTE_AUTH_ENABLED`.)
|
||||
When remote user authentication is in use, this is the name of the HTTP header which informs NetBox of the currently authenticated user. For example, to use the request header `X-Remote-User` it needs to be set to `HTTP_X_REMOTE_USER`. (Requires `REMOTE_AUTH_ENABLED`.)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
This is a list of valid fully-qualified domain names (FQDNs) and/or IP addresses that can be used to reach the NetBox service. Usually this is the same as the hostname for the NetBox server, but can also be different; for example, when using a reverse proxy serving the NetBox website under a different FQDN than the hostname of the NetBox server. To help guard against [HTTP Host header attackes](https://docs.djangoproject.com/en/3.0/topics/security/#host-headers-virtual-hosting), NetBox will not permit access to the server via any other hostnames (or IPs).
|
||||
|
||||
!!! note
|
||||
This parameter must always be defined as a list or tuple, even if only value is provided.
|
||||
This parameter must always be defined as a list or tuple, even if only a single value is provided.
|
||||
|
||||
The value of this option is also used to set `CSRF_TRUSTED_ORIGINS`, which restricts POST requests to the same set of hosts (more about this [here](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS)). Keep in mind that NetBox, by default, sets `USE_X_FORWARDED_HOST` to true, which means that if you're using a reverse proxy, it's the FQDN used to reach that reverse proxy which needs to be in this list (more about this [here](https://docs.djangoproject.com/en/stable/ref/settings/#allowed-hosts)).
|
||||
|
||||
@@ -66,6 +66,7 @@ Redis is configured using a configuration setting similar to `DATABASE` and thes
|
||||
* `PASSWORD` - Redis password (if set)
|
||||
* `DATABASE` - Numeric database ID
|
||||
* `SSL` - Use SSL connection to Redis
|
||||
* `INSECURE_SKIP_TLS_VERIFY` - Set to `True` to **disable** TLS certificate verification (not recommended)
|
||||
|
||||
An example configuration is provided below:
|
||||
|
||||
@@ -101,7 +102,7 @@ REDIS = {
|
||||
|
||||
If you are using [Redis Sentinel](https://redis.io/topics/sentinel) for high-availability purposes, there is minimal
|
||||
configuration necessary to convert NetBox to recognize it. It requires the removal of the `HOST` and `PORT` keys from
|
||||
above and the addition of two new keys.
|
||||
above and the addition of three new keys.
|
||||
|
||||
* `SENTINELS`: List of tuples or tuple of tuples with each inner tuple containing the name or IP address
|
||||
of the Redis server and port for each sentinel instance to connect to
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Circuits
|
||||
|
||||
{!docs/models/circuits/provider.md!}
|
||||
{!docs/models/circuits/providernetwork.md!}
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
## Device Components
|
||||
|
||||
Device components represent discrete objects within a device which are used to terminate cables, house child devices, or track resources.
|
||||
|
||||
{!docs/models/dcim/consoleport.md!}
|
||||
{!docs/models/dcim/consoleserverport.md!}
|
||||
{!docs/models/dcim/powerport.md!}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Sites and Racks
|
||||
|
||||
{!docs/models/dcim/site.md!}
|
||||
{!docs/models/dcim/region.md!}
|
||||
{!docs/models/dcim/sitegroup.md!}
|
||||
{!docs/models/dcim/site.md!}
|
||||
{!docs/models/dcim/location.md!}
|
||||
|
||||
---
|
||||
|
||||
{!docs/models/dcim/rack.md!}
|
||||
{!docs/models/dcim/rackgroup.md!}
|
||||
{!docs/models/dcim/rackrole.md!}
|
||||
{!docs/models/dcim/rackreservation.md!}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Application Registry
|
||||
|
||||
The registry is an in-memory data structure which houses various miscellaneous application-wide parameters, such as installed plugins. It is not exposed to the user and is not intended to be modified by any code outside of NetBox core.
|
||||
The registry is an in-memory data structure which houses various application-wide parameters, such as the list of enabled plugins. It is not exposed to the user and is not intended to be modified by any code outside of NetBox core.
|
||||
|
||||
The registry behaves essentially like a Python dictionary, with the notable exception that once a store (key) has been declared, it cannot be deleted or overwritten. The value of a store can, however, me modified; e.g. by appending a value to a list. Store values generally do not change once the application has been initialized.
|
||||
The registry behaves essentially like a Python dictionary, with the notable exception that once a store (key) has been declared, it cannot be deleted or overwritten. The value of a store can, however, be modified; e.g. by appending a value to a list. Store values generally do not change once the application has been initialized.
|
||||
|
||||
The registry can be inspected by importing `registry` from `extras.registry`.
|
||||
|
||||
## Stores
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Extending Models
|
||||
|
||||
Below is a list of items to consider when adding a new field to a model:
|
||||
Below is a list of tasks to consider when adding a new field to a core model.
|
||||
|
||||
## 1. Generate and run database migration
|
||||
## 1. Generate and run database migrations
|
||||
|
||||
Django migrations are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
|
||||
|
||||
@@ -14,18 +14,18 @@ Django migrations are used to express changes to the database schema. In most ca
|
||||
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in the same migration. You can merge a new migration with an existing one by combining their `operations` lists.
|
||||
|
||||
!!! note
|
||||
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered.
|
||||
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered (other than for the purpose of correcting a bug).
|
||||
|
||||
## 2. Add validation logic to `clean()`
|
||||
|
||||
If the new field introduces additional validation requirements (beyond what's included with the field itself), implement them in the model's `clean()` method. Remember to call the model's original method using `super()` before or agter your custom validation as appropriate:
|
||||
If the new field introduces additional validation requirements (beyond what's included with the field itself), implement them in the model's `clean()` method. Remember to call the model's original method using `super()` before or after your custom validation as appropriate:
|
||||
|
||||
```
|
||||
class Foo(models.Model):
|
||||
|
||||
def clean(self):
|
||||
|
||||
super(DeviceCSVForm, self).clean()
|
||||
super().clean()
|
||||
|
||||
# Custom validation goes here
|
||||
if self.bar is None:
|
||||
@@ -38,7 +38,7 @@ Add the name of the new field to `csv_headers` and included a CSV-friendly repre
|
||||
|
||||
## 4. Update relevant querysets
|
||||
|
||||
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups.
|
||||
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid extraneous database queries.
|
||||
|
||||
## 5. Update API serializer
|
||||
|
||||
@@ -49,7 +49,7 @@ Extend the model's API serializer in `<app>.api.serializers` to include the new
|
||||
Extend any forms to include the new field as appropriate. Common forms include:
|
||||
|
||||
* **Credit/edit** - Manipulating a single object
|
||||
* **Bulk edit** - Performing a change on mnay objects at once
|
||||
* **Bulk edit** - Performing a change on many objects at once
|
||||
* **CSV import** - The form used when bulk importing objects in CSV format
|
||||
* **Filter** - Displays the options available for filtering a list of objects (both UI and API)
|
||||
|
||||
@@ -59,7 +59,7 @@ If the new field should be filterable, add it to the `FilterSet` for the model.
|
||||
|
||||
## 8. Add column to object table
|
||||
|
||||
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require explicitly declaring a new column.
|
||||
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require declaring a custom column.
|
||||
|
||||
## 9. Update the UI templates
|
||||
|
||||
@@ -76,3 +76,7 @@ Create or extend the relevant test cases to verify that the new field and any ac
|
||||
* View tests
|
||||
|
||||
Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality.
|
||||
|
||||
## 11. Update the model's documentation
|
||||
|
||||
Each model has a dedicated page in the documentation, at `models/<app>/<model>.md`. Update this file to include any relevant information about the new field.
|
||||
|
||||
140
docs/development/getting-started.md
Normal file
140
docs/development/getting-started.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Getting Started
|
||||
|
||||
## Setting up a Development Environment
|
||||
|
||||
Getting started with NetBox development is pretty straightforward, and should feel very familiar to anyone with Django development experience. There are a few things you'll need:
|
||||
|
||||
* A Linux system or environment
|
||||
* A PostgreSQL server, which can be installed locally [per the documentation](../installation/1-postgresql.md)
|
||||
* A Redis server, which can also be [installed locally](../installation/2-redis.md)
|
||||
* A supported version of Python
|
||||
|
||||
### Fork the Repo
|
||||
|
||||
Assuming you'll be working on your own fork, your first step will be to fork the [official git repository](https://github.com/netbox-community/netbox). (If you're a maintainer who's going to be working directly with the official repo, skip this step.) You can then clone your GitHub fork locally for development:
|
||||
|
||||
```no-highlight
|
||||
$ git clone https://github.com/youruseraccount/netbox.git
|
||||
Cloning into 'netbox'...
|
||||
remote: Enumerating objects: 231, done.
|
||||
remote: Counting objects: 100% (231/231), done.
|
||||
remote: Compressing objects: 100% (147/147), done.
|
||||
remote: Total 56705 (delta 134), reused 145 (delta 84), pack-reused 56474
|
||||
Receiving objects: 100% (56705/56705), 27.96 MiB | 34.92 MiB/s, done.
|
||||
Resolving deltas: 100% (44177/44177), done.
|
||||
$ ls netbox/
|
||||
base_requirements.txt contrib docs mkdocs.yml NOTICE requirements.txt upgrade.sh
|
||||
CHANGELOG.md CONTRIBUTING.md LICENSE.txt netbox README.md scripts
|
||||
```
|
||||
|
||||
The NetBox project utilizes three persistent git branches to track work:
|
||||
|
||||
* `master` - Serves as a snapshot of the current stable release
|
||||
* `develop` - All development on the upcoming stable release occurs here
|
||||
* `feature` - Tracks work on an upcoming major release
|
||||
|
||||
Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. **Never** merge pull requests into the `master` branch, which receives merged only from the `develop` branch.
|
||||
|
||||
### Enable Pre-Commit Hooks
|
||||
|
||||
NetBox ships with a [git pre-commit hook](https://githooks.com/) script that automatically checks for style compliance and missing database migrations prior to committing changes. This helps avoid erroneous commits that result in CI test failures. You are encouraged to enable it by creating a link to `scripts/git-hooks/pre-commit`:
|
||||
|
||||
```no-highlight
|
||||
$ cd .git/hooks/
|
||||
$ ln -s ../../scripts/git-hooks/pre-commit
|
||||
```
|
||||
|
||||
### Create a Python Virtual Environment
|
||||
|
||||
A [virtual environment](https://docs.python.org/3/tutorial/venv.html) is like a container for a set of Python packages. They allow you to build environments suited to specific projects without interfering with system packages or other projects. When installed per the documentation, NetBox uses a virtual environment in production.
|
||||
|
||||
Create a virtual environment using the `venv` Python module:
|
||||
|
||||
```no-highlight
|
||||
$ mkdir ~/.venv
|
||||
$ python3 -m venv ~/.venv/netbox
|
||||
```
|
||||
|
||||
This will create a directory named `.venv/netbox/` in your home directory, which houses a virtual copy of the Python executable and its related libraries and tooling. When running NetBox for development, it will be run using the Python binary at `~/.venv/netbox/bin/python`.
|
||||
|
||||
!!! info
|
||||
Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created wherever you please.
|
||||
|
||||
Once created, activate the virtual environment:
|
||||
|
||||
```no-highlight
|
||||
$ source ~/.venv/netbox/bin/activate
|
||||
(netbox) $
|
||||
```
|
||||
|
||||
Notice that the console prompt changes to indicate the active environment. This updates the necessary system environment variables to ensure that any Python scripts are run within the virtual environment.
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
With the virtual environment activated, install the project's required Python packages using the `pip` module:
|
||||
|
||||
```no-highlight
|
||||
(netbox) $ python -m pip install -r requirements.txt
|
||||
Collecting Django==3.1 (from -r requirements.txt (line 1))
|
||||
Cache entry deserialization failed, entry ignored
|
||||
Using cached https://files.pythonhosted.org/packages/2b/5a/4bd5624546912082a1bd2709d0edc0685f5c7827a278d806a20cf6adea28/Django-3.1-py3-none-any.whl
|
||||
...
|
||||
```
|
||||
|
||||
### Configure NetBox
|
||||
|
||||
Within the `netbox/netbox/` directory, copy `configuration.example.py` to `configuration.py` and update the following parameters:
|
||||
|
||||
* `ALLOWED_HOSTS`: This can be set to `['*']` for development purposes
|
||||
* `DATABASE`: PostgreSQL database connection parameters
|
||||
* `REDIS`: Redis configuration, if different from the defaults
|
||||
* `SECRET_KEY`: Set to a random string (use `generate_secret_key.py` in the parent directory to generate a suitable key)
|
||||
* `DEBUG`: Set to `True`
|
||||
* `DEVELOPER`: Set to `True` (this enables the creation of new database migrations)
|
||||
|
||||
### Start the Development Server
|
||||
|
||||
Django provides a lightweight, auto-updating HTTP/WSGI server for development use. NetBox extends this slightly to automatically import models and other utilities. Run the NetBox development server with the `nbshell` management command:
|
||||
|
||||
```no-highlight
|
||||
$ python netbox/manage.py runserver
|
||||
Performing system checks...
|
||||
|
||||
System check identified no issues (0 silenced).
|
||||
November 18, 2020 - 15:52:31
|
||||
Django version 3.1, using settings 'netbox.settings'
|
||||
Starting development server at http://127.0.0.1:8000/
|
||||
Quit the server with CONTROL-C.
|
||||
```
|
||||
|
||||
This ensures that your development environment is now complete and operational. Any changes you make to the code base will be automatically adapted by the development server.
|
||||
|
||||
## Running Tests
|
||||
|
||||
Throughout the course of development, it's a good idea to occasionally run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command:
|
||||
|
||||
```no-highlight
|
||||
$ python netbox/manage.py test
|
||||
```
|
||||
|
||||
In cases where you haven't made any changes to the database (which is most of the time), you can append the `--keepdb` argument to this command to reuse the test database between runs. This cuts down on the time it takes to run the test suite since the database doesn't have to be rebuilt each time. (Note that this argument will cause errors if you've modified any model fields since the previous test run.)
|
||||
|
||||
```no-highlight
|
||||
$ python netbox/manage.py test --keepdb
|
||||
```
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. When working on a specific issue, be sure to reference it.
|
||||
|
||||
```no-highlight
|
||||
$ git commit -m "Closes #1234: Add IPv5 support"
|
||||
$ git push origin
|
||||
```
|
||||
|
||||
Once your fork has the new commit, submit a [pull request](https://github.com/netbox-community/netbox/compare) to the NetBox repo to propose the changes. Be sure to provide a detailed accounting of the changes being made and the reasons for doing so.
|
||||
|
||||
Once submitted, a maintainer will review your pull request and either merge it or request changes. If changes are needed, you can make them via new commits to your fork: The pull request will update automatically.
|
||||
|
||||
!!! note
|
||||
Remember, pull requests are entertained only for **accepted** issues. If an issue you want to work on hasn't been approved by a maintainer yet, it's best to avoid risking your time and effort on a change that might not be accepted.
|
||||
@@ -4,11 +4,12 @@ NetBox is maintained as a [GitHub project](https://github.com/netbox-community/n
|
||||
|
||||
## Communication
|
||||
|
||||
Communication among developers should always occur via public channels:
|
||||
There are several official forums for communication among the developers and community members:
|
||||
|
||||
* [GitHub issues](https://github.com/netbox-community/netbox/issues) - All feature requests, bug reports, and other substantial changes to the code base **must** be documented in an issue.
|
||||
* [The mailing list](https://groups.google.com/g/netbox-discuss) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
|
||||
* [#netbox on NetworkToCode](http://slack.networktocode.com/) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained long.
|
||||
* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
|
||||
* [#netbox on NetDev Community Slack](https://slack.netbox.dev/) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained long.
|
||||
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being phased out in favor of GitHub discussions.
|
||||
|
||||
## Governance
|
||||
|
||||
@@ -18,7 +19,7 @@ NetBox follows the [benevolent dictator](http://oss-watch.ac.uk/resources/benevo
|
||||
|
||||
All development of the current NetBox release occurs in the `develop` branch; releases are packaged from the `master` branch. The `master` branch should _always_ represent the current stable release in its entirety, such that installing NetBox by either downloading a packaged release or cloning the `master` branch provides the same code base.
|
||||
|
||||
NetBox components are arranged into functional subsections called _apps_ (a carryover from Django verancular). Each app holds the models, views, and templates relevant to a particular function:
|
||||
NetBox components are arranged into functional subsections called _apps_ (a carryover from Django vernacular). Each app holds the models, views, and templates relevant to a particular function:
|
||||
|
||||
* `circuits`: Communications circuits and providers (not to be confused with power circuits)
|
||||
* `dcim`: Datacenter infrastructure management (sites, racks, and devices)
|
||||
@@ -26,5 +27,6 @@ NetBox components are arranged into functional subsections called _apps_ (a carr
|
||||
* `ipam`: IP address management (VRFs, prefixes, IP addresses, and VLANs)
|
||||
* `secrets`: Encrypted storage of sensitive data (e.g. login credentials)
|
||||
* `tenancy`: Tenants (such as customers) to which NetBox objects may be assigned
|
||||
* `users`: Authentication and user preferences
|
||||
* `utilities`: Resources which are not user-facing (extendable classes, etc.)
|
||||
* `virtualization`: Virtual machines and clusters
|
||||
|
||||
98
docs/development/models.md
Normal file
98
docs/development/models.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# NetBox Models
|
||||
|
||||
## Model Types
|
||||
|
||||
A NetBox model represents a discrete object type such as a device or IP address. Each model is defined as a Python class and has its own SQL table. All NetBox data models can be categorized by type.
|
||||
|
||||
The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/) framework can be used to reference models within the database. A ContentType instance references a model by its `app_label` and `name`: For example, the Site model is referred to as `dcim.site`. The content type combined with an object's primary key form a globally unique identifier for the object (e.g. `dcim.site:123`).
|
||||
|
||||
### Features Matrix
|
||||
|
||||
* [Change logging](../additional-features/change-logging.md) - Changes to these objects are automatically recorded in the change log
|
||||
* [Webhooks](../additional-features/webhooks.md) - NetBox is capable of generating outgoing webhooks for these objects
|
||||
* [Custom fields](../additional-features/custom-fields.md) - These models support the addition of user-defined fields
|
||||
* [Export templates](../additional-features/export-templates.md) - Users can create custom export templates for these models
|
||||
* [Tagging](../models/extras/tag.md) - The models can be tagged with user-defined tags
|
||||
* [Journaling](../additional-features/journaling.md) - These models support persistent historical commentary
|
||||
* Nesting - These models can be nested recursively to create a hierarchy
|
||||
|
||||
| Type | Change Logging | Webhooks | Custom Fields | Export Templates | Tags | Journaling | Nesting |
|
||||
| ------------------ | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- |
|
||||
| Primary | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | |
|
||||
| Organizational | :material-check: | :material-check: | :material-check: | :material-check: | | | |
|
||||
| Nested Group | :material-check: | :material-check: | :material-check: | :material-check: | | | :material-check: |
|
||||
| Component | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | | |
|
||||
| Component Template | :material-check: | :material-check: | :material-check: | | | | |
|
||||
|
||||
## Models Index
|
||||
|
||||
### Primary Models
|
||||
|
||||
* [circuits.Circuit](../models/circuits/circuit.md)
|
||||
* [circuits.Provider](../models/circuits/provider.md)
|
||||
* [circuits.ProviderNetwork](../models/circuits/providernetwork.md)
|
||||
* [dcim.Cable](../models/dcim/cable.md)
|
||||
* [dcim.Device](../models/dcim/device.md)
|
||||
* [dcim.DeviceType](../models/dcim/devicetype.md)
|
||||
* [dcim.PowerFeed](../models/dcim/powerfeed.md)
|
||||
* [dcim.PowerPanel](../models/dcim/powerpanel.md)
|
||||
* [dcim.Rack](../models/dcim/rack.md)
|
||||
* [dcim.RackReservation](../models/dcim/rackreservation.md)
|
||||
* [dcim.Site](../models/dcim/site.md)
|
||||
* [dcim.VirtualChassis](../models/dcim/virtualchassis.md)
|
||||
* [ipam.Aggregate](../models/ipam/aggregate.md)
|
||||
* [ipam.IPAddress](../models/ipam/ipaddress.md)
|
||||
* [ipam.Prefix](../models/ipam/prefix.md)
|
||||
* [ipam.RouteTarget](../models/ipam/routetarget.md)
|
||||
* [ipam.Service](../models/ipam/service.md)
|
||||
* [ipam.VLAN](../models/ipam/vlan.md)
|
||||
* [ipam.VRF](../models/ipam/vrf.md)
|
||||
* [secrets.Secret](../models/secrets/secret.md)
|
||||
* [tenancy.Tenant](../models/tenancy/tenant.md)
|
||||
* [virtualization.Cluster](../models/virtualization/cluster.md)
|
||||
* [virtualization.VirtualMachine](../models/virtualization/virtualmachine.md)
|
||||
|
||||
### Organizational Models
|
||||
|
||||
* [circuits.CircuitType](../models/circuits/circuittype.md)
|
||||
* [dcim.DeviceRole](../models/dcim/devicerole.md)
|
||||
* [dcim.Manufacturer](../models/dcim/manufacturer.md)
|
||||
* [dcim.Platform](../models/dcim/platform.md)
|
||||
* [dcim.RackRole](../models/dcim/rackrole.md)
|
||||
* [ipam.RIR](../models/ipam/rir.md)
|
||||
* [ipam.Role](../models/ipam/role.md)
|
||||
* [ipam.VLANGroup](../models/ipam/vlangroup.md)
|
||||
* [secrets.SecretRole](../models/secrets/secretrole.md)
|
||||
* [virtualization.ClusterGroup](../models/virtualization/clustergroup.md)
|
||||
* [virtualization.ClusterType](../models/virtualization/clustertype.md)
|
||||
|
||||
### Nested Group Models
|
||||
|
||||
* [dcim.Location](../models/dcim/location.md) (formerly RackGroup)
|
||||
* [dcim.Region](../models/dcim/region.md)
|
||||
* [dcim.SiteGroup](../models/dcim/sitegroup.md)
|
||||
* [tenancy.TenantGroup](../models/tenancy/tenantgroup.md)
|
||||
|
||||
### Component Models
|
||||
|
||||
* [dcim.ConsolePort](../models/dcim/consoleport.md)
|
||||
* [dcim.ConsoleServerPort](../models/dcim/consoleserverport.md)
|
||||
* [dcim.DeviceBay](../models/dcim/devicebay.md)
|
||||
* [dcim.FrontPort](../models/dcim/frontport.md)
|
||||
* [dcim.Interface](../models/dcim/interface.md)
|
||||
* [dcim.InventoryItem](../models/dcim/inventoryitem.md)
|
||||
* [dcim.PowerOutlet](../models/dcim/poweroutlet.md)
|
||||
* [dcim.PowerPort](../models/dcim/powerport.md)
|
||||
* [dcim.RearPort](../models/dcim/rearport.md)
|
||||
* [virtualization.VMInterface](../models/virtualization/vminterface.md)
|
||||
|
||||
### Component Template Models
|
||||
|
||||
* [dcim.ConsolePortTemplate](../models/dcim/consoleporttemplate.md)
|
||||
* [dcim.ConsoleServerPortTemplate](../models/dcim/consoleserverporttemplate.md)
|
||||
* [dcim.DeviceBayTemplate](../models/dcim/devicebaytemplate.md)
|
||||
* [dcim.FrontPortTemplate](../models/dcim/frontporttemplate.md)
|
||||
* [dcim.InterfaceTemplate](../models/dcim/interfacetemplate.md)
|
||||
* [dcim.PowerOutletTemplate](../models/dcim/poweroutlettemplate.md)
|
||||
* [dcim.PowerPortTemplate](../models/dcim/powerporttemplate.md)
|
||||
* [dcim.RearPortTemplate](../models/dcim/rearporttemplate.md)
|
||||
@@ -2,42 +2,23 @@
|
||||
|
||||
## Minor Version Bumps
|
||||
|
||||
### Update Requirements
|
||||
### Address Pinned Dependencies
|
||||
|
||||
Required Python packages are maintained in two files. `base_requirements.txt` contains a list of all the packages required by NetBox. Some of them may be pinned to a specific version of the package due to a known issue. For example:
|
||||
|
||||
```
|
||||
# https://github.com/encode/django-rest-framework/issues/6053
|
||||
djangorestframework==3.8.1
|
||||
```
|
||||
|
||||
The other file is `requirements.txt`, which lists each of the required packages pinned to its current stable version. When NetBox is installed, the Python environment is configured to match this file. This helps ensure that a new release of a dependency doesn't break NetBox.
|
||||
|
||||
Every minor version release should refresh `requirements.txt` so that it lists the most recent stable release of each package. To do this:
|
||||
|
||||
1. Create a new virtual environment.
|
||||
2. Install the latest version of all required packages via pip:
|
||||
|
||||
```
|
||||
pip install -U -r base_requirements.txt
|
||||
```
|
||||
|
||||
3. Run all tests and check that the UI and API function as expected.
|
||||
4. Update the package versions in `requirements.txt` as appropriate.
|
||||
Check `base_requirements.txt` for any dependencies pinned to a specific version, and upgrade them to their most stable release (where possible).
|
||||
|
||||
### Update Static Libraries
|
||||
|
||||
Update the following static libraries to their most recent stable release:
|
||||
|
||||
* Bootstrap 3
|
||||
* Font Awesome 4
|
||||
* Material Design Icons
|
||||
* Select2
|
||||
* jQuery
|
||||
* jQuery UI
|
||||
|
||||
### Create a new Release Notes Page
|
||||
### Link to the Release Notes Page
|
||||
|
||||
Create a file at `/docs/release-notes/X.Y.md` to establish the release notes for the new release. Add the file to the table of contents within `mkdocs.yml`, and point `index.md` to the new file.
|
||||
Add the release notes (`/docs/release-notes/X.Y.md`) to the table of contents within `mkdocs.yml`, and point `index.md` to the new file.
|
||||
|
||||
### Manually Perform a New Install
|
||||
|
||||
@@ -52,23 +33,48 @@ Follow these instructions to perform a new installation of NetBox. This process
|
||||
|
||||
### Close the Release Milestone
|
||||
|
||||
Close the release milestone on GitHub. Ensure that there are no remaining open issues associated with it.
|
||||
Close the release milestone on GitHub after ensuring there are no remaining open issues associated with it.
|
||||
|
||||
### Merge the Release Branch
|
||||
|
||||
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release.
|
||||
|
||||
---
|
||||
|
||||
## All Releases
|
||||
|
||||
### Update Requirements
|
||||
|
||||
Required Python packages are maintained in two files. `base_requirements.txt` contains a list of all the packages required by NetBox. Some of them may be pinned to a specific version of the package due to a known issue. For example:
|
||||
|
||||
```
|
||||
# https://github.com/encode/django-rest-framework/issues/6053
|
||||
djangorestframework==3.8.1
|
||||
```
|
||||
|
||||
The other file is `requirements.txt`, which lists each of the required packages pinned to its current stable version. When NetBox is installed, the Python environment is configured to match this file. This helps ensure that a new release of a dependency doesn't break NetBox.
|
||||
|
||||
Every release should refresh `requirements.txt` so that it lists the most recent stable release of each package. To do this:
|
||||
|
||||
1. Create a new virtual environment.
|
||||
2. Install the latest version of all required packages `pip install -U -r base_requirements.txt`).
|
||||
3. Run all tests and check that the UI and API function as expected.
|
||||
4. Review each requirement's release notes for any breaking or otherwise noteworthy changes.
|
||||
5. Update the package versions in `requirements.txt` as appropriate.
|
||||
|
||||
In cases where upgrading a dependency to its most recent release is breaking, it should be pinned to its current minor version in `base_requirements.txt` (with an explanatory comment) and revisited for the next major NetBox release.
|
||||
|
||||
### Verify CI Build Status
|
||||
|
||||
Ensure that continuous integration testing on the `develop` branch is completing successfully.
|
||||
|
||||
### Update Version and Changelog
|
||||
|
||||
Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version.
|
||||
Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version. Commit these changes to the `develop` branch.
|
||||
|
||||
### Submit a Pull Request
|
||||
|
||||
Submit a pull request title **"Release vX.Y.Z"** to merge the `develop` branch into `master`. Include a brief change log listing the features, improvements, and/or bugs addressed in the release.
|
||||
Submit a pull request title **"Release vX.Y.Z"** to merge the `develop` branch into `master`. Copy the documented release notes into the pull request's body.
|
||||
|
||||
Once CI has completed on the PR, merge it.
|
||||
|
||||
@@ -76,16 +82,16 @@ Once CI has completed on the PR, merge it.
|
||||
|
||||
Draft a [new release](https://github.com/netbox-community/netbox/releases/new) with the following parameters.
|
||||
|
||||
* **Tag:** Current version (e.g. `v2.3.4`)
|
||||
* **Tag:** Current version (e.g. `v2.9.9`)
|
||||
* **Target:** `master`
|
||||
* **Title:** Version and date (e.g. `v2.3.4 - 2018-08-02`)
|
||||
* **Title:** Version and date (e.g. `v2.9.9 - 2020-11-09`)
|
||||
|
||||
Copy the description from the pull request into the release notes.
|
||||
Copy the description from the pull request to the release.
|
||||
|
||||
### Update the Development Version
|
||||
|
||||
On the `develop` branch, update `VERSION` in `settings.py` to point to the next release. For example, if you just released v2.3.4, set:
|
||||
On the `develop` branch, update `VERSION` in `settings.py` to point to the next release. For example, if you just released v2.9.9, set:
|
||||
|
||||
```
|
||||
VERSION = 'v2.3.5-dev'
|
||||
VERSION = 'v2.9.10-dev'
|
||||
```
|
||||
|
||||
@@ -5,8 +5,8 @@ NetBox generally follows the [Django style guide](https://docs.djangoproject.com
|
||||
## PEP 8 Exceptions
|
||||
|
||||
* Wildcard imports (for example, `from .constants import *`) are acceptable under any of the following conditions:
|
||||
* The library being import contains only constant declarations (`constants.py`)
|
||||
* The library being imported explicitly defines `__all__` (e.g. `<app>.api.nested_serializers`)
|
||||
* The library being import contains only constant declarations (e.g. `constants.py`)
|
||||
* The library being imported explicitly defines `__all__`
|
||||
|
||||
* Maximum line length is 120 characters (E501)
|
||||
* This does not apply to HTML templates or to automatically generated code (e.g. database migrations).
|
||||
@@ -45,7 +45,7 @@ When adding a new dependency, a short description of the package and the URL of
|
||||
|
||||
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and open a bug so that the entire code base can be evaluated at a later point.
|
||||
|
||||
* Prioritize readability over concision. Python is a very flexible language that typically gives us several options for expressing a given piece of logic, but some may be more friendly to the reader than others. (List comprehensions are particularly vulnerable to over-optimization.) Always remain considerate of the future reader who may need to interpret your code without the benefit of the context within which you are writing it.
|
||||
* Prioritize readability over concision. Python is a very flexible language that typically offers several options for expressing a given piece of logic, but some may be more friendly to the reader than others. (List comprehensions are particularly vulnerable to over-optimization.) Always remain considerate of the future reader who may need to interpret your code without the benefit of the context within which you are writing it.
|
||||
|
||||
* No easter eggs. While they can be fun, NetBox must be considered as a business-critical tool. The potential, however minor, for introducing a bug caused by unnecessary logic is best avoided entirely.
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ The `users.UserConfig` model holds individual preferences for each user in the f
|
||||
| ---- | ----------- |
|
||||
| extras.configcontext.format | Preferred format when rendering config context data (JSON or YAML) |
|
||||
| pagination.per_page | The number of items to display per page of a paginated table |
|
||||
| tables.${table_name}.columns | The ordered list of columns to display when viewing the table |
|
||||
| tables.TABLE_NAME.columns | The ordered list of columns to display when viewing the table |
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# Utility Views
|
||||
|
||||
Utility views are reusable views that handle common CRUD tasks, such as listing and updating objects. Some views operate on individual objects, whereas others (referred to as "bulk" views) operate on multiple objects at once.
|
||||
|
||||
## Individual Views
|
||||
|
||||
### ObjectView
|
||||
|
||||
Retrieve and display a single object.
|
||||
|
||||
### ObjectListView
|
||||
|
||||
Generates a paginated table of objects from a given queryset, which may optionally be filtered.
|
||||
|
||||
### ObjectEditView
|
||||
|
||||
Updates an object identified by a primary key (PK) or slug. If no existing object is specified, a new object will be created.
|
||||
|
||||
### ObjectDeleteView
|
||||
|
||||
Deletes an object. The user is redirected to a confirmation page before the deletion is executed.
|
||||
|
||||
## Bulk Views
|
||||
|
||||
### BulkCreateView
|
||||
|
||||
Creates multiple objects at once based on a given pattern. Currently used only for IP addresses.
|
||||
|
||||
### BulkImportView
|
||||
|
||||
Accepts CSV-formatted data and creates a new object for each line. Creation is all-or-none.
|
||||
|
||||
### BulkEditView
|
||||
|
||||
Applies changes to multiple objects at once in a two-step operation. First, the list of PKs for selected objects is POSTed and an edit form is presented to the user. On submission of that form, the specified changes are made to all selected objects.
|
||||
|
||||
### BulkDeleteView
|
||||
|
||||
Deletes multiple objects. The user selects the objects to be deleted and confirms the deletion.
|
||||
|
||||
## Component Views
|
||||
|
||||
### ComponentCreateView
|
||||
|
||||
Create one or more component objects beloning to a parent object (e.g. interfaces attached to a device).
|
||||
|
||||
### ComponentEditView
|
||||
|
||||
A subclass of `ObjectEditView`: Updates an individual component object.
|
||||
|
||||
### ComponentDeleteView
|
||||
|
||||
A subclass of `ObjectDeleteView`: Deletes an individual component object.
|
||||
|
||||
### BulkComponentCreateView
|
||||
|
||||
Create a set of components objects for each of a selected set of parent objects. This view can be used e.g. to create multiple interfaces on multiple devices at once.
|
||||
@@ -7,32 +7,31 @@ This section entails the installation and configuration of a local PostgreSQL da
|
||||
|
||||
## Installation
|
||||
|
||||
#### Ubuntu
|
||||
=== "Ubuntu"
|
||||
|
||||
Install the PostgreSQL server and client development libraries using `apt`.
|
||||
```no-highlight
|
||||
sudo apt update
|
||||
sudo apt install -y postgresql libpq-dev
|
||||
```
|
||||
|
||||
```no-highlight
|
||||
sudo apt update
|
||||
sudo apt install -y postgresql libpq-dev
|
||||
```
|
||||
=== "CentOS"
|
||||
|
||||
#### CentOS
|
||||
```no-highlight
|
||||
sudo yum install -y postgresql-server libpq-devel
|
||||
sudo postgresql-setup --initdb
|
||||
```
|
||||
|
||||
PostgreSQL 9.6 and later are available natively on CentOS 8.2. If using an earlier CentOS release, you may need to [install it from an RPM](https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/).
|
||||
!!! info
|
||||
PostgreSQL 9.6 and later are available natively on CentOS 8.2. If using an earlier CentOS release, you may need to [install it from an RPM](https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/).
|
||||
|
||||
```no-highlight
|
||||
sudo yum install -y postgresql-server libpq-devel
|
||||
sudo postgresql-setup --initdb
|
||||
```
|
||||
CentOS configures ident host-based authentication for PostgreSQL by default. Because NetBox will need to authenticate using a username and password, modify `/var/lib/pgsql/data/pg_hba.conf` to support MD5 authentication by changing `ident` to `md5` for the lines below:
|
||||
|
||||
CentOS configures ident host-based authentication for PostgreSQL by default. Because NetBox will need to authenticate using a username and password, modify `/var/lib/pgsql/data/pg_hba.conf` to support MD5 authentication by changing `ident` to `md5` for the lines below:
|
||||
```no-highlight
|
||||
host all all 127.0.0.1/32 md5
|
||||
host all all ::1/128 md5
|
||||
```
|
||||
|
||||
```no-highlight
|
||||
host all all 127.0.0.1/32 md5
|
||||
host all all ::1/128 md5
|
||||
```
|
||||
|
||||
Then, start the service and enable it to run at boot:
|
||||
Once PostgreSQL has been installed, start the service and enable it to run at boot:
|
||||
|
||||
```no-highlight
|
||||
sudo systemctl start postgresql
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
!!! note
|
||||
NetBox v2.9.0 and later require Redis v4.0 or higher. If your distribution does not offer a recent enough release, you will need to build Redis from source. Please see [the Redis installation documentation](https://github.com/redis/redis) for further details.
|
||||
|
||||
### Ubuntu
|
||||
=== "Ubuntu"
|
||||
|
||||
```no-highlight
|
||||
sudo apt install -y redis-server
|
||||
```
|
||||
```no-highlight
|
||||
sudo apt install -y redis-server
|
||||
```
|
||||
|
||||
### CentOS
|
||||
=== "CentOS"
|
||||
|
||||
```no-highlight
|
||||
sudo yum install -y redis
|
||||
sudo systemctl start redis
|
||||
sudo systemctl enable redis
|
||||
```
|
||||
```no-highlight
|
||||
sudo yum install -y redis
|
||||
sudo systemctl start redis
|
||||
sudo systemctl enable redis
|
||||
```
|
||||
|
||||
You may wish to modify the Redis configuration at `/etc/redis.conf` or `/etc/redis/redis.conf`, however in most cases the default configuration is sufficient.
|
||||
|
||||
|
||||
@@ -7,19 +7,19 @@ This section of the documentation discusses installing and configuring the NetBo
|
||||
Begin by installing all system packages required by NetBox and its dependencies.
|
||||
|
||||
!!! note
|
||||
NetBox v2.8.0 and later require Python 3.6, 3.7, or 3.8. This documentation assumes Python 3.6.
|
||||
NetBox v2.8.0 and later require Python 3.6, 3.7, or 3.8.
|
||||
|
||||
### Ubuntu
|
||||
=== "Ubuntu"
|
||||
|
||||
```no-highlight
|
||||
sudo apt install -y python3.6 python3-pip python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev zlib1g-dev
|
||||
```
|
||||
```no-highlight
|
||||
sudo apt install -y python3 python3-pip python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev zlib1g-dev
|
||||
```
|
||||
|
||||
### CentOS
|
||||
=== "CentOS"
|
||||
|
||||
```no-highlight
|
||||
sudo yum install -y gcc python36 python36-devel python3-pip libxml2-devel libxslt-devel libffi-devel openssl-devel redhat-rpm-config
|
||||
```
|
||||
```no-highlight
|
||||
sudo yum install -y gcc python36 python36-devel python3-pip libxml2-devel libxslt-devel libffi-devel openssl-devel redhat-rpm-config
|
||||
```
|
||||
|
||||
Before continuing with either platform, update pip (Python's package management tool) to its latest release:
|
||||
|
||||
@@ -57,17 +57,17 @@ sudo mkdir -p /opt/netbox/ && cd /opt/netbox/
|
||||
|
||||
If `git` is not already installed, install it:
|
||||
|
||||
#### Ubuntu
|
||||
=== "Ubuntu"
|
||||
|
||||
```no-highlight
|
||||
sudo apt install -y git
|
||||
```
|
||||
```no-highlight
|
||||
sudo apt install -y git
|
||||
```
|
||||
|
||||
#### CentOS
|
||||
=== "CentOS"
|
||||
|
||||
```no-highlight
|
||||
sudo yum install -y git
|
||||
```
|
||||
```no-highlight
|
||||
sudo yum install -y git
|
||||
```
|
||||
|
||||
Next, clone the **master** branch of the NetBox GitHub repository into the current directory. (This branch always holds the current stable release.)
|
||||
|
||||
@@ -83,26 +83,26 @@ Checking connectivity... done.
|
||||
```
|
||||
|
||||
!!! note
|
||||
Installation via git also allows you to easily try out development versions of NetBox. The `develop` branch contains all work underway for the next minor release, and the `develop-x.y` branch (if present) tracks progress on the next major release.
|
||||
Installation via git also allows you to easily try out development versions of NetBox. The `develop` branch contains all work underway for the next minor release, and the `feature` branch tracks progress on the next major release.
|
||||
|
||||
## Create the NetBox System User
|
||||
|
||||
Create a system user account named `netbox`. We'll configure the WSGI and HTTP services to run under this account. We'll also assign this user ownership of the media directory. This ensures that NetBox will be able to save uploaded files.
|
||||
|
||||
#### Ubuntu
|
||||
=== "Ubuntu"
|
||||
|
||||
```
|
||||
sudo adduser --system --group netbox
|
||||
sudo chown --recursive netbox /opt/netbox/netbox/media/
|
||||
```
|
||||
```
|
||||
sudo adduser --system --group netbox
|
||||
sudo chown --recursive netbox /opt/netbox/netbox/media/
|
||||
```
|
||||
|
||||
#### CentOS
|
||||
=== "CentOS"
|
||||
|
||||
```
|
||||
sudo groupadd --system netbox
|
||||
sudo adduser --system -g netbox netbox
|
||||
sudo chown --recursive netbox /opt/netbox/netbox/media/
|
||||
```
|
||||
```
|
||||
sudo groupadd --system netbox
|
||||
sudo adduser --system -g netbox netbox
|
||||
sudo chown --recursive netbox /opt/netbox/netbox/media/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -113,7 +113,7 @@ cd /opt/netbox/netbox/netbox/
|
||||
sudo cp configuration.example.py configuration.py
|
||||
```
|
||||
|
||||
Open `configuration.py` with your preferred editor to begin configuring NetBox. NetBox offers [many configuration parameters](/configuration/), but only the following four are required for new installations:
|
||||
Open `configuration.py` with your preferred editor to begin configuring NetBox. NetBox offers [many configuration parameters](../configuration/index.md), but only the following four are required for new installations:
|
||||
|
||||
* `ALLOWED_HOSTS`
|
||||
* `DATABASE`
|
||||
@@ -136,7 +136,7 @@ ALLOWED_HOSTS = ['*']
|
||||
|
||||
### DATABASE
|
||||
|
||||
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, update the `HOST` and `PORT` parameters accordingly. See the [configuration documentation](/configuration/required-settings/#database) for more detail on individual parameters.
|
||||
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, update the `HOST` and `PORT` parameters accordingly. See the [configuration documentation](../configuration/required-settings.md#database) for more detail on individual parameters.
|
||||
|
||||
```python
|
||||
DATABASE = {
|
||||
@@ -151,7 +151,7 @@ DATABASE = {
|
||||
|
||||
### REDIS
|
||||
|
||||
Redis is a in-memory key-value store used by NetBox for caching and background task queuing. Redis typically requires minimal configuration; the values below should suffice for most installations. See the [configuration documentation](/configuration/required-settings/#redis) for more detail on individual parameters.
|
||||
Redis is a in-memory key-value store used by NetBox for caching and background task queuing. Redis typically requires minimal configuration; the values below should suffice for most installations. See the [configuration documentation](../configuration/required-settings.md#redis) for more detail on individual parameters.
|
||||
|
||||
Note that NetBox requires the specification of two separate Redis databases: `tasks` and `caching`. These may both be provided by the same Redis service, however each should have a unique numeric database ID.
|
||||
|
||||
@@ -203,7 +203,7 @@ sudo echo napalm >> /opt/netbox/local_requirements.txt
|
||||
|
||||
### Remote File Storage
|
||||
|
||||
By default, NetBox will use the local filesystem to store uploaded files. To use a remote filesystem, install the [`django-storages`](https://django-storages.readthedocs.io/en/stable/) library and configure your [desired storage backend](/configuration/optional-settings/#storage_backend) in `configuration.py`.
|
||||
By default, NetBox will use the local filesystem to store uploaded files. To use a remote filesystem, install the [`django-storages`](https://django-storages.readthedocs.io/en/stable/) library and configure your [desired storage backend](../configuration/optional-settings.md#storage_backend) in `configuration.py`.
|
||||
|
||||
```no-highlight
|
||||
sudo echo django-storages >> /opt/netbox/local_requirements.txt
|
||||
@@ -264,7 +264,7 @@ Quit the server with CONTROL-C.
|
||||
|
||||
Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, <http://127.0.0.1:8000/>. You should be greeted with the NetBox home page.
|
||||
|
||||
!!! warning
|
||||
!!! danger
|
||||
The development server is for development and testing purposes only. It is neither performant nor secure enough for production use. **Do not use it in production.**
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# HTTP Server Setup
|
||||
|
||||
This documentation provides example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/current/), though any HTTP server which supports WSGI should be compatible.
|
||||
This documentation provides example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](https://httpd.apache.org/docs/current/), though any HTTP server which supports WSGI should be compatible.
|
||||
|
||||
!!! info
|
||||
For the sake of brevity, only Ubuntu 20.04 instructions are provided here, these tasks not unique to NetBox and should carry over to other distributions with mininal changes. Please consult your distribution's documentation for assistance if needed.
|
||||
For the sake of brevity, only Ubuntu 20.04 instructions are provided here. These tasks are not unique to NetBox and should carry over to other distributions with minimal changes. Please consult your distribution's documentation for assistance if needed.
|
||||
|
||||
## Obtain an SSL Certificate
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ First, enable the LDAP authentication backend in `configuration.py`. (Be sure to
|
||||
REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'
|
||||
```
|
||||
|
||||
Next, create a file in the same directory as `configuration.py` (typically `/opt/netbox/netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](http://django-auth-ldap.readthedocs.io/).
|
||||
Next, create a file in the same directory as `configuration.py` (typically `/opt/netbox/netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](https://django-auth-ldap.readthedocs.io/).
|
||||
|
||||
### General Server Configuration
|
||||
|
||||
@@ -140,9 +140,9 @@ AUTH_LDAP_CACHE_TIMEOUT = 3600
|
||||
|
||||
## Troubleshooting LDAP
|
||||
|
||||
`systemctl restart netbox` restarts the Netbox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/messages`.
|
||||
`systemctl restart netbox` restarts the NetBox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/messages`.
|
||||
|
||||
For troubleshooting LDAP user/group queries, add or merge the following [logging](/configuration/optional-settings.md#logging) configuration to `configuration.py`:
|
||||
For troubleshooting LDAP user/group queries, add or merge the following [logging](../configuration/optional-settings.md#logging) configuration to `configuration.py`:
|
||||
|
||||
```python
|
||||
LOGGING = {
|
||||
|
||||
@@ -11,6 +11,10 @@ The following sections detail how to set up a new instance of NetBox:
|
||||
5. [HTTP server](5-http-server.md)
|
||||
6. [LDAP authentication](6-ldap.md) (optional)
|
||||
|
||||
The video below demonstrates the installation of NetBox v2.10.3 on Ubuntu 20.04 for your reference.
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/dFANGlxXEng" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## Requirements
|
||||
|
||||
| Dependency | Minimum Version |
|
||||
@@ -19,6 +23,9 @@ The following sections detail how to set up a new instance of NetBox:
|
||||
| PostgreSQL | 9.6 |
|
||||
| Redis | 4.0 |
|
||||
|
||||
!!! note
|
||||
Python 3.7 or later will be required in NetBox v2.12. Users are strongly encouraged to install NetBox using Python 3.7 or later for new deployments.
|
||||
|
||||
Below is a simplified overview of the NetBox application stack for reference:
|
||||
|
||||

|
||||
@@ -26,6 +33,3 @@ Below is a simplified overview of the NetBox application stack for reference:
|
||||
## Upgrading
|
||||
|
||||
If you are upgrading from an existing installation, please consult the [upgrading guide](upgrading.md).
|
||||
|
||||
!!! note
|
||||
Beginning with v2.5.9, the official documentation calls for systemd to be used for managing the WSGI workers in place of supervisord. Please see the instructions for [migrating to systemd](migrating-to-systemd.md) if you are still using supervisord.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Review the Release Notes
|
||||
|
||||
Prior to upgrading your NetBox instance, be sure to carefully review all [release notes](../../release-notes/) that have been published since your current version was released. Although the upgrade process typically does not involve additional work, certain releases may introduce breaking or backward-incompatible changes. These are called out in the release notes under the release in which the change went into effect.
|
||||
Prior to upgrading your NetBox instance, be sure to carefully review all [release notes](../release-notes/index.md) that have been published since your current version was released. Although the upgrade process typically does not involve additional work, certain releases may introduce breaking or backward-incompatible changes. These are called out in the release notes under the release in which the change went into effect.
|
||||
|
||||
## Update Dependencies to Required Versions
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
The association of a circuit with a particular site and/or device is modeled separately as a circuit termination. A circuit may have up to two terminations, labeled A and Z. A single-termination circuit can be used when you don't know (or care) about the far end of a circuit (for example, an Internet access circuit which connects to a transit provider). A dual-termination circuit is useful for tracking circuits which connect two sites.
|
||||
|
||||
Each circuit termination is tied to a site, and may optionally be connected via a cable to a specific device interface or port within that site. Each termination must be assigned a port speed, and can optionally be assigned an upstream speed if it differs from the downstream speed (a common scenario with e.g. DOCSIS cable modems). Fields are also available to track cross-connect and patch panel details.
|
||||
Each circuit termination is attached to either a site or to a provider network. Site terminations may optionally be connected via a cable to a specific device interface or port within that site. Each termination must be assigned a port speed, and can optionally be assigned an upstream speed if it differs from the downstream speed (a common scenario with e.g. DOCSIS cable modems). Fields are also available to track cross-connect and patch panel details.
|
||||
|
||||
In adherence with NetBox's philosophy of closely modeling the real world, a circuit may terminate only to a physical interface. For example, circuits may not terminate to LAG interfaces, which are virtual in nature. In such cases, a separate physical circuit is associated with each LAG member interface and each needs to be modeled discretely.
|
||||
In adherence with NetBox's philosophy of closely modeling the real world, a circuit may be connected only to a physical interface. For example, circuits may not terminate to LAG interfaces, which are virtual in nature. In such cases, a separate physical circuit is associated with each LAG member interface and each needs to be modeled discretely.
|
||||
|
||||
!!! note
|
||||
A circuit in NetBox represents a physical link, and cannot have more than two endpoints. When modeling a multi-point topology, each leg of the topology must be defined as a discrete circuit, with one end terminating within the provider's infrastructure.
|
||||
A circuit in NetBox represents a physical link, and cannot have more than two endpoints. When modeling a multi-point topology, each leg of the topology must be defined as a discrete circuit, with one end terminating within the provider's infrastructure. The provider network model is ideal for representing these networks.
|
||||
|
||||
5
docs/models/circuits/providernetwork.md
Normal file
5
docs/models/circuits/providernetwork.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Provider Networks
|
||||
|
||||
This model can be used to represent the boundary of a provider network, the details of which are unknown or unimportant to the NetBox user. For example, it might represent a provider's regional MPLS network to which multiple circuits provide connectivity.
|
||||
|
||||
Each provider network must be assigned to a provider. A circuit may terminate to either a provider network or to a site.
|
||||
@@ -8,7 +8,7 @@ A device is said to be full-depth if its installation on one rack face prevents
|
||||
|
||||
Each device must be instantiated from a pre-created device type, and its default components (console ports, power ports, interfaces, etc.) will be created automatically. (The device type associated with a device may be changed after its creation, however its components will not be updated retroactively.)
|
||||
|
||||
Each device must be assigned a site, device role, and operational status, and may optionally be assigned to a specific rack within a site. A platform, serial number, and asset tag may optionally be assigned to each device.
|
||||
Each device must be assigned a site, device role, and operational status, and may optionally be assigned to a specific location and/or rack within a site. A platform, serial number, and asset tag may optionally be assigned to each device.
|
||||
|
||||
Device names must be unique within a site, unless the device has been assigned to a tenant. Devices may also be unnamed.
|
||||
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
Interfaces in NetBox represent network interfaces used to exchange data with connected devices. On modern networks, these are most commonly Ethernet, but other types are supported as well. Each interface must be assigned a type, and may optionally be assigned a MAC address, MTU, and IEEE 802.1Q mode (tagged or access). Each interface can also be enabled or disabled, and optionally designated as management-only (for out-of-band management).
|
||||
|
||||
Interfaces may be physical or virtual in nature, but only physical interfaces may be connected via cables. Cables can connect interfaces to pass-through ports, circuit terminations, or other interfaces.
|
||||
!!! note
|
||||
Although devices and virtual machines both can have interfaces, a separate model is used for each. Thus, device interfaces have some properties that are not present on virtual machine interfaces and vice versa.
|
||||
|
||||
### Interface Types
|
||||
|
||||
Interfaces may be physical or virtual in nature, but only physical interfaces may be connected via cables. Cables can connect interfaces to pass-through ports, circuit terminations, or other interfaces. Virtual interfaces, such as 802.1Q-tagged subinterfaces, may be assigned to physical parent interfaces.
|
||||
|
||||
Physical interfaces may be arranged into a link aggregation group (LAG) and associated with a parent LAG (virtual) interface. LAG interfaces can be recursively nested to model bonding of trunk groups. Like all virtual interfaces, LAG interfaces cannot be connected physically.
|
||||
|
||||
IP addresses can be assigned to interfaces. VLANs can also be assigned to each interface as either tagged or untagged. (An interface may have only one untagged VLAN.)
|
||||
### IP Address Assignment
|
||||
|
||||
!!! note
|
||||
Although devices and virtual machines both can have interfaces, a separate model is used for each. Thus, device interfaces have some properties that are not present on virtual machine interfaces and vice versa.
|
||||
IP addresses can be assigned to interfaces. VLANs can also be assigned to each interface as either tagged or untagged. (An interface may have only one untagged VLAN.)
|
||||
|
||||
5
docs/models/dcim/location.md
Normal file
5
docs/models/dcim/location.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Locations
|
||||
|
||||
Racks and devices can be grouped by location within a site. A location may represent a floor, room, cage, or similar organizational unit. Locations can be nested to form a hierarchy. For example, you may have floors within a site, and rooms within a floor.
|
||||
|
||||
The name and facility ID of each rack within a location must be unique. (Racks not assigned to the same location may have identical names and/or facility IDs.)
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
A power panel represents the origin point in NetBox for electrical power being disseminated by one or more power feeds. In a data center environment, one power panel often serves a group of racks, with an individual power feed extending to each rack, though this is not always the case. It is common to have two sets of panels and feeds arranged in parallel to provide redundant power to each rack.
|
||||
|
||||
Each power panel must be assigned to a site, and may optionally be assigned to a particular rack group.
|
||||
Each power panel must be assigned to a site, and may optionally be assigned to a particular location within that site.
|
||||
|
||||
!!! note
|
||||
NetBox does not model the mechanism by which power is delivered to a power panel. Power panels define the root level of the power distribution hierarchy in NetBox.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Racks
|
||||
|
||||
The rack model represents a physical two- or four-post equipment rack in which devices can be installed. Each rack must be assigned to a site, and may optionally be assigned to a rack group and/or tenant. Racks can also be organized by user-defined functional roles.
|
||||
The rack model represents a physical two- or four-post equipment rack in which devices can be installed. Each rack must be assigned to a site, and may optionally be assigned to a location and/or tenant. Racks can also be organized by user-defined functional roles.
|
||||
|
||||
Rack height is measured in *rack units* (U); racks are commonly between 42U and 48U tall, but NetBox allows you to define racks of arbitrary height. A toggle is provided to indicate whether rack units are in ascending (from the ground up) or descending order.
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Rack Groups
|
||||
|
||||
Racks can be organized into groups, which can be nested into themselves similar to regions. As with sites, how you choose to designate rack groups will depend on the nature of your organization. For example, if each site represents a campus, each group might represent a building within a campus. If each site represents a building, each rack group might equate to a floor or room.
|
||||
|
||||
Each rack group must be assigned to a parent site, and rack groups may optionally be nested within a site to model a multi-level hierarchy. For example, you might have a tier of rooms beneath a tier of floors, all belonging to the same parent building (site).
|
||||
|
||||
The name and facility ID of each rack within a group must be unique. (Racks not assigned to the same rack group may have identical names and/or facility IDs.)
|
||||
3
docs/models/dcim/sitegroup.md
Normal file
3
docs/models/dcim/sitegroup.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Site Groups
|
||||
|
||||
Like regions, site groups can be used to organize sites. Whereas regions are intended to provide geographic organization, site groups can be used to classify sites by role or function. Also like regions, site groups can be nested to form a hierarchy. Sites which belong to a child group are also considered to be members of any of its parent groups.
|
||||
@@ -3,11 +3,13 @@
|
||||
Sometimes it is desirable to associate additional data with a group of devices or virtual machines to aid in automated configuration. For example, you might want to associate a set of syslog servers for all devices within a particular region. Context data enables the association of extra user-defined data with devices and virtual machines grouped by one or more of the following assignments:
|
||||
|
||||
* Region
|
||||
* Site group
|
||||
* Site
|
||||
* Device type (devices only)
|
||||
* Role
|
||||
* Platform
|
||||
* Cluster group
|
||||
* Cluster
|
||||
* Cluster group (VMs only)
|
||||
* Cluster (VMs only)
|
||||
* Tenant group
|
||||
* Tenant
|
||||
* Tag
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# VLANs
|
||||
|
||||
A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094) as defined in [IEEE 802.1Q](https://en.wikipedia.org/wiki/IEEE_802.1Q). Each VLAN may be assigned to a site, tenant, and/or VLAN group.
|
||||
A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094) as defined in [IEEE 802.1Q](https://en.wikipedia.org/wiki/IEEE_802.1Q). VLANs are arranged into VLAN groups to define scope and to enforce uniqueness.
|
||||
|
||||
Each VLAN must be assigned one of the following operational statuses:
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# VLAN Groups
|
||||
|
||||
VLAN groups can be used to organize VLANs within NetBox. Each group may optionally be assigned to a specific site, but a group cannot belong to multiple sites.
|
||||
VLAN groups can be used to organize VLANs within NetBox. Each VLAN group can be scoped to a particular region, site group, site, location, rack, cluster group, or cluster. Member VLANs will be available for assignment to devices and/or virtual machines within the specified scope.
|
||||
|
||||
Groups can also be used to enforce uniqueness: Each VLAN within a group must have a unique ID and name. VLANs which are not assigned to a group may have overlapping names and IDs (including VLANs which belong to a common site). For example, you can create two VLANs with ID 123, but they cannot both be assigned to the same group.
|
||||
|
||||
@@ -11,4 +11,6 @@ Like devices, each VM can be assigned a platform and/or functional role, and mus
|
||||
* Failed
|
||||
* Decommissioning
|
||||
|
||||
Additional fields are available for annotating the vCPU count, memory (GB), and disk (GB) allocated to each VM. Each VM may optionally be assigned to a tenant. Virtual machines may have virtual interfaces assigned to them, but do not support any physical component.
|
||||
Additional fields are available for annotating the vCPU count, memory (GB), and disk (GB) allocated to each VM. A VM may be allocated a partial vCPU count (e.g. 1.5 vCPU).
|
||||
|
||||
Each VM may optionally be assigned to a tenant. Virtual machines may have virtual interfaces assigned to them, but do not support any physical component.
|
||||
|
||||
@@ -63,11 +63,15 @@ setup(
|
||||
install_requires=[],
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
```
|
||||
|
||||
Many of these are self-explanatory, but for more information, see the [setuptools documentation](https://setuptools.readthedocs.io/en/latest/setuptools.html).
|
||||
|
||||
!!! note
|
||||
`zip_safe=False` is **required** as the current plugin iteration is not zip safe due to upstream python issue [issue19699](https://bugs.python.org/issue19699)
|
||||
|
||||
### Define a PluginConfig
|
||||
|
||||
The `PluginConfig` class is a NetBox-specific wrapper around Django's built-in [`AppConfig`](https://docs.djangoproject.com/en/stable/ref/applications/) class. It is used to declare NetBox plugin functionality within a Python package. Each plugin should provide its own subclass, defining its name, metadata, and default and required configuration parameters. An example is below:
|
||||
|
||||
@@ -1 +1 @@
|
||||
version-2.10.md
|
||||
version-2.11.md
|
||||
@@ -121,7 +121,7 @@ A new API endpoint has been added at `/api/ipam/prefixes/<pk>/available-ips/`. A
|
||||
|
||||
#### NAPALM Integration ([#1348](https://github.com/netbox-community/netbox/issues/1348))
|
||||
|
||||
The [NAPALM automation](https://napalm-automation.net/) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](http://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py.
|
||||
The [NAPALM automation](https://napalm-automation.net/) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py.
|
||||
|
||||
### Enhancements
|
||||
|
||||
|
||||
@@ -1,6 +1,208 @@
|
||||
# NetBox v2.10
|
||||
|
||||
## v2.10-beta1 (2020-11-17)
|
||||
## v2.10.10 (2021-04-15)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5796](https://github.com/netbox-community/netbox/issues/5796) - Add DC terminal power port, outlet types
|
||||
* [#5980](https://github.com/netbox-community/netbox/issues/5980) - Add Saf-D-Grid power port, outlet types
|
||||
* [#6157](https://github.com/netbox-community/netbox/issues/6157) - Support Markdown rendering for report logs
|
||||
* [#6160](https://github.com/netbox-community/netbox/issues/6160) - Add F connector port type
|
||||
* [#6168](https://github.com/netbox-community/netbox/issues/6168) - Add SFP56 50GE interface type
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5419](https://github.com/netbox-community/netbox/issues/5419) - Update parent device/VM when deleting a primary IP
|
||||
* [#5643](https://github.com/netbox-community/netbox/issues/5643) - Fix VLAN assignment when editing VM interfaces in bulk
|
||||
* [#5652](https://github.com/netbox-community/netbox/issues/5652) - Update object data when renaming a custom field
|
||||
* [#6056](https://github.com/netbox-community/netbox/issues/6056) - Optimize change log cleanup
|
||||
* [#6144](https://github.com/netbox-community/netbox/issues/6144) - Fix MAC address field display in VM interfaces search form
|
||||
* [#6152](https://github.com/netbox-community/netbox/issues/6152) - Fix custom field filtering for cables, virtual chassis
|
||||
* [#6162](https://github.com/netbox-community/netbox/issues/6162) - Fix choice field filters (multiple models)
|
||||
|
||||
---
|
||||
|
||||
## v2.10.9 (2021-04-12)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5526](https://github.com/netbox-community/netbox/issues/5526) - Add MAC address search field to VM interfaces list
|
||||
* [#5756](https://github.com/netbox-community/netbox/issues/5756) - Omit child devices from non-racked devices list under rack view
|
||||
* [#5840](https://github.com/netbox-community/netbox/issues/5840) - Add column to cable termination objects to display cable color
|
||||
* [#6054](https://github.com/netbox-community/netbox/issues/6054) - Display NAPALM-enabled device tabs only when relevant
|
||||
* [#6083](https://github.com/netbox-community/netbox/issues/6083) - Support disabling TLS certificate validation for Redis
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5805](https://github.com/netbox-community/netbox/issues/5805) - Fix missing custom field filters for cables, rack reservations
|
||||
* [#6070](https://github.com/netbox-community/netbox/issues/6070) - Add missing `count_ipaddresses` attribute to VMInterface serializer
|
||||
* [#6073](https://github.com/netbox-community/netbox/issues/6073) - Permit users to manage their own REST API tokens without needing explicit permission
|
||||
* [#6081](https://github.com/netbox-community/netbox/issues/6081) - Fix interface connections REST API endpoint
|
||||
* [#6082](https://github.com/netbox-community/netbox/issues/6082) - Support colons in webhook header values
|
||||
* [#6108](https://github.com/netbox-community/netbox/issues/6108) - Do not infer tenant assignment from parent objects for prefixes, IP addresses
|
||||
* [#6117](https://github.com/netbox-community/netbox/issues/6117) - Handle exception when attempting to assign an MPTT-enabled model as its own parent
|
||||
* [#6131](https://github.com/netbox-community/netbox/issues/6131) - Correct handling of boolean fields when cloning objects
|
||||
|
||||
---
|
||||
|
||||
## v2.10.8 (2021-03-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#6060](https://github.com/netbox-community/netbox/issues/6060) - Fix exception on cable trace in UI (regression from #5650)
|
||||
|
||||
---
|
||||
|
||||
## v2.10.7 (2021-03-25)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5641](https://github.com/netbox-community/netbox/issues/5641) - Allow filtering device components by label
|
||||
* [#5723](https://github.com/netbox-community/netbox/issues/5723) - Allow customization of the geographic mapping service via `MAPS_URL` config parameter
|
||||
* [#5736](https://github.com/netbox-community/netbox/issues/5736) - Allow changing site assignment when bulk editing devices
|
||||
* [#5953](https://github.com/netbox-community/netbox/issues/5953) - Support Markdown rendering for custom script descriptions
|
||||
* [#6040](https://github.com/netbox-community/netbox/issues/6040) - Add UI search fields for asset tag for devices and racks
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5595](https://github.com/netbox-community/netbox/issues/5595) - Restore ability to delete an uploaded device type image
|
||||
* [#5650](https://github.com/netbox-community/netbox/issues/5650) - Denote when the total length of a cable trace may exceed the indicated value
|
||||
* [#5962](https://github.com/netbox-community/netbox/issues/5962) - Ensure consistent display of change log action labels
|
||||
* [#5966](https://github.com/netbox-community/netbox/issues/5966) - Skip Markdown reference link when tabbing through form fields
|
||||
* [#5977](https://github.com/netbox-community/netbox/issues/5977) - Correct validation of `RELEASE_CHECK_URL` config parameter
|
||||
* [#6006](https://github.com/netbox-community/netbox/issues/6006) - Fix VLAN group/site association for bulk prefix import
|
||||
* [#6010](https://github.com/netbox-community/netbox/issues/6010) - Eliminate duplicate virtual chassis search results
|
||||
* [#6012](https://github.com/netbox-community/netbox/issues/6012) - Pre-populate attributes when creating an available child prefix via the UI
|
||||
* [#6023](https://github.com/netbox-community/netbox/issues/6023) - Fix display of bottom banner with uBlock Origin enabled
|
||||
|
||||
---
|
||||
|
||||
## v2.10.6 (2021-03-09)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5592](https://github.com/netbox-community/netbox/issues/5592) - Add IP addresses count to VRF view
|
||||
* [#5630](https://github.com/netbox-community/netbox/issues/5630) - Add QSFP+ (64GFC) FibreChannel Interface option
|
||||
* [#5884](https://github.com/netbox-community/netbox/issues/5884) - Enable custom links for device components
|
||||
* [#5914](https://github.com/netbox-community/netbox/issues/5914) - Add edit/delete buttons for IP addresses on interface view
|
||||
* [#5942](https://github.com/netbox-community/netbox/issues/5942) - Add button to add a new IP address on interface view
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5703](https://github.com/netbox-community/netbox/issues/5703) - Fix VRF and Tenant field population when adding IP addresses from prefix
|
||||
* [#5819](https://github.com/netbox-community/netbox/issues/5819) - Enable ordering of virtual machines by primary IP address
|
||||
* [#5872](https://github.com/netbox-community/netbox/issues/5872) - Ordering of devices by primary IP should respect `PREFER_IPV4` configuration parameter
|
||||
* [#5922](https://github.com/netbox-community/netbox/issues/5922) - Fix options for filtering object permissions in admin UI
|
||||
* [#5935](https://github.com/netbox-community/netbox/issues/5935) - Fix filtering prefixes list by multiple prefix values
|
||||
* [#5948](https://github.com/netbox-community/netbox/issues/5948) - Invalidate cached queries when running `renaturalize`
|
||||
|
||||
---
|
||||
|
||||
## v2.10.5 (2021-02-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5315](https://github.com/netbox-community/netbox/issues/5315) - Fix site unassignment from VLAN when using "None" option
|
||||
* [#5626](https://github.com/netbox-community/netbox/issues/5626) - Fix REST API representation for circuit terminations connected to non-interface endpoints
|
||||
* [#5716](https://github.com/netbox-community/netbox/issues/5716) - Fix filtering rack reservations by custom field
|
||||
* [#5718](https://github.com/netbox-community/netbox/issues/5718) - Fix bulk editing of services when no port(s) are defined
|
||||
* [#5735](https://github.com/netbox-community/netbox/issues/5735) - Ensure consistent treatment of duplicate IP addresses
|
||||
* [#5738](https://github.com/netbox-community/netbox/issues/5738) - Fix redirect to device components view after disconnecting a cable
|
||||
* [#5753](https://github.com/netbox-community/netbox/issues/5753) - Fix Redis Sentinel password application for caching
|
||||
* [#5786](https://github.com/netbox-community/netbox/issues/5786) - Allow setting null tenant group on tenant via REST API
|
||||
* [#5841](https://github.com/netbox-community/netbox/issues/5841) - Disallow the creation of available prefixes/IP addresses in violation of assigned permission constraints
|
||||
|
||||
---
|
||||
|
||||
## v2.10.4 (2021-01-26)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5542](https://github.com/netbox-community/netbox/issues/5542) - Show cable trace lengths in both meters and feet
|
||||
* [#5570](https://github.com/netbox-community/netbox/issues/5570) - Add "management only" filter widget for interfaces list
|
||||
* [#5586](https://github.com/netbox-community/netbox/issues/5586) - Allow filtering virtual chassis by name and master
|
||||
* [#5612](https://github.com/netbox-community/netbox/issues/5612) - Add GG45 and TERA port types, and CAT7a and CAT8 cable types
|
||||
* [#5678](https://github.com/netbox-community/netbox/issues/5678) - Show available type choices for all device component import forms
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5232](https://github.com/netbox-community/netbox/issues/5232) - Correct swagger definition for ip_prefixes_available-ips_create API
|
||||
* [#5574](https://github.com/netbox-community/netbox/issues/5574) - Restrict the creation of device bay templates on non-parent device types
|
||||
* [#5584](https://github.com/netbox-community/netbox/issues/5584) - Restore power utilization panel under device view
|
||||
* [#5597](https://github.com/netbox-community/netbox/issues/5597) - Fix ordering devices by primary IP address
|
||||
* [#5603](https://github.com/netbox-community/netbox/issues/5603) - Fix display of white cables in trace view
|
||||
* [#5639](https://github.com/netbox-community/netbox/issues/5639) - Fix filtering connection lists by device name
|
||||
* [#5640](https://github.com/netbox-community/netbox/issues/5640) - Fix permissions assessment when adding VM interfaces in bulk
|
||||
* [#5648](https://github.com/netbox-community/netbox/issues/5648) - Include VC member interfaces on interfaces tab count when viewing VC master
|
||||
* [#5665](https://github.com/netbox-community/netbox/issues/5665) - Validate rack group is assigned to same site when creating a rack
|
||||
* [#5683](https://github.com/netbox-community/netbox/issues/5683) - Correct rack elevation displayed when viewing a reservation
|
||||
|
||||
---
|
||||
|
||||
## v2.10.3 (2021-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5049](https://github.com/netbox-community/netbox/issues/5049) - Add check for LLDP neighbor chassis name to lldp_neighbors
|
||||
* [#5301](https://github.com/netbox-community/netbox/issues/5301) - Fix misleading error when racking a device with invalid parameters
|
||||
* [#5311](https://github.com/netbox-community/netbox/issues/5311) - Update child objects when a rack group is moved to a new site
|
||||
* [#5518](https://github.com/netbox-community/netbox/issues/5518) - Fix persistent vertical scrollbar
|
||||
* [#5533](https://github.com/netbox-community/netbox/issues/5533) - Fix bulk editing of objects with required custom fields
|
||||
* [#5540](https://github.com/netbox-community/netbox/issues/5540) - Fix exception when viewing a provider with one or more tags assigned
|
||||
* [#5543](https://github.com/netbox-community/netbox/issues/5543) - Fix rendering of config contexts with cluster assignment for devices
|
||||
* [#5546](https://github.com/netbox-community/netbox/issues/5546) - Add custom field bulk edit support for cables, power panels, rack reservations, and virtual chassis
|
||||
* [#5547](https://github.com/netbox-community/netbox/issues/5547) - Add custom field bulk import support for cables, power panels, rack reservations, and virtual chassis
|
||||
* [#5551](https://github.com/netbox-community/netbox/issues/5551) - Restore missing import button on services list
|
||||
* [#5557](https://github.com/netbox-community/netbox/issues/5557) - Fix VRF route target assignment via REST API
|
||||
* [#5558](https://github.com/netbox-community/netbox/issues/5558) - Fix regex validation support for custom URL fields
|
||||
* [#5563](https://github.com/netbox-community/netbox/issues/5563) - Fix power feed cable trace link
|
||||
* [#5564](https://github.com/netbox-community/netbox/issues/5564) - Raise validation error if a power port template's `allocated_draw` exceeds its `maximum_draw`
|
||||
* [#5569](https://github.com/netbox-community/netbox/issues/5569) - Ensure consistent labeling of interface `mgmt_only` field
|
||||
* [#5573](https://github.com/netbox-community/netbox/issues/5573) - Report inconsistent values when migrating custom field data
|
||||
|
||||
---
|
||||
|
||||
## v2.10.2 (2020-12-21)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5489](https://github.com/netbox-community/netbox/issues/5489) - Add filters for type and width to racks list
|
||||
* [#5496](https://github.com/netbox-community/netbox/issues/5496) - Add form field to filter rack reservation by user
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5254](https://github.com/netbox-community/netbox/issues/5254) - Require plugin authors to set zip_safe=False
|
||||
* [#5468](https://github.com/netbox-community/netbox/issues/5468) - Fix unlocking secrets from device/VM view
|
||||
* [#5473](https://github.com/netbox-community/netbox/issues/5473) - Fix alignment of rack names in elevations list
|
||||
* [#5478](https://github.com/netbox-community/netbox/issues/5478) - Fix display of route target description
|
||||
* [#5484](https://github.com/netbox-community/netbox/issues/5484) - Fix "tagged" indication in VLAN members list
|
||||
* [#5486](https://github.com/netbox-community/netbox/issues/5486) - Optimize retrieval of config context data for device/VM REST API views
|
||||
* [#5487](https://github.com/netbox-community/netbox/issues/5487) - Support filtering rack type/width with multiple values
|
||||
* [#5488](https://github.com/netbox-community/netbox/issues/5488) - Fix caching error when viewing cable trace after toggling cable status
|
||||
* [#5498](https://github.com/netbox-community/netbox/issues/5498) - Fix filtering rack reservations by username
|
||||
* [#5499](https://github.com/netbox-community/netbox/issues/5499) - Fix filtering of displayed device/VM interfaces by regex
|
||||
* [#5507](https://github.com/netbox-community/netbox/issues/5507) - Fix custom field data assignment via UI for IP addresses, secrets
|
||||
* [#5510](https://github.com/netbox-community/netbox/issues/5510) - Fix filtering by boolean custom fields
|
||||
|
||||
---
|
||||
|
||||
## v2.10.1 (2020-12-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5444](https://github.com/netbox-community/netbox/issues/5444) - Don't force overwriting of boolean fields when bulk editing interfaces
|
||||
* [#5450](https://github.com/netbox-community/netbox/issues/5450) - API serializer foreign count fields do not have a default value
|
||||
* [#5453](https://github.com/netbox-community/netbox/issues/5453) - Correct change log representation when creating a cable
|
||||
* [#5458](https://github.com/netbox-community/netbox/issues/5458) - Creating a component template throws an exception
|
||||
* [#5461](https://github.com/netbox-community/netbox/issues/5461) - Rack Elevations throw reverse match exception
|
||||
* [#5463](https://github.com/netbox-community/netbox/issues/5463) - Back-to-back Circuit Termination throws AttributeError exception
|
||||
* [#5465](https://github.com/netbox-community/netbox/issues/5465) - Correct return URL when disconnecting a cable from a device
|
||||
* [#5466](https://github.com/netbox-community/netbox/issues/5466) - Fix validation for required custom fields
|
||||
* [#5470](https://github.com/netbox-community/netbox/issues/5470) - Fix exception when making `OPTIONS` request for a REST API list endpoint
|
||||
|
||||
---
|
||||
|
||||
## v2.10.0 (2020-12-14)
|
||||
|
||||
**NOTE:** This release completely removes support for embedded graphs.
|
||||
|
||||
@@ -74,12 +276,17 @@ All end-to-end cable paths are now cached using the new CablePath backend model.
|
||||
* [#5146](https://github.com/netbox-community/netbox/issues/5146) - Add custom field support for cables, power panels, rack reservations, and virtual chassis
|
||||
* [#5154](https://github.com/netbox-community/netbox/issues/5154) - The web interface now consumes the entire browser window
|
||||
* [#5190](https://github.com/netbox-community/netbox/issues/5190) - Add a REST API endpoint for retrieving content types (`/api/extras/content-types/`)
|
||||
* [#5274](https://github.com/netbox-community/netbox/issues/5274) - Add REST API support for custom fields
|
||||
* [#5399](https://github.com/netbox-community/netbox/issues/5399) - Show options for cable endpoint types during bulk import
|
||||
* [#5411](https://github.com/netbox-community/netbox/issues/5411) - Include cable tags in trace view
|
||||
|
||||
### Other Changes
|
||||
|
||||
* [#1846](https://github.com/netbox-community/netbox/issues/1846) - Enable MPTT for InventoryItem hierarchy
|
||||
* [#2755](https://github.com/netbox-community/netbox/issues/2755) - Switched from Font Awesome/Glyphicons to Material Design icons
|
||||
* [#4349](https://github.com/netbox-community/netbox/issues/4349) - Dropped support for embedded graphs
|
||||
* [#4360](https://github.com/netbox-community/netbox/issues/4360) - Dropped support for the Django template language from export templates
|
||||
* [#4711](https://github.com/netbox-community/netbox/issues/4711) - Renamed Webhook `obj_type` to `content_types`
|
||||
* [#4941](https://github.com/netbox-community/netbox/issues/4941) - `commit` argument is now required argument in a custom script's `run()` method
|
||||
* [#5011](https://github.com/netbox-community/netbox/issues/5011) - Standardized name field lengths across all models
|
||||
* [#5139](https://github.com/netbox-community/netbox/issues/5139) - Omit utilization statistics from RIR list
|
||||
@@ -89,45 +296,47 @@ All end-to-end cable paths are now cached using the new CablePath backend model.
|
||||
|
||||
* Added support for `PUT`, `PATCH`, and `DELETE` operations on list endpoints (bulk update and delete)
|
||||
* Added the `/extras/content-types/` endpoint for Django ContentTypes
|
||||
* Added the `/extras/custom-fields/` endpoint for custom fields
|
||||
* Removed the `/extras/_custom_field_choices/` endpoint (replaced by new custom fields endpoint)
|
||||
* Added the `/status/` endpoint to convey NetBox's current status
|
||||
* circuits.CircuitTermination:
|
||||
* Added the `/trace/` endpoint
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* `port_speed` may now be null
|
||||
* Added the `/trace/` endpoint
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* `port_speed` may now be null
|
||||
* dcim.Cable: Added `custom_fields`
|
||||
* dcim.ConsolePort:
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* dcim.ConsoleServerPort:
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* dcim.FrontPort:
|
||||
* Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* dcim.Interface:
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* dcim.InventoryItem: The `_depth` field has been added to reflect MPTT positioning
|
||||
* dcim.PowerFeed:
|
||||
* Added the `/trace/` endpoint
|
||||
* Added fields `connected_endpoint`, `connected_endpoint_type`, `connected_endpoint_reachable`, `cable_peer`, and `cable_peer_type`
|
||||
* Added the `/trace/` endpoint
|
||||
* Added fields `connected_endpoint`, `connected_endpoint_type`, `connected_endpoint_reachable`, `cable_peer`, and `cable_peer_type`
|
||||
* dcim.PowerOutlet:
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* dcim.PowerPanel: Added `custom_fields`
|
||||
* dcim.PowerPort
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Removed `connection_status` from nested serializer
|
||||
* dcim.RackReservation: Added `custom_fields`
|
||||
* dcim.RearPort:
|
||||
* Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths
|
||||
* Added `cable_peer` and `cable_peer_type`
|
||||
* dcim.VirtualChassis: Added `custom_fields`
|
||||
* extras.ExportTemplate: The `template_language` field has been removed
|
||||
* extras.Graph: This API endpoint has been removed (see #4349)
|
||||
|
||||
211
docs/release-notes/version-2.11.md
Normal file
211
docs/release-notes/version-2.11.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# NetBox v2.11
|
||||
|
||||
## v2.11.1 (2021-04-21)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#6161](https://github.com/netbox-community/netbox/issues/6161) - Enable ordering of device component tables
|
||||
* [#6179](https://github.com/netbox-community/netbox/issues/6179) - Enable natural ordering for virtual machines
|
||||
* [#6189](https://github.com/netbox-community/netbox/issues/6189) - Add ability to search for locations by name or description
|
||||
* [#6190](https://github.com/netbox-community/netbox/issues/6190) - Allow filtering devices with no location assigned
|
||||
* [#6210](https://github.com/netbox-community/netbox/issues/6210) - Include child locations on location view
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#6184](https://github.com/netbox-community/netbox/issues/6184) - Fix parent object table column in prefix IP addresses list
|
||||
* [#6188](https://github.com/netbox-community/netbox/issues/6188) - Support custom field filtering for regions, site groups, and locations
|
||||
* [#6196](https://github.com/netbox-community/netbox/issues/6196) - Fix object list display for users with read-only permissions
|
||||
* [#6215](https://github.com/netbox-community/netbox/issues/6215) - Restore tenancy section in virtual machine form
|
||||
|
||||
---
|
||||
|
||||
## v2.11.0 (2021-04-16)
|
||||
|
||||
**Note:** NetBox v2.11 is the last major release that will support Python 3.6. Beginning with NetBox v2.12, Python 3.7 or later will be required.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* All objects now use numeric IDs in their UI view URLs instead of slugs. You may need to update external references to NetBox objects. (Note that this does _not_ affect the REST API.)
|
||||
* The UI now uses numeric IDs when filtering object lists. You may need to update external links to filtered object lists. (Note that the slug- and name-based filters will continue to work, however the filter selection fields within the UI will not be automatically populated.)
|
||||
* The RackGroup model has been renamed to Location (see [#4971](https://github.com/netbox-community/netbox/issues/4971)). Its REST API endpoint has changed from `/api/dcim/rack-groups/` to `/api/dcim/locations/`.
|
||||
* The foreign key field `group` on dcim.Rack has been renamed to `location`.
|
||||
* The foreign key field `site` on ipam.VLANGroup has been replaced with the `scope` generic foreign key (see [#5284](https://github.com/netbox-community/netbox/issues/5284)).
|
||||
* Custom script ObjectVars no longer support the `queryset` parameter: Use `model` instead (see [#5995](https://github.com/netbox-community/netbox/issues/5995)).
|
||||
|
||||
### New Features
|
||||
|
||||
#### Journaling Support ([#151](https://github.com/netbox-community/netbox/issues/151))
|
||||
|
||||
NetBox now supports journaling for all primary objects. The journal is a collection of human-generated notes and comments about an object maintained for historical context. It supplements NetBox's change log to provide additional information about why changes have been made or to convey events which occur outside NetBox. Unlike the change log, in which records typically expire after some time, journal entries persist for the life of the associated object.
|
||||
|
||||
#### Parent Interface Assignments ([#1519](https://github.com/netbox-community/netbox/issues/1519))
|
||||
|
||||
Virtual device and VM interfaces can now be assigned to a "parent" interface by setting the `parent` field on the interface object. This is helpful for associating subinterfaces with their physical counterpart. For example, you might assign virtual interfaces Gi0/0.100 and Gi0/0.200 as children of the physical interface Gi0/0.
|
||||
|
||||
#### Pre- and Post-Change Snapshots in Webhooks ([#3451](https://github.com/netbox-community/netbox/issues/3451))
|
||||
|
||||
In conjunction with the newly improved change logging functionality ([#5913](https://github.com/netbox-community/netbox/issues/5913)), outgoing webhooks now include both pre- and post-change representations of the modified object. These are available in the rendering context as a dictionary named `snapshots` with keys `prechange` and `postchange`. For example, here are the abridged snapshots resulting from renaming a site and changing its status:
|
||||
|
||||
```json
|
||||
"snapshots": {
|
||||
"prechange": {
|
||||
"name": "Site 1",
|
||||
"slug": "site-1",
|
||||
"status": "active",
|
||||
...
|
||||
},
|
||||
"postchange": {
|
||||
"name": "Site 2",
|
||||
"slug": "site-2",
|
||||
"status": "planned",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: The pre-change snapshot for a newly created will always be null, as will the post-change snapshot for a deleted object.
|
||||
|
||||
#### Mark as Connected Without a Cable ([#3648](https://github.com/netbox-community/netbox/issues/3648))
|
||||
|
||||
Cable termination objects (circuit terminations, power feeds, and most device components) can now be marked as "connected" without actually attaching a cable. This helps simplify the process of modeling an infrastructure boundary where we don't necessarily know or care what is connected to an attachment point, but still need to reflect the termination as being occupied.
|
||||
|
||||
In addition to the new `mark_connected` boolean field, the REST API representation of these objects now also includes a read-only boolean field named `_occupied`. This conveniently returns true if either a cable is attached or `mark_connected` is true.
|
||||
|
||||
#### Allow Assigning Devices to Locations ([#4971](https://github.com/netbox-community/netbox/issues/4971))
|
||||
|
||||
Devices can now be assigned to locations (formerly known as rack groups) within a site without needing to be assigned to a particular rack. This is handy for assigning devices to rooms or floors within a building where racks are not used. The `location` foreign key field has been added to the Device model to support this.
|
||||
|
||||
#### Dynamic Object Exports ([#4999](https://github.com/netbox-community/netbox/issues/4999))
|
||||
|
||||
When exporting a list of objects in NetBox, users now have the option of selecting the "current view". This will render CSV output matching the current configuration of the table being viewed. For example, if you modify the sites list to display only the site name, tenant, and status, the rendered CSV will include only these columns, and they will appear in the order chosen.
|
||||
|
||||
The legacy static export behavior has been retained to ensure backward compatibility for dependent integrations. However, users are strongly encouraged to adapt custom export templates where needed as this functionality will be removed in v2.12.
|
||||
|
||||
#### Variable Scope Support for VLAN Groups ([#5284](https://github.com/netbox-community/netbox/issues/5284))
|
||||
|
||||
In previous releases, VLAN groups could be assigned only to a site. To afford more flexibility in conveying the true scope of an L2 domain, a VLAN group can now be assigned to a region, site group (new in v2.11), site, location, or rack. VLANs assigned to a group will be available only to devices and virtual machines which exist within its scope.
|
||||
|
||||
For example, a VLAN within a group assigned to a location will be available only to devices assigned to that location (or one of its child locations), or to a rack within that location.
|
||||
|
||||
#### New Site Group Model ([#5892](https://github.com/netbox-community/netbox/issues/5892))
|
||||
|
||||
This release introduces the new SiteGroup model, which can be used to organize sites similar to the existing Region model. Whereas regions are intended for geographically arranging sites into countries, states, and so on, the new site group model can be used to organize sites by functional role or other arbitrary classification. Using regions and site groups in conjunction provides two dimensions along which sites can be organized, offering greater flexibility to the user.
|
||||
|
||||
#### Improved Change Logging ([#5913](https://github.com/netbox-community/netbox/issues/5913))
|
||||
|
||||
The ObjectChange model (which is used to record the creation, modification, and deletion of NetBox objects) now explicitly records the pre-change and post-change state of each object, rather than only the post-change state. This was done to present a more clear depiction of each change being made, and to prevent the erroneous association of a previous unlogged change with its successor.
|
||||
|
||||
#### Provider Network Modeling ([#5986](https://github.com/netbox-community/netbox/issues/5986))
|
||||
|
||||
A new provider network model has been introduced to represent the boundary of a network that exists outside the scope of NetBox. Each instance of this model must be assigned to a provider, and circuits can now terminate to either provider networks or to sites. The use of this model will likely be extended by future releases to support overlay and virtual circuit modeling.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#4833](https://github.com/netbox-community/netbox/issues/4833) - Allow assigning config contexts by device type
|
||||
* [#5344](https://github.com/netbox-community/netbox/issues/5344) - Add support for custom fields in tables
|
||||
* [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
|
||||
* [#5375](https://github.com/netbox-community/netbox/issues/5375) - Add `speed` attribute to console port models
|
||||
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
|
||||
* [#5425](https://github.com/netbox-community/netbox/issues/5425) - Create separate tabs for VMs and devices under the cluster view
|
||||
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
|
||||
* [#5608](https://github.com/netbox-community/netbox/issues/5608) - Add REST API endpoint for custom links
|
||||
* [#5610](https://github.com/netbox-community/netbox/issues/5610) - Add REST API endpoint for webhooks
|
||||
* [#5757](https://github.com/netbox-community/netbox/issues/5757) - Add unique identifier to every object view
|
||||
* [#5830](https://github.com/netbox-community/netbox/issues/5830) - Add `as_attachment` to ExportTemplate to control download behavior
|
||||
* [#5848](https://github.com/netbox-community/netbox/issues/5848) - Filter custom fields by content type in format `<app_label>.<model>`
|
||||
* [#5891](https://github.com/netbox-community/netbox/issues/5891) - Add `display` field to all REST API serializers
|
||||
* [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI
|
||||
* [#5895](https://github.com/netbox-community/netbox/issues/5895) - Rename RackGroup to Location
|
||||
* [#5901](https://github.com/netbox-community/netbox/issues/5901) - Add `created` and `last_updated` fields to device component models
|
||||
* [#5971](https://github.com/netbox-community/netbox/issues/5971) - Add dedicated views for organizational models
|
||||
* [#5972](https://github.com/netbox-community/netbox/issues/5972) - Enable bulk editing for organizational models
|
||||
* [#5975](https://github.com/netbox-community/netbox/issues/5975) - Allow partial (decimal) vCPU allocations for virtual machines
|
||||
* [#6001](https://github.com/netbox-community/netbox/issues/6001) - Paginate component tables under device views
|
||||
* [#6038](https://github.com/netbox-community/netbox/issues/6038) - Include tagged objects list on tag view
|
||||
* [#6088](https://github.com/netbox-community/netbox/issues/6088) - Improved table configuration form
|
||||
* [#6097](https://github.com/netbox-community/netbox/issues/6097) - Redirect old slug-based object views
|
||||
* [#6125](https://github.com/netbox-community/netbox/issues/6125) - Add locations count to home page
|
||||
* [#6146](https://github.com/netbox-community/netbox/issues/6146) - Add bulk disconnect support for power feeds
|
||||
* [#6149](https://github.com/netbox-community/netbox/issues/6149) - Support image attachments for locations
|
||||
|
||||
### Bug Fixes (from v2.11-beta1)
|
||||
|
||||
* [#5583](https://github.com/netbox-community/netbox/issues/5583) - Eliminate redundant change records when adding/removing tags
|
||||
* [#6100](https://github.com/netbox-community/netbox/issues/6100) - Fix VM interfaces table "add interfaces" link
|
||||
* [#6104](https://github.com/netbox-community/netbox/issues/6104) - Fix location column on racks table
|
||||
* [#6105](https://github.com/netbox-community/netbox/issues/6105) - Hide checkboxes for VMs under cluster VMs view
|
||||
* [#6106](https://github.com/netbox-community/netbox/issues/6106) - Allow assigning a virtual interface as the parent of an existing interface
|
||||
* [#6107](https://github.com/netbox-community/netbox/issues/6107) - Fix rack selection field on device form
|
||||
* [#6110](https://github.com/netbox-community/netbox/issues/6110) - Fix handling of TemplateColumn values for table export
|
||||
* [#6123](https://github.com/netbox-community/netbox/issues/6123) - Prevent device from being assigned to mismatched site and location
|
||||
* [#6124](https://github.com/netbox-community/netbox/issues/6124) - Location `parent` filter should return all child locations (not just those directly assigned)
|
||||
* [#6130](https://github.com/netbox-community/netbox/issues/6130) - Improve display of assigned models in custom fields list
|
||||
* [#6155](https://github.com/netbox-community/netbox/issues/6155) - Fix admin links for plugins, background tasks
|
||||
* [#6171](https://github.com/netbox-community/netbox/issues/6171) - Fix display of horizontally-scrolling object lists
|
||||
* [#6173](https://github.com/netbox-community/netbox/issues/6173) - Fix assigned device/VM count when bulk editing/deleting device roles
|
||||
* [#6176](https://github.com/netbox-community/netbox/issues/6176) - Correct position of MAC address field when creating VM interfaces
|
||||
* [#6177](https://github.com/netbox-community/netbox/issues/6177) - Prevent VM interface from being assigned as its own parent
|
||||
|
||||
### Other Changes
|
||||
|
||||
* [#1638](https://github.com/netbox-community/netbox/issues/1638) - Migrate all primary keys to 64-bit integers
|
||||
* [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs
|
||||
* [#5938](https://github.com/netbox-community/netbox/issues/5938) - Deprecated support for Python 3.6
|
||||
* [#5990](https://github.com/netbox-community/netbox/issues/5990) - Deprecated `display_field` parameter for custom script ObjectVar and MultiObjectVar fields
|
||||
* [#5995](https://github.com/netbox-community/netbox/issues/5995) - Dropped backward compatibility for `queryset` parameter on ObjectVar and MultiObjectVar (use `model` instead)
|
||||
* [#6014](https://github.com/netbox-community/netbox/issues/6014) - Moved the virtual machine interfaces list to a separate view
|
||||
* [#6071](https://github.com/netbox-community/netbox/issues/6071) - Cable traces now traverse circuits
|
||||
|
||||
### REST API Changes
|
||||
|
||||
* All primary keys are now 64-bit integers
|
||||
* All model serializers now include a `display` field to be used for the presentation of an object to a human user
|
||||
* All device components
|
||||
* Added support for custom fields
|
||||
* Added `created` and `last_updated` fields to track object creation and modification
|
||||
* All device component templates
|
||||
* Added `created` and `last_updated` fields to track object creation and modification
|
||||
* All organizational models
|
||||
* Added support for custom fields
|
||||
* All cable termination models (cabled device components, power feeds, and circuit terminations)
|
||||
* Added `mark_connected` boolean field to force connection status
|
||||
* Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied
|
||||
* Renamed RackGroup to Location
|
||||
* The `/dcim/rack-groups/` endpoint is now `/dcim/locations/`
|
||||
* circuits.CircuitTermination
|
||||
* Added the `provider_network` field
|
||||
* Removed the `connected_endpoint`, `connected_endpoint_type`, and `connected_endpoint_reachable` fields
|
||||
* circuits.ProviderNetwork
|
||||
* Added the `/api/circuits/provider-networks/` endpoint
|
||||
* dcim.Device
|
||||
* Added the `location` field
|
||||
* dcim.Interface
|
||||
* Added the `parent` field
|
||||
* dcim.PowerPanel
|
||||
* Renamed `rack_group` field to `location`
|
||||
* dcim.Rack
|
||||
* Renamed `group` field to `location`
|
||||
* dcim.Site
|
||||
* Added the `group` foreign key field to SiteGroup
|
||||
* dcim.SiteGroup
|
||||
* Added the `/api/dcim/site-groups/` endpoint
|
||||
* extras.ConfigContext
|
||||
* Added the `site_groups` many-to-many field to track the assignment of ConfigContexts to SiteGroups
|
||||
* extras.CustomField
|
||||
* Added new custom field type: `multi-select`
|
||||
* extras.CustomLink
|
||||
* Added the `/api/extras/custom-links/` endpoint
|
||||
* extras.ExportTemplate
|
||||
* Added the `as_attachment` boolean field
|
||||
* extras.ObjectChange
|
||||
* Added the `prechange_data` field
|
||||
* Renamed `object_data` to `postchange_data`
|
||||
* extras.Webhook
|
||||
* Added the `/api/extras/webhooks/` endpoint
|
||||
* ipam.VLANGroup
|
||||
* Added the `scope_type`, `scope_id`, and `scope` fields (`scope` is a generic foreign key)
|
||||
* Dropped the `site` foreign key field
|
||||
* virtualization.VirtualMachine
|
||||
* `vcpus` has been changed from an integer to a decimal value
|
||||
* virtualization.VMInterface
|
||||
* Added the `parent` field
|
||||
@@ -196,7 +196,7 @@ Our second-most popular feature request has arrived! NetBox now supports the cre
|
||||
|
||||
#### Custom Validation Reports ([#1511](https://github.com/netbox-community/netbox/issues/1511))
|
||||
|
||||
Users can now create custom reports which are run to validate data in NetBox. Reports work very similar to Python unit tests: Each report inherits from NetBox's Report class and contains one or more test method. Reports can be run and retrieved via the web UI, API, or CLI. See [the docs](http://netbox.readthedocs.io/en/stable/miscellaneous/reports/) for more info.
|
||||
Users can now create custom reports which are run to validate data in NetBox. Reports work very similar to Python unit tests: Each report inherits from NetBox's Report class and contains one or more test method. Reports can be run and retrieved via the web UI, API, or CLI. See [the docs](https://netbox.readthedocs.io/en/stable/miscellaneous/reports/) for more info.
|
||||
|
||||
### Enhancements
|
||||
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
# NetBox v2.9
|
||||
|
||||
## v2.9.10 (FUTURE)
|
||||
## v2.9.11 (2020-12-11)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5424](https://github.com/netbox-community/netbox/issues/5424) - Allow passing Python code to `nbshell` using `--command`
|
||||
* [#5439](https://github.com/netbox-community/netbox/issues/5439) - Add CS and SN fiber port types
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5383](https://github.com/netbox-community/netbox/issues/5383) - Fix setting user password via REST API
|
||||
* [#5396](https://github.com/netbox-community/netbox/issues/5396) - Fix uniqueness constraint for virtual machine names
|
||||
* [#5387](https://github.com/netbox-community/netbox/issues/5387) - Fix error when rendering config contexts when objects have multiple tags assigned
|
||||
* [#5407](https://github.com/netbox-community/netbox/issues/5407) - Add direct link to secret on secrets list
|
||||
* [#5408](https://github.com/netbox-community/netbox/issues/5408) - Fix updating secrets without setting new plaintext
|
||||
* [#5410](https://github.com/netbox-community/netbox/issues/5410) - Restore tags field on cable connection forms
|
||||
* [#5433](https://github.com/netbox-community/netbox/issues/5433) - Exclude SVG files from front/rear image upload for device types (currently unsupported)
|
||||
* [#5436](https://github.com/netbox-community/netbox/issues/5436) - Show assigned IP addresses in interfaces list
|
||||
* [#5446](https://github.com/netbox-community/netbox/issues/5446) - Fix validation for plugin version and required settings
|
||||
|
||||
---
|
||||
|
||||
## v2.9.10 (2020-11-24)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#5319](https://github.com/netbox-community/netbox/issues/5319) - Add USB types for power ports and outlets
|
||||
* [#5337](https://github.com/netbox-community/netbox/issues/5337) - Add "splice" type for pass-through ports
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#5235](https://github.com/netbox-community/netbox/issues/5235) - Fix exception when editing IP address with a NAT IP assigned to a non-racked device
|
||||
* [#5309](https://github.com/netbox-community/netbox/issues/5309) - Avoid extraneous database queries when manipulating objects
|
||||
* [#5345](https://github.com/netbox-community/netbox/issues/5345) - Fix non-deterministic ordering of prefixes and IP addresses
|
||||
* [#5350](https://github.com/netbox-community/netbox/issues/5350) - Filter available racks by selected group when creating a rack reservation
|
||||
* [#5355](https://github.com/netbox-community/netbox/issues/5355) - Limit rack groups by selected site when editing a rack
|
||||
* [#5356](https://github.com/netbox-community/netbox/issues/5356) - Populate manufacturer field when adding a device component template
|
||||
* [#5360](https://github.com/netbox-community/netbox/issues/5360) - Clear VLAN assignments when setting interface mode to none
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mkdocs==1.1
|
||||
mkdocs-material
|
||||
git+https://github.com/cmacmackin/markdown-include.git
|
||||
|
||||
@@ -20,7 +20,7 @@ http://netbox/api/dcim/sites/
|
||||
}
|
||||
```
|
||||
|
||||
A token is not required for read-only operations which have been exempted from permissions enforcement (using the [`EXEMPT_VIEW_PERMISSIONS`](../../configuration/optional-settings/#exempt_view_permissions) configuration parameter). However, if a token _is_ required but not present in a request, the API will return a 403 (Forbidden) response:
|
||||
A token is not required for read-only operations which have been exempted from permissions enforcement (using the [`EXEMPT_VIEW_PERMISSIONS`](../configuration/optional-settings.md#exempt_view_permissions) configuration parameter). However, if a token _is_ required but not present in a request, the API will return a 403 (Forbidden) response:
|
||||
|
||||
```
|
||||
$ curl http://netbox/api/dcim/sites/
|
||||
|
||||
@@ -78,8 +78,8 @@ String based (char) fields (Name, Address, etc) support these lookup expressions
|
||||
- `nisw` - negated case insensitive starts with
|
||||
- `iew` - case insensitive ends with
|
||||
- `niew` - negated case insensitive ends with
|
||||
- `ie` - case sensitive exact match
|
||||
- `nie` - negated case sensitive exact match
|
||||
- `ie` - case insensitive exact match
|
||||
- `nie` - negated case insensitive exact match
|
||||
|
||||
### Foreign Keys & Other Fields
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## What is a REST API?
|
||||
|
||||
REST stands for [representational state transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It's a particular type of API which employs HTTP requests and [JavaScript Object Notation (JSON)](http://www.json.org/) to facilitate create, retrieve, update, and delete (CRUD) operations on objects within an application. Each type of operation is associated with a particular HTTP verb:
|
||||
REST stands for [representational state transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It's a particular type of API which employs HTTP requests and [JavaScript Object Notation (JSON)](https://www.json.org/) to facilitate create, retrieve, update, and delete (CRUD) operations on objects within an application. Each type of operation is associated with a particular HTTP verb:
|
||||
|
||||
* `GET`: Retrieve an object or list of objects
|
||||
* `POST`: Create an object
|
||||
@@ -269,7 +269,7 @@ The brief format is supported for both lists and individual objects.
|
||||
|
||||
### Excluding Config Contexts
|
||||
|
||||
When retrieving devices and virtual machines via the REST API, each will included its rendered [configuration context data](../models/extras/configcontext/) by default. Users with large amounts of context data will likely observe suboptimal performance when returning multiple objects, particularly with very high page sizes. To combat this, context data may be excluded from the response data by attaching the query parameter `?exclude=config_context` to the request. This parameter works for both list and detail views.
|
||||
When retrieving devices and virtual machines via the REST API, each will included its rendered [configuration context data](../models/extras/configcontext.md) by default. Users with large amounts of context data will likely observe suboptimal performance when returning multiple objects, particularly with very high page sizes. To combat this, context data may be excluded from the response data by attaching the query parameter `?exclude=config_context` to the request. This parameter works for both list and detail views.
|
||||
|
||||
## Pagination
|
||||
|
||||
@@ -308,7 +308,7 @@ Vary: Accept
|
||||
}
|
||||
```
|
||||
|
||||
The default page is determined by the [`PAGINATE_COUNT`](../../configuration/optional-settings/#paginate_count) configuration parameter, which defaults to 50. However, this can be overridden per request by specifying the desired `offset` and `limit` query parameters. For example, if you wish to retrieve a hundred devices at a time, you would make a request for:
|
||||
The default page is determined by the [`PAGINATE_COUNT`](../configuration/optional-settings.md#paginate_count) configuration parameter, which defaults to 50. However, this can be overridden per request by specifying the desired `offset` and `limit` query parameters. For example, if you wish to retrieve a hundred devices at a time, you would make a request for:
|
||||
|
||||
```
|
||||
http://netbox/api/dcim/devices/?limit=100
|
||||
@@ -325,7 +325,7 @@ The response will return devices 1 through 100. The URL provided in the `next` a
|
||||
}
|
||||
```
|
||||
|
||||
The maximum number of objects that can be returned is limited by the [`MAX_PAGE_SIZE`](../../configuration/optional-settings/#max_page_size) configuration parameter, which is 1000 by default. Setting this to `0` or `None` will remove the maximum limit. An API consumer can then pass `?limit=0` to retrieve _all_ matching objects with a single request.
|
||||
The maximum number of objects that can be returned is limited by the [`MAX_PAGE_SIZE`](../configuration/optional-settings.md#max_page_size) configuration parameter, which is 1000 by default. Setting this to `0` or `None` will remove the maximum limit. An API consumer can then pass `?limit=0` to retrieve _all_ matching objects with a single request.
|
||||
|
||||
!!! warning
|
||||
Disabling the page size limit introduces a potential for very resource-intensive requests, since one API request can effectively retrieve an entire table from the database.
|
||||
@@ -387,7 +387,7 @@ curl -s -X GET http://netbox/api/ipam/ip-addresses/5618/ | jq '.'
|
||||
|
||||
### Creating a New Object
|
||||
|
||||
To create a new object, make a `POST` request to the model's _list_ endpoint with JSON data pertaining to the object being created. Note that a REST API token is required for all write operations; see the [authentication documentation](../authentication/) for more information. Also be sure to set the `Content-Type` HTTP header to `application/json`.
|
||||
To create a new object, make a `POST` request to the model's _list_ endpoint with JSON data pertaining to the object being created. Note that a REST API token is required for all write operations; see the [authentication documentation](authentication.md) for more information. Also be sure to set the `Content-Type` HTTP header to `application/json`.
|
||||
|
||||
```no-highlight
|
||||
curl -s -X POST \
|
||||
|
||||
@@ -4,7 +4,7 @@ As with most other objects, the REST API can be used to view, create, modify, an
|
||||
|
||||
## Generating a Session Key
|
||||
|
||||
In order to encrypt or decrypt secret data, a session key must be attached to the API request. To generate a session key, send an authenticated request to the `/api/secrets/get-session-key/` endpoint with the private RSA key which matches your [UserKey](../../core-functionality/secrets/#user-keys). The private key must be POSTed with the name `private_key`.
|
||||
In order to encrypt or decrypt secret data, a session key must be attached to the API request. To generate a session key, send an authenticated request to the `/api/secrets/get-session-key/` endpoint with the private RSA key which matches your [UserKey](../core-functionality/secrets.md#user-keys). The private key must be POSTed with the name `private_key`.
|
||||
|
||||
```no-highlight
|
||||
$ curl -X POST http://netbox/api/secrets/get-session-key/ \
|
||||
|
||||
15
mkdocs.yml
15
mkdocs.yml
@@ -5,14 +5,18 @@ python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
theme:
|
||||
name: readthedocs
|
||||
navigation_depth: 3
|
||||
name: material
|
||||
extra_css:
|
||||
- extra.css
|
||||
markdown_extensions:
|
||||
- admonition:
|
||||
- admonition
|
||||
- markdown_include.include:
|
||||
headingOffset: 1
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed
|
||||
nav:
|
||||
- Introduction: 'index.md'
|
||||
- Installation:
|
||||
@@ -49,6 +53,7 @@ nav:
|
||||
- Custom Links: 'additional-features/custom-links.md'
|
||||
- Custom Scripts: 'additional-features/custom-scripts.md'
|
||||
- Export Templates: 'additional-features/export-templates.md'
|
||||
- Journaling: 'additional-features/journaling.md'
|
||||
- NAPALM: 'additional-features/napalm.md'
|
||||
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
|
||||
- Reports: 'additional-features/reports.md'
|
||||
@@ -68,13 +73,15 @@ nav:
|
||||
- Working with Secrets: 'rest-api/working-with-secrets.md'
|
||||
- Development:
|
||||
- Introduction: 'development/index.md'
|
||||
- Getting Started: 'development/getting-started.md'
|
||||
- Style Guide: 'development/style-guide.md'
|
||||
- Utility Views: 'development/utility-views.md'
|
||||
- Models: 'development/models.md'
|
||||
- Extending Models: 'development/extending-models.md'
|
||||
- Application Registry: 'development/application-registry.md'
|
||||
- User Preferences: 'development/user-preferences.md'
|
||||
- Release Checklist: 'development/release-checklist.md'
|
||||
- Release Notes:
|
||||
- Version 2.11: 'release-notes/version-2.11.md'
|
||||
- Version 2.10: 'release-notes/version-2.10.md'
|
||||
- Version 2.9: 'release-notes/version-2.9.md'
|
||||
- Version 2.8: 'release-notes/version-2.8.md'
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from circuits.models import *
|
||||
from netbox.api import WritableNestedSerializer
|
||||
|
||||
__all__ = [
|
||||
'NestedCircuitSerializer',
|
||||
'NestedCircuitTerminationSerializer',
|
||||
'NestedCircuitTypeSerializer',
|
||||
'NestedProviderNetworkSerializer',
|
||||
'NestedProviderSerializer',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class NestedProviderNetworkSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail')
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Providers
|
||||
#
|
||||
@@ -21,7 +34,7 @@ class NestedProviderSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = ['id', 'url', 'name', 'slug', 'circuit_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'circuit_count']
|
||||
|
||||
|
||||
#
|
||||
@@ -34,7 +47,7 @@ class NestedCircuitTypeSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'url', 'name', 'slug', 'circuit_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'circuit_count']
|
||||
|
||||
|
||||
class NestedCircuitSerializer(WritableNestedSerializer):
|
||||
@@ -42,7 +55,7 @@ class NestedCircuitSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['id', 'url', 'cid']
|
||||
fields = ['id', 'url', 'display', 'cid']
|
||||
|
||||
|
||||
class NestedCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
@@ -51,4 +64,4 @@ class NestedCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = ['id', 'url', 'circuit', 'term_side', 'cable']
|
||||
fields = ['id', 'url', 'display', 'circuit', 'term_side', 'cable', '_occupied']
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from circuits.choices import CircuitStatusChoices
|
||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer
|
||||
from circuits.models import *
|
||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||
from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from extras.api.serializers import TaggedObjectSerializer
|
||||
from netbox.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
||||
from netbox.api import ChoiceField
|
||||
from netbox.api.serializers import (
|
||||
BaseModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer
|
||||
)
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
from .nested_serializers import *
|
||||
|
||||
@@ -15,15 +16,31 @@ from .nested_serializers import *
|
||||
# Providers
|
||||
#
|
||||
|
||||
class ProviderSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class ProviderSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
|
||||
circuit_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'id', 'url', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class ProviderNetworkSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail')
|
||||
provider = NestedProviderSerializer()
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'id', 'url', 'display', 'provider', 'name', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@@ -31,26 +48,31 @@ class ProviderSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
# Circuits
|
||||
#
|
||||
|
||||
class CircuitTypeSerializer(ValidatedModelSerializer):
|
||||
class CircuitTypeSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
|
||||
circuit_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'url', 'name', 'slug', 'description', 'circuit_count']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'circuit_count',
|
||||
]
|
||||
|
||||
|
||||
class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||
site = NestedSiteSerializer()
|
||||
connected_endpoint = NestedInterfaceSerializer()
|
||||
provider_network = NestedProviderNetworkSerializer()
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = ['id', 'url', 'site', 'connected_endpoint', 'port_speed', 'upstream_speed', 'xconnect_id']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id',
|
||||
]
|
||||
|
||||
|
||||
class CircuitSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class CircuitSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
|
||||
provider = NestedProviderSerializer()
|
||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||
@@ -62,21 +84,23 @@ class CircuitSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = [
|
||||
'id', 'url', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
|
||||
'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate',
|
||||
'description', 'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class CircuitTerminationSerializer(CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
class CircuitTerminationSerializer(BaseModelSerializer, CableTerminationSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||
circuit = NestedCircuitSerializer()
|
||||
site = NestedSiteSerializer()
|
||||
site = NestedSiteSerializer(required=False)
|
||||
provider_network = NestedProviderNetworkSerializer(required=False)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'id', 'url', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||
'description', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable'
|
||||
'id', 'url', 'display', 'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed',
|
||||
'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type',
|
||||
'_occupied',
|
||||
]
|
||||
|
||||
@@ -13,5 +13,8 @@ router.register('circuit-types', views.CircuitTypeViewSet)
|
||||
router.register('circuits', views.CircuitViewSet)
|
||||
router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
||||
|
||||
# Provider networks
|
||||
router.register('provider-networks', views.ProviderNetworkViewSet)
|
||||
|
||||
app_name = 'circuits-api'
|
||||
urlpatterns = router.urls
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from django.db.models import Count, Prefetch
|
||||
from rest_framework.routers import APIRootView
|
||||
|
||||
from circuits import filters
|
||||
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
||||
from circuits.models import *
|
||||
from dcim.api.views import PathEndpointMixin
|
||||
from extras.api.views import CustomFieldModelViewSet
|
||||
from netbox.api.views import ModelViewSet
|
||||
from utilities.utils import count_related
|
||||
from . import serializers
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ class CircuitsRootView(APIRootView):
|
||||
|
||||
class ProviderViewSet(CustomFieldModelViewSet):
|
||||
queryset = Provider.objects.prefetch_related('tags').annotate(
|
||||
circuit_count=Count('circuits')
|
||||
).order_by(*Provider._meta.ordering)
|
||||
circuit_count=count_related(Circuit, 'provider')
|
||||
)
|
||||
serializer_class = serializers.ProviderSerializer
|
||||
filterset_class = filters.ProviderFilterSet
|
||||
|
||||
@@ -33,10 +33,10 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
||||
# Circuit Types
|
||||
#
|
||||
|
||||
class CircuitTypeViewSet(ModelViewSet):
|
||||
class CircuitTypeViewSet(CustomFieldModelViewSet):
|
||||
queryset = CircuitType.objects.annotate(
|
||||
circuit_count=Count('circuits')
|
||||
).order_by(*CircuitType._meta.ordering)
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
serializer_class = serializers.CircuitTypeSerializer
|
||||
filterset_class = filters.CircuitTypeFilterSet
|
||||
|
||||
@@ -47,8 +47,7 @@ class CircuitTypeViewSet(ModelViewSet):
|
||||
|
||||
class CircuitViewSet(CustomFieldModelViewSet):
|
||||
queryset = Circuit.objects.prefetch_related(
|
||||
Prefetch('terminations', queryset=CircuitTermination.objects.prefetch_related('site')),
|
||||
'type', 'tenant', 'provider',
|
||||
'type', 'tenant', 'provider', 'termination_a', 'termination_z'
|
||||
).prefetch_related('tags')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
filterset_class = filters.CircuitFilterSet
|
||||
@@ -60,7 +59,18 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
||||
|
||||
class CircuitTerminationViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = CircuitTermination.objects.prefetch_related(
|
||||
'circuit', 'site', '_path__destination', 'cable'
|
||||
'circuit', 'site', 'provider_network', 'cable'
|
||||
)
|
||||
serializer_class = serializers.CircuitTerminationSerializer
|
||||
filterset_class = filters.CircuitTerminationFilterSet
|
||||
brief_prefetch_fields = ['circuit']
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class ProviderNetworkViewSet(CustomFieldModelViewSet):
|
||||
queryset = ProviderNetwork.objects.prefetch_related('tags')
|
||||
serializer_class = serializers.ProviderNetworkSerializer
|
||||
filterset_class = filters.ProviderNetworkFilterSet
|
||||
|
||||
@@ -2,24 +2,25 @@ import django_filters
|
||||
from django.db.models import Q
|
||||
|
||||
from dcim.filters import CableTerminationFilterSet, PathEndpointFilterSet
|
||||
from dcim.models import Region, Site
|
||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||
from tenancy.filters import TenancyFilterSet
|
||||
from utilities.filters import (
|
||||
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
||||
)
|
||||
from .choices import *
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from .models import *
|
||||
|
||||
__all__ = (
|
||||
'CircuitFilterSet',
|
||||
'CircuitTerminationFilterSet',
|
||||
'CircuitTypeFilterSet',
|
||||
'ProviderNetworkFilterSet',
|
||||
'ProviderFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -37,6 +38,19 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilte
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_group_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='circuits__terminations__site__group',
|
||||
lookup_expr='in',
|
||||
label='Site group (ID)',
|
||||
)
|
||||
site_group = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='circuits__terminations__site__group',
|
||||
lookup_expr='in',
|
||||
to_field_name='slug',
|
||||
label='Site group (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='circuits__terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
@@ -66,14 +80,7 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilte
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
||||
class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -88,6 +95,48 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
|
||||
to_field_name='slug',
|
||||
label='Provider (slug)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = ['id', 'name']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(description__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
|
||||
|
||||
class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Provider.objects.all(),
|
||||
label='Provider (ID)',
|
||||
)
|
||||
provider = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider__slug',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Provider (slug)',
|
||||
)
|
||||
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='terminations__provider_network',
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
label='ProviderNetwork (ID)',
|
||||
)
|
||||
type_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=CircuitType.objects.all(),
|
||||
label='Circuit type (ID)',
|
||||
@@ -102,17 +151,6 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
|
||||
choices=CircuitStatusChoices,
|
||||
null_value=None
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='terminations__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='terminations__site__region',
|
||||
@@ -126,6 +164,30 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_group_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='terminations__site__group',
|
||||
lookup_expr='in',
|
||||
label='Site group (ID)',
|
||||
)
|
||||
site_group = TreeNodeMultipleChoiceFilter(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
field_name='terminations__site__group',
|
||||
lookup_expr='in',
|
||||
to_field_name='slug',
|
||||
label='Site group (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='terminations__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
@@ -145,7 +207,7 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, Cr
|
||||
).distinct()
|
||||
|
||||
|
||||
class CircuitTerminationFilterSet(BaseFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||
class CircuitTerminationFilterSet(BaseFilterSet, CableTerminationFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -164,6 +226,10 @@ class CircuitTerminationFilterSet(BaseFilterSet, CableTerminationFilterSet, Path
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
label='ProviderNetwork (ID)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.models import Region, Site
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from extras.forms import (
|
||||
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm,
|
||||
)
|
||||
@@ -8,12 +9,12 @@ from extras.models import Tag
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DatePicker,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2,
|
||||
StaticSelect2Multiple, TagFilterField,
|
||||
add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, DatePicker,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SmallTextarea, SlugField,
|
||||
StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||
)
|
||||
from .choices import CircuitStatusChoices
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from .models import *
|
||||
|
||||
|
||||
#
|
||||
@@ -33,6 +34,10 @@ class ProviderForm(BootstrapMixin, CustomFieldModelForm):
|
||||
fields = [
|
||||
'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Provider', ('name', 'slug', 'asn', 'tags')),
|
||||
('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
|
||||
)
|
||||
widgets = {
|
||||
'noc_contact': SmallTextarea(
|
||||
attrs={'rows': 5}
|
||||
@@ -101,24 +106,101 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = Provider
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
label=_('Search')
|
||||
)
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False
|
||||
required=False,
|
||||
label=_('Region')
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
site_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
'region_id': '$region_id'
|
||||
},
|
||||
label=_('Site')
|
||||
)
|
||||
asn = forms.IntegerField(
|
||||
required=False,
|
||||
label='ASN'
|
||||
label=_('ASN')
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class ProviderNetworkForm(BootstrapMixin, CustomFieldModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all()
|
||||
)
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'provider', 'name', 'description', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Provider Network', ('provider', 'name', 'description', 'tags')),
|
||||
)
|
||||
|
||||
|
||||
class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
|
||||
provider = CSVModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text='Assigned provider'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'provider', 'name', 'description', 'comments',
|
||||
]
|
||||
|
||||
|
||||
class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'description', 'comments',
|
||||
]
|
||||
|
||||
|
||||
class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = ProviderNetwork
|
||||
field_order = ['q', 'provider_id']
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label=_('Search')
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
required=False,
|
||||
label=_('Provider')
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
@@ -127,7 +209,7 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
# Circuit types
|
||||
#
|
||||
|
||||
class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
|
||||
class CircuitTypeForm(BootstrapMixin, CustomFieldModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@@ -137,7 +219,21 @@ class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
|
||||
]
|
||||
|
||||
|
||||
class CircuitTypeCSVForm(CSVModelForm):
|
||||
class CircuitTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=CircuitType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
|
||||
|
||||
class CircuitTypeCSVForm(CustomFieldModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@@ -171,6 +267,10 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
|
||||
'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
help_texts = {
|
||||
'cid': "Unique circuit ID",
|
||||
'commit_rate': "Committed rate",
|
||||
@@ -178,6 +278,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
widgets = {
|
||||
'status': StaticSelect2(),
|
||||
'install_date': DatePicker(),
|
||||
'commit_rate': SelectSpeedWidget(),
|
||||
}
|
||||
|
||||
|
||||
@@ -256,44 +357,53 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
||||
class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
model = Circuit
|
||||
field_order = [
|
||||
'q', 'type', 'provider', 'status', 'region', 'site', 'tenant_group', 'tenant', 'commit_rate',
|
||||
'q', 'type_id', 'provider_id', 'provider_network_id', 'status', 'region_id', 'site_id', 'tenant_group_id', 'tenant_id',
|
||||
'commit_rate',
|
||||
]
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
label=_('Search')
|
||||
)
|
||||
type = DynamicModelMultipleChoiceField(
|
||||
type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=CircuitType.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False
|
||||
required=False,
|
||||
label=_('Type')
|
||||
)
|
||||
provider = DynamicModelMultipleChoiceField(
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False
|
||||
required=False,
|
||||
label=_('Provider')
|
||||
)
|
||||
provider_network_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'provider_id': '$provider_id'
|
||||
},
|
||||
label=_('Provider network')
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=CircuitStatusChoices,
|
||||
required=False,
|
||||
widget=StaticSelect2Multiple()
|
||||
)
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False
|
||||
required=False,
|
||||
label=_('Region')
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
site_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
'region_id': '$region_id'
|
||||
},
|
||||
label=_('Site')
|
||||
)
|
||||
commit_rate = forms.IntegerField(
|
||||
required=False,
|
||||
min_value=0,
|
||||
label='Commit rate (Kbps)'
|
||||
label=_('Commit rate (Kbps)')
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
@@ -310,17 +420,31 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site_group = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
initial_params={
|
||||
'sites': '$site'
|
||||
}
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
query_params={
|
||||
'region_id': '$region'
|
||||
}
|
||||
'region_id': '$region',
|
||||
'group_id': '$site_group',
|
||||
},
|
||||
required=False
|
||||
)
|
||||
provider_network = DynamicModelChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'term_side', 'region', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
|
||||
'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected', 'port_speed',
|
||||
'upstream_speed', 'xconnect_id', 'pp_info', 'description',
|
||||
]
|
||||
help_texts = {
|
||||
'port_speed': "Physical circuit speed",
|
||||
@@ -329,4 +453,11 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
}
|
||||
widgets = {
|
||||
'term_side': forms.HiddenInput(),
|
||||
'port_speed': SelectSpeedWidget(),
|
||||
'upstream_speed': SelectSpeedWidget(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['provider_network'].widget.add_query_param('provider_id', self.instance.circuit.provider_id)
|
||||
|
||||
47
netbox/circuits/migrations/0025_standardize_models.py
Normal file
47
netbox/circuits/migrations/0025_standardize_models.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0024_standardize_name_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuittype',
|
||||
name='custom_field_data',
|
||||
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='id',
|
||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='id',
|
||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittype',
|
||||
name='id',
|
||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='id',
|
||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
]
|
||||
16
netbox/circuits/migrations/0026_mark_connected.py
Normal file
16
netbox/circuits/migrations/0026_mark_connected.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0025_standardize_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='mark_connected',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
65
netbox/circuits/migrations/0027_providernetwork.py
Normal file
65
netbox/circuits/migrations/0027_providernetwork.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import django.core.serializers.json
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0058_journalentry'),
|
||||
('circuits', '0026_mark_connected'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Create the new ProviderNetwork model
|
||||
migrations.CreateModel(
|
||||
name='ProviderNetwork',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='networks', to='circuits.provider')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('provider', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='providernetwork',
|
||||
constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_provider_name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='providernetwork',
|
||||
unique_together={('provider', 'name')},
|
||||
),
|
||||
|
||||
# Add ProviderNetwork FK to CircuitTermination
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='provider_network',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='circuits.providernetwork'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='site',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'),
|
||||
),
|
||||
|
||||
# Add FKs to CircuitTermination on Circuit
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='termination_a',
|
||||
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='termination_z',
|
||||
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
import sys
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def cache_circuit_terminations(apps, schema_editor):
|
||||
Circuit = apps.get_model('circuits', 'Circuit')
|
||||
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||
|
||||
if 'test' not in sys.argv:
|
||||
print(f"\n Caching circuit terminations...", flush=True)
|
||||
|
||||
a_terminations = {
|
||||
ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='A')
|
||||
}
|
||||
z_terminations = {
|
||||
ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='Z')
|
||||
}
|
||||
for circuit in Circuit.objects.all():
|
||||
Circuit.objects.filter(pk=circuit.pk).update(
|
||||
termination_a_id=a_terminations.get(circuit.pk),
|
||||
termination_z_id=z_terminations.get(circuit.pk),
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0027_providernetwork'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=cache_circuit_terminations,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
32
netbox/circuits/migrations/0029_circuit_tracing.py
Normal file
32
netbox/circuits/migrations/0029_circuit_tracing.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
def delete_obsolete_cablepaths(apps, schema_editor):
|
||||
"""
|
||||
Delete all CablePath instances which originate or terminate at a CircuitTermination.
|
||||
"""
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||
CablePath = apps.get_model('dcim', 'CablePath')
|
||||
|
||||
ct = ContentType.objects.get_for_model(CircuitTermination)
|
||||
CablePath.objects.filter(Q(origin_type=ct) | Q(destination_type=ct)).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0028_cache_circuit_terminations'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='circuittermination',
|
||||
name='_path',
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=delete_obsolete_cablepaths,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
@@ -1,27 +1,27 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from dcim.fields import ASNField
|
||||
from dcim.models import CableTermination, PathEndpoint
|
||||
from extras.models import ChangeLoggedModel, CustomFieldModel, ObjectChange, TaggedItem
|
||||
from extras.models import ObjectChange
|
||||
from extras.utils import extras_features
|
||||
from netbox.models import BigIDModel, ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.utils import serialize_object
|
||||
from .choices import *
|
||||
from .querysets import CircuitQuerySet
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Circuit',
|
||||
'CircuitTermination',
|
||||
'CircuitType',
|
||||
'ProviderNetwork',
|
||||
'Provider',
|
||||
)
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
class Provider(PrimaryModel):
|
||||
"""
|
||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||
stores information pertinent to the user's relationship with the Provider.
|
||||
@@ -60,7 +60,6 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
@@ -78,7 +77,7 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('circuits:provider', args=[self.slug])
|
||||
return reverse('circuits:provider', args=[self.pk])
|
||||
|
||||
def to_csv(self):
|
||||
return (
|
||||
@@ -93,7 +92,65 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
)
|
||||
|
||||
|
||||
class CircuitType(ChangeLoggedModel):
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class ProviderNetwork(PrimaryModel):
|
||||
"""
|
||||
This represents a provider network which exists outside of NetBox, the details of which are unknown or
|
||||
unimportant to the user.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
provider = models.ForeignKey(
|
||||
to='circuits.Provider',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='networks'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
csv_headers = [
|
||||
'provider', 'name', 'description', 'comments',
|
||||
]
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('provider', 'name')
|
||||
constraints = (
|
||||
models.UniqueConstraint(
|
||||
fields=('provider', 'name'),
|
||||
name='circuits_providernetwork_provider_name'
|
||||
),
|
||||
)
|
||||
unique_together = ('provider', 'name')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('circuits:providernetwork', args=[self.pk])
|
||||
|
||||
def to_csv(self):
|
||||
return (
|
||||
self.provider.name,
|
||||
self.name,
|
||||
self.description,
|
||||
self.comments,
|
||||
)
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||
class CircuitType(OrganizationalModel):
|
||||
"""
|
||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||
"Long Haul," "Metro," or "Out-of-Band".
|
||||
@@ -122,7 +179,7 @@ class CircuitType(ChangeLoggedModel):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug)
|
||||
return reverse('circuits:circuittype', args=[self.pk])
|
||||
|
||||
def to_csv(self):
|
||||
return (
|
||||
@@ -133,7 +190,7 @@ class CircuitType(ChangeLoggedModel):
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||
class Circuit(PrimaryModel):
|
||||
"""
|
||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||
@@ -182,8 +239,25 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = CircuitQuerySet.as_manager()
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
# Cache associated CircuitTerminations
|
||||
termination_a = models.ForeignKey(
|
||||
to='circuits.CircuitTermination',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
editable=False,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
termination_z = models.ForeignKey(
|
||||
to='circuits.CircuitTermination',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
editable=False,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
csv_headers = [
|
||||
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
|
||||
@@ -218,22 +292,9 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||
def get_status_class(self):
|
||||
return CircuitStatusChoices.CSS_CLASSES.get(self.status)
|
||||
|
||||
def _get_termination(self, side):
|
||||
for ct in self.terminations.all():
|
||||
if ct.term_side == side:
|
||||
return ct
|
||||
return None
|
||||
|
||||
@property
|
||||
def termination_a(self):
|
||||
return self._get_termination('A')
|
||||
|
||||
@property
|
||||
def termination_z(self):
|
||||
return self._get_termination('Z')
|
||||
|
||||
|
||||
class CircuitTermination(PathEndpoint, CableTermination):
|
||||
@extras_features('webhooks')
|
||||
class CircuitTermination(ChangeLoggedModel, CableTermination):
|
||||
circuit = models.ForeignKey(
|
||||
to='circuits.Circuit',
|
||||
on_delete=models.CASCADE,
|
||||
@@ -247,7 +308,16 @@ class CircuitTermination(PathEndpoint, CableTermination):
|
||||
site = models.ForeignKey(
|
||||
to='dcim.Site',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='circuit_terminations'
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
provider_network = models.ForeignKey(
|
||||
to=ProviderNetwork,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
port_speed = models.PositiveIntegerField(
|
||||
verbose_name='Port speed (Kbps)',
|
||||
@@ -282,26 +352,33 @@ class CircuitTermination(PathEndpoint, CableTermination):
|
||||
unique_together = ['circuit', 'term_side']
|
||||
|
||||
def __str__(self):
|
||||
return 'Side {}'.format(self.get_term_side_display())
|
||||
return f'Termination {self.term_side}: {self.site or self.provider_network}'
|
||||
|
||||
def get_absolute_url(self):
|
||||
if self.site:
|
||||
return self.site.get_absolute_url()
|
||||
return self.provider_network.get_absolute_url()
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Must define either site *or* provider network
|
||||
if self.site is None and self.provider_network is None:
|
||||
raise ValidationError("A circuit termination must attach to either a site or a provider network.")
|
||||
if self.site and self.provider_network:
|
||||
raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
|
||||
|
||||
def to_objectchange(self, action):
|
||||
# Annotate the parent Circuit
|
||||
try:
|
||||
related_object = self.circuit
|
||||
circuit = self.circuit
|
||||
except Circuit.DoesNotExist:
|
||||
# Parent circuit has been deleted
|
||||
related_object = None
|
||||
|
||||
return ObjectChange(
|
||||
changed_object=self,
|
||||
object_repr=str(self),
|
||||
action=action,
|
||||
related_object=related_object,
|
||||
object_data=serialize_object(self)
|
||||
)
|
||||
circuit = None
|
||||
return super().to_objectchange(action, related_object=circuit)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
def parent_object(self):
|
||||
return self.circuit
|
||||
|
||||
def get_peer_termination(self):
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from django.db.models import OuterRef, Subquery
|
||||
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
|
||||
|
||||
class CircuitQuerySet(RestrictedQuerySet):
|
||||
|
||||
def annotate_sites(self):
|
||||
"""
|
||||
Annotate the A and Z termination site names for ordering.
|
||||
"""
|
||||
from circuits.models import CircuitTermination
|
||||
_terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk'))
|
||||
return self.annotate(
|
||||
a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]),
|
||||
z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]),
|
||||
)
|
||||
@@ -2,16 +2,28 @@ from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
from dcim.signals import rebuild_paths
|
||||
from .models import Circuit, CircuitTermination
|
||||
|
||||
|
||||
@receiver((post_save, post_delete), sender=CircuitTermination)
|
||||
@receiver(post_save, sender=CircuitTermination)
|
||||
def update_circuit(instance, **kwargs):
|
||||
"""
|
||||
When a CircuitTermination has been modified, update the last_updated time of its parent Circuit.
|
||||
When a CircuitTermination has been modified, update its parent Circuit.
|
||||
"""
|
||||
circuits = Circuit.objects.filter(pk=instance.circuit_id)
|
||||
time = timezone.now()
|
||||
for circuit in circuits:
|
||||
circuit.last_updated = time
|
||||
circuit.save()
|
||||
fields = {
|
||||
'last_updated': timezone.now(),
|
||||
f'termination_{instance.term_side.lower()}': instance.pk,
|
||||
}
|
||||
Circuit.objects.filter(pk=instance.circuit_id).update(**fields)
|
||||
|
||||
|
||||
@receiver((post_save, post_delete), sender=CircuitTermination)
|
||||
def rebuild_cablepaths(instance, raw=False, **kwargs):
|
||||
"""
|
||||
Rebuild any CablePaths which traverse the peer CircuitTermination.
|
||||
"""
|
||||
if not raw:
|
||||
peer_termination = instance.get_peer_termination()
|
||||
if peer_termination:
|
||||
rebuild_paths(peer_termination)
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from tenancy.tables import COL_TENANT
|
||||
from tenancy.tables import TenantColumn
|
||||
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
from .models import *
|
||||
|
||||
|
||||
CIRCUITTERMINATION_LINK = """
|
||||
{% if value.site %}
|
||||
<a href="{{ value.site.get_absolute_url }}">{{ value.site }}</a>
|
||||
{% elif value.provider_network %}
|
||||
<a href="{{ value.provider_network.get_absolute_url }}">{{ value.provider_network }}</a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
@@ -12,7 +21,9 @@ from .models import Circuit, CircuitType, Provider
|
||||
|
||||
class ProviderTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn()
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
circuit_count = tables.Column(
|
||||
accessor=Accessor('count_circuits'),
|
||||
verbose_name='Circuits'
|
||||
@@ -29,17 +40,41 @@ class ProviderTable(BaseTable):
|
||||
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class ProviderNetworkTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
provider = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='circuits:providernetwork_list'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ProviderNetwork
|
||||
fields = ('pk', 'name', 'provider', 'description', 'tags')
|
||||
default_columns = ('pk', 'name', 'provider', 'description')
|
||||
|
||||
|
||||
#
|
||||
# Circuit types
|
||||
#
|
||||
|
||||
class CircuitTypeTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn()
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
circuit_count = tables.Column(
|
||||
verbose_name='Circuits'
|
||||
)
|
||||
actions = ButtonsColumn(CircuitType, pk_field='slug')
|
||||
actions = ButtonsColumn(CircuitType)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = CircuitType
|
||||
@@ -53,22 +88,22 @@ class CircuitTypeTable(BaseTable):
|
||||
|
||||
class CircuitTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
cid = tables.LinkColumn(
|
||||
cid = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='ID'
|
||||
)
|
||||
provider = tables.LinkColumn(
|
||||
viewname='circuits:provider',
|
||||
args=[Accessor('provider__slug')]
|
||||
provider = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
status = ChoiceFieldColumn()
|
||||
tenant = tables.TemplateColumn(
|
||||
template_code=COL_TENANT
|
||||
tenant = TenantColumn()
|
||||
termination_a = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
verbose_name='Side A'
|
||||
)
|
||||
a_side = tables.Column(
|
||||
verbose_name='A Side'
|
||||
)
|
||||
z_side = tables.Column(
|
||||
verbose_name='Z Side'
|
||||
termination_z = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
verbose_name='Side Z'
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='circuits:circuit_list'
|
||||
@@ -77,7 +112,9 @@ class CircuitTable(BaseTable):
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Circuit
|
||||
fields = (
|
||||
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'install_date', 'commit_rate',
|
||||
'description', 'tags',
|
||||
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date',
|
||||
'commit_rate', 'description', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'description',
|
||||
)
|
||||
default_columns = ('pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'description')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from circuits.choices import *
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from circuits.models import *
|
||||
from dcim.models import Site
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
|
||||
@@ -17,7 +17,7 @@ class AppTest(APITestCase):
|
||||
|
||||
class ProviderTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Provider
|
||||
brief_fields = ['circuit_count', 'id', 'name', 'slug', 'url']
|
||||
brief_fields = ['circuit_count', 'display', 'id', 'name', 'slug', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'name': 'Provider 4',
|
||||
@@ -49,7 +49,7 @@ class ProviderTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class CircuitTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
model = CircuitType
|
||||
brief_fields = ['circuit_count', 'id', 'name', 'slug', 'url']
|
||||
brief_fields = ['circuit_count', 'display', 'id', 'name', 'slug', 'url']
|
||||
create_data = (
|
||||
{
|
||||
'name': 'Circuit Type 4',
|
||||
@@ -81,7 +81,7 @@ class CircuitTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class CircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Circuit
|
||||
brief_fields = ['cid', 'id', 'url']
|
||||
brief_fields = ['cid', 'display', 'id', 'url']
|
||||
bulk_update_data = {
|
||||
'status': 'planned',
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
|
||||
class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||
model = CircuitTermination
|
||||
brief_fields = ['cable', 'circuit', 'id', 'term_side', 'url']
|
||||
brief_fields = ['_occupied', 'cable', 'circuit', 'display', 'id', 'term_side', 'url']
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -178,3 +178,43 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||
cls.bulk_update_data = {
|
||||
'port_speed': 123456
|
||||
}
|
||||
|
||||
|
||||
class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ProviderNetwork
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1'),
|
||||
Provider(name='Provider 2', slug='provider-2'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
provider_networks = (
|
||||
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 2', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 3', provider=providers[0]),
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'name': 'Provider Network 4',
|
||||
'provider': providers[0].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Provider Network 5',
|
||||
'provider': providers[0].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Provider Network 6',
|
||||
'provider': providers[0].pk,
|
||||
},
|
||||
]
|
||||
|
||||
cls.bulk_update_data = {
|
||||
'provider': providers[1].pk,
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ from django.test import TestCase
|
||||
|
||||
from circuits.choices import *
|
||||
from circuits.filters import *
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from dcim.models import Cable, Region, Site
|
||||
from circuits.models import *
|
||||
from dcim.models import Cable, Region, Site, SiteGroup
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
|
||||
@@ -27,13 +27,20 @@ class ProviderTestCase(TestCase):
|
||||
Region(name='Test Region 1', slug='test-region-1'),
|
||||
Region(name='Test Region 2', slug='test-region-2'),
|
||||
)
|
||||
# Can't use bulk_create for models with MPTT fields
|
||||
for r in regions:
|
||||
r.save()
|
||||
|
||||
site_groups = (
|
||||
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
||||
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||
)
|
||||
for site_group in site_groups:
|
||||
site_group.save()
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
@@ -74,13 +81,6 @@ class ProviderTestCase(TestCase):
|
||||
params = {'account': ['1234', '2345']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
@@ -88,6 +88,20 @@ class ProviderTestCase(TestCase):
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site_group(self):
|
||||
site_groups = SiteGroup.objects.all()[:2]
|
||||
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class CircuitTypeTestCase(TestCase):
|
||||
queryset = CircuitType.objects.all()
|
||||
@@ -127,14 +141,21 @@ class CircuitTestCase(TestCase):
|
||||
Region(name='Test Region 2', slug='test-region-2'),
|
||||
Region(name='Test Region 3', slug='test-region-3'),
|
||||
)
|
||||
# Can't use bulk_create for models with MPTT fields
|
||||
for r in regions:
|
||||
r.save()
|
||||
|
||||
site_groups = (
|
||||
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
||||
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||
)
|
||||
for site_group in site_groups:
|
||||
site_group.save()
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
|
||||
Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
|
||||
Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
@@ -165,6 +186,13 @@ class CircuitTestCase(TestCase):
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
provider_networks = (
|
||||
ProviderNetwork(name='Provider Network 1', provider=providers[1]),
|
||||
ProviderNetwork(name='Provider Network 2', provider=providers[1]),
|
||||
ProviderNetwork(name='Provider Network 3', provider=providers[1]),
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
circuits = (
|
||||
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE),
|
||||
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE),
|
||||
@@ -179,6 +207,9 @@ class CircuitTestCase(TestCase):
|
||||
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[3], provider_network=provider_networks[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[4], provider_network=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], provider_network=provider_networks[2], term_side='A'),
|
||||
))
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
@@ -205,6 +236,11 @@ class CircuitTestCase(TestCase):
|
||||
params = {'provider': [provider.slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_provider_network(self):
|
||||
provider_networks = ProviderNetwork.objects.all()[:2]
|
||||
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_type(self):
|
||||
circuit_type = CircuitType.objects.first()
|
||||
params = {'type_id': [circuit_type.pk]}
|
||||
@@ -223,6 +259,13 @@ class CircuitTestCase(TestCase):
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site_group(self):
|
||||
site_groups = SiteGroup.objects.all()[:2]
|
||||
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
@@ -253,14 +296,14 @@ class CircuitTerminationTestCase(TestCase):
|
||||
def setUpTestData(cls):
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1'),
|
||||
Site(name='Test Site 2', slug='test-site-2'),
|
||||
Site(name='Test Site 3', slug='test-site-3'),
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
Site(name='Site 3', slug='site-3'),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
circuit_types = (
|
||||
CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
|
||||
CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
|
||||
)
|
||||
CircuitType.objects.bulk_create(circuit_types)
|
||||
|
||||
@@ -269,10 +312,20 @@ class CircuitTerminationTestCase(TestCase):
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
provider_networks = (
|
||||
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 2', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 3', provider=providers[0]),
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
circuits = (
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 1'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 2'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 3'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 4'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 5'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 6'),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
@@ -283,6 +336,9 @@ class CircuitTerminationTestCase(TestCase):
|
||||
CircuitTermination(circuit=circuits[1], site=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
|
||||
CircuitTermination(circuit=circuits[3], provider_network=provider_networks[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[4], provider_network=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], provider_network=provider_networks[2], term_side='A'),
|
||||
))
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
@@ -290,7 +346,7 @@ class CircuitTerminationTestCase(TestCase):
|
||||
|
||||
def test_term_side(self):
|
||||
params = {'term_side': 'A'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_port_speed(self):
|
||||
params = {'port_speed': ['1000', '2000']}
|
||||
@@ -316,12 +372,48 @@ class CircuitTerminationTestCase(TestCase):
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_provider_network(self):
|
||||
provider_networks = ProviderNetwork.objects.all()[:2]
|
||||
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_cabled(self):
|
||||
params = {'cabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_connected(self):
|
||||
params = {'connected': True}
|
||||
|
||||
class ProviderNetworkTestCase(TestCase):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
filterset = ProviderNetworkFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1'),
|
||||
Provider(name='Provider 2', slug='provider-2'),
|
||||
Provider(name='Provider 3', slug='provider-3'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
provider_networks = (
|
||||
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 2', provider=providers[1]),
|
||||
ProviderNetwork(name='Provider Network 3', provider=providers[2]),
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
def test_id(self):
|
||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Provider Network 1', 'Provider Network 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_provider(self):
|
||||
providers = Provider.objects.all()[:2]
|
||||
params = {'provider_id': [providers[0].pk, providers[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'provider': [providers[0].slug, providers[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'connected': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import datetime
|
||||
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from circuits.choices import *
|
||||
from circuits.models import Circuit, CircuitType, Provider
|
||||
from utilities.testing import ViewTestCases
|
||||
from circuits.models import *
|
||||
from dcim.models import Cable, Interface, Site
|
||||
from utilities.testing import ViewTestCases, create_tags, create_test_device
|
||||
|
||||
|
||||
class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
@@ -17,7 +21,7 @@ class ProviderTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
Provider(name='Provider 3', slug='provider-3', asn=65003),
|
||||
])
|
||||
|
||||
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Provider X',
|
||||
@@ -73,6 +77,10 @@ class CircuitTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
"Circuit Type 6,circuit-type-6",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'Foo',
|
||||
}
|
||||
|
||||
|
||||
class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = Circuit
|
||||
@@ -98,7 +106,7 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
Circuit(cid='Circuit 3', provider=providers[0], type=circuittypes[0]),
|
||||
])
|
||||
|
||||
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'cid': 'Circuit X',
|
||||
@@ -129,3 +137,99 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'description': 'New description',
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
|
||||
class ProviderNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = ProviderNetwork
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1'),
|
||||
Provider(name='Provider 2', slug='provider-2'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
ProviderNetwork.objects.bulk_create([
|
||||
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 2', provider=providers[0]),
|
||||
ProviderNetwork(name='Provider Network 3', provider=providers[0]),
|
||||
])
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Provider Network X',
|
||||
'provider': providers[1].pk,
|
||||
'description': 'A new provider network',
|
||||
'comments': 'Longer description goes here',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,provider,description",
|
||||
"Provider Network 4,Provider 1,Foo",
|
||||
"Provider Network 5,Provider 1,Bar",
|
||||
"Provider Network 6,Provider 1,Baz",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'provider': providers[1].pk,
|
||||
'description': 'New description',
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
|
||||
class CircuitTerminationTestCase(
|
||||
ViewTestCases.EditObjectViewTestCase,
|
||||
ViewTestCases.DeleteObjectViewTestCase,
|
||||
):
|
||||
model = CircuitTermination
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
sites = (
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
Site(name='Site 3', slug='site-3'),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
|
||||
circuits = (
|
||||
Circuit(cid='Circuit 1', provider=provider, type=circuittype),
|
||||
Circuit(cid='Circuit 2', provider=provider, type=circuittype),
|
||||
Circuit(cid='Circuit 3', provider=provider, type=circuittype),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = (
|
||||
CircuitTermination(circuit=circuits[0], term_side='A', site=sites[0]),
|
||||
CircuitTermination(circuit=circuits[0], term_side='Z', site=sites[1]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='A', site=sites[0]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='Z', site=sites[1]),
|
||||
)
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
cls.form_data = {
|
||||
'term_side': 'A',
|
||||
'site': sites[2].pk,
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_trace(self):
|
||||
device = create_test_device('Device 1')
|
||||
|
||||
circuittermination = CircuitTermination.objects.first()
|
||||
interface = Interface.objects.create(
|
||||
device=device,
|
||||
name='Interface 1'
|
||||
)
|
||||
Cable(termination_a=circuittermination, termination_b=interface).save()
|
||||
|
||||
response = self.client.get(reverse('circuits:circuittermination_trace', kwargs={'pk': circuittermination.pk}))
|
||||
self.assertHttpStatus(response, 200)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from dcim.views import CableCreateView, PathTraceView
|
||||
from extras.views import ObjectChangeLogView
|
||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.views import SlugRedirectView
|
||||
from . import views
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from .models import *
|
||||
|
||||
app_name = 'circuits'
|
||||
urlpatterns = [
|
||||
@@ -14,19 +15,35 @@ urlpatterns = [
|
||||
path('providers/import/', views.ProviderBulkImportView.as_view(), name='provider_import'),
|
||||
path('providers/edit/', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'),
|
||||
path('providers/delete/', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
|
||||
path('providers/<slug:slug>/', views.ProviderView.as_view(), name='provider'),
|
||||
path('providers/<slug:slug>/edit/', views.ProviderEditView.as_view(), name='provider_edit'),
|
||||
path('providers/<slug:slug>/delete/', views.ProviderDeleteView.as_view(), name='provider_delete'),
|
||||
path('providers/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
|
||||
path('providers/<int:pk>/', views.ProviderView.as_view(), name='provider'),
|
||||
path('providers/<slug:slug>/', SlugRedirectView.as_view(), kwargs={'model': Provider}),
|
||||
path('providers/<int:pk>/edit/', views.ProviderEditView.as_view(), name='provider_edit'),
|
||||
path('providers/<int:pk>/delete/', views.ProviderDeleteView.as_view(), name='provider_delete'),
|
||||
path('providers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
|
||||
path('providers/<int:pk>/journal/', ObjectJournalView.as_view(), name='provider_journal', kwargs={'model': Provider}),
|
||||
|
||||
# Provider networks
|
||||
path('provider-networks/', views.ProviderNetworkListView.as_view(), name='providernetwork_list'),
|
||||
path('provider-networks/add/', views.ProviderNetworkEditView.as_view(), name='providernetwork_add'),
|
||||
path('provider-networks/import/', views.ProviderNetworkBulkImportView.as_view(), name='providernetwork_import'),
|
||||
path('provider-networks/edit/', views.ProviderNetworkBulkEditView.as_view(), name='providernetwork_bulk_edit'),
|
||||
path('provider-networks/delete/', views.ProviderNetworkBulkDeleteView.as_view(), name='providernetwork_bulk_delete'),
|
||||
path('provider-networks/<int:pk>/', views.ProviderNetworkView.as_view(), name='providernetwork'),
|
||||
path('provider-networks/<int:pk>/edit/', views.ProviderNetworkEditView.as_view(), name='providernetwork_edit'),
|
||||
path('provider-networks/<int:pk>/delete/', views.ProviderNetworkDeleteView.as_view(), name='providernetwork_delete'),
|
||||
path('provider-networks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='providernetwork_changelog', kwargs={'model': ProviderNetwork}),
|
||||
path('provider-networks/<int:pk>/journal/', ObjectJournalView.as_view(), name='providernetwork_journal', kwargs={'model': ProviderNetwork}),
|
||||
|
||||
# Circuit types
|
||||
path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'),
|
||||
path('circuit-types/add/', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
|
||||
path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'),
|
||||
path('circuit-types/edit/', views.CircuitTypeBulkEditView.as_view(), name='circuittype_bulk_edit'),
|
||||
path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
|
||||
path('circuit-types/<slug:slug>/edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
|
||||
path('circuit-types/<slug:slug>/delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'),
|
||||
path('circuit-types/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}),
|
||||
path('circuit-types/<int:pk>/', views.CircuitTypeView.as_view(), name='circuittype'),
|
||||
path('circuit-types/<int:pk>/edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
|
||||
path('circuit-types/<int:pk>/delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'),
|
||||
path('circuit-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}),
|
||||
|
||||
# Circuits
|
||||
path('circuits/', views.CircuitListView.as_view(), name='circuit_list'),
|
||||
@@ -38,6 +55,7 @@ urlpatterns = [
|
||||
path('circuits/<int:pk>/edit/', views.CircuitEditView.as_view(), name='circuit_edit'),
|
||||
path('circuits/<int:pk>/delete/', views.CircuitDeleteView.as_view(), name='circuit_delete'),
|
||||
path('circuits/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='circuit_changelog', kwargs={'model': Circuit}),
|
||||
path('circuits/<int:pk>/journal/', ObjectJournalView.as_view(), name='circuit_journal', kwargs={'model': Circuit}),
|
||||
path('circuits/<int:pk>/terminations/swap/', views.CircuitSwapTerminations.as_view(), name='circuit_terminations_swap'),
|
||||
|
||||
# Circuit terminations
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django_tables2 import RequestConfig
|
||||
|
||||
from netbox.views import generic
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.tables import paginate_table
|
||||
from utilities.utils import count_related
|
||||
from . import filters, forms, tables
|
||||
from .choices import CircuitTerminationSideChoices
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from .models import *
|
||||
|
||||
|
||||
#
|
||||
@@ -17,7 +17,9 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
#
|
||||
|
||||
class ProviderListView(generic.ObjectListView):
|
||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
||||
queryset = Provider.objects.annotate(
|
||||
count_circuits=count_related(Circuit, 'provider')
|
||||
)
|
||||
filterset = filters.ProviderFilterSet
|
||||
filterset_form = forms.ProviderFilterForm
|
||||
table = tables.ProviderTable
|
||||
@@ -26,34 +28,25 @@ class ProviderListView(generic.ObjectListView):
|
||||
class ProviderView(generic.ObjectView):
|
||||
queryset = Provider.objects.all()
|
||||
|
||||
def get(self, request, slug):
|
||||
|
||||
provider = get_object_or_404(self.queryset, slug=slug)
|
||||
def get_extra_context(self, request, instance):
|
||||
circuits = Circuit.objects.restrict(request.user, 'view').filter(
|
||||
provider=provider
|
||||
provider=instance
|
||||
).prefetch_related(
|
||||
'type', 'tenant', 'terminations__site'
|
||||
).annotate_sites()
|
||||
)
|
||||
|
||||
circuits_table = tables.CircuitTable(circuits)
|
||||
circuits_table.columns.hide('provider')
|
||||
paginate_table(circuits_table, request)
|
||||
|
||||
paginate = {
|
||||
'paginator_class': EnhancedPaginator,
|
||||
'per_page': get_paginate_count(request)
|
||||
}
|
||||
RequestConfig(request, paginate).configure(circuits_table)
|
||||
|
||||
return render(request, 'circuits/provider.html', {
|
||||
'provider': provider,
|
||||
return {
|
||||
'circuits_table': circuits_table,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
class ProviderEditView(generic.ObjectEditView):
|
||||
queryset = Provider.objects.all()
|
||||
model_form = forms.ProviderForm
|
||||
template_name = 'circuits/provider_edit.html'
|
||||
|
||||
|
||||
class ProviderDeleteView(generic.ObjectDeleteView):
|
||||
@@ -67,27 +60,110 @@ class ProviderBulkImportView(generic.BulkImportView):
|
||||
|
||||
|
||||
class ProviderBulkEditView(generic.BulkEditView):
|
||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
||||
queryset = Provider.objects.annotate(
|
||||
count_circuits=count_related(Circuit, 'provider')
|
||||
)
|
||||
filterset = filters.ProviderFilterSet
|
||||
table = tables.ProviderTable
|
||||
form = forms.ProviderBulkEditForm
|
||||
|
||||
|
||||
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
||||
queryset = Provider.objects.annotate(
|
||||
count_circuits=count_related(Circuit, 'provider')
|
||||
)
|
||||
filterset = filters.ProviderFilterSet
|
||||
table = tables.ProviderTable
|
||||
|
||||
|
||||
#
|
||||
# Provider networks
|
||||
#
|
||||
|
||||
class ProviderNetworkListView(generic.ObjectListView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
filterset = filters.ProviderNetworkFilterSet
|
||||
filterset_form = forms.ProviderNetworkFilterForm
|
||||
table = tables.ProviderNetworkTable
|
||||
|
||||
|
||||
class ProviderNetworkView(generic.ObjectView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
circuits = Circuit.objects.restrict(request.user, 'view').filter(
|
||||
Q(termination_a__provider_network=instance.pk) |
|
||||
Q(termination_z__provider_network=instance.pk)
|
||||
).prefetch_related(
|
||||
'type', 'tenant', 'terminations__site'
|
||||
)
|
||||
|
||||
circuits_table = tables.CircuitTable(circuits)
|
||||
circuits_table.columns.hide('termination_a')
|
||||
circuits_table.columns.hide('termination_z')
|
||||
paginate_table(circuits_table, request)
|
||||
|
||||
return {
|
||||
'circuits_table': circuits_table,
|
||||
}
|
||||
|
||||
|
||||
class ProviderNetworkEditView(generic.ObjectEditView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
model_form = forms.ProviderNetworkForm
|
||||
|
||||
|
||||
class ProviderNetworkDeleteView(generic.ObjectDeleteView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
|
||||
|
||||
class ProviderNetworkBulkImportView(generic.BulkImportView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
model_form = forms.ProviderNetworkCSVForm
|
||||
table = tables.ProviderNetworkTable
|
||||
|
||||
|
||||
class ProviderNetworkBulkEditView(generic.BulkEditView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
filterset = filters.ProviderNetworkFilterSet
|
||||
table = tables.ProviderNetworkTable
|
||||
form = forms.ProviderNetworkBulkEditForm
|
||||
|
||||
|
||||
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
filterset = filters.ProviderNetworkFilterSet
|
||||
table = tables.ProviderNetworkTable
|
||||
|
||||
|
||||
#
|
||||
# Circuit Types
|
||||
#
|
||||
|
||||
class CircuitTypeListView(generic.ObjectListView):
|
||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
||||
queryset = CircuitType.objects.annotate(
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
table = tables.CircuitTypeTable
|
||||
|
||||
|
||||
class CircuitTypeView(generic.ObjectView):
|
||||
queryset = CircuitType.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
circuits = Circuit.objects.restrict(request.user, 'view').filter(
|
||||
type=instance
|
||||
)
|
||||
|
||||
circuits_table = tables.CircuitTable(circuits)
|
||||
circuits_table.columns.hide('type')
|
||||
paginate_table(circuits_table, request)
|
||||
|
||||
return {
|
||||
'circuits_table': circuits_table,
|
||||
}
|
||||
|
||||
|
||||
class CircuitTypeEditView(generic.ObjectEditView):
|
||||
queryset = CircuitType.objects.all()
|
||||
model_form = forms.CircuitTypeForm
|
||||
@@ -103,8 +179,19 @@ class CircuitTypeBulkImportView(generic.BulkImportView):
|
||||
table = tables.CircuitTypeTable
|
||||
|
||||
|
||||
class CircuitTypeBulkEditView(generic.BulkEditView):
|
||||
queryset = CircuitType.objects.annotate(
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
filterset = filters.CircuitTypeFilterSet
|
||||
table = tables.CircuitTypeTable
|
||||
form = forms.CircuitTypeBulkEditForm
|
||||
|
||||
|
||||
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
||||
queryset = CircuitType.objects.annotate(
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
table = tables.CircuitTypeTable
|
||||
|
||||
|
||||
@@ -114,8 +201,8 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
|
||||
class CircuitListView(generic.ObjectListView):
|
||||
queryset = Circuit.objects.prefetch_related(
|
||||
'provider', 'type', 'tenant', 'terminations'
|
||||
).annotate_sites()
|
||||
'provider', 'type', 'tenant', 'termination_a', 'termination_z'
|
||||
)
|
||||
filterset = filters.CircuitFilterSet
|
||||
filterset_form = forms.CircuitFilterForm
|
||||
table = tables.CircuitTable
|
||||
@@ -124,36 +211,31 @@ class CircuitListView(generic.ObjectListView):
|
||||
class CircuitView(generic.ObjectView):
|
||||
queryset = Circuit.objects.all()
|
||||
|
||||
def get(self, request, pk):
|
||||
circuit = get_object_or_404(self.queryset, pk=pk)
|
||||
def get_extra_context(self, request, instance):
|
||||
|
||||
# A-side termination
|
||||
termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
||||
'site__region'
|
||||
).filter(
|
||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
|
||||
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_A
|
||||
).first()
|
||||
if termination_a and termination_a.connected_endpoint:
|
||||
termination_a.ip_addresses = termination_a.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
||||
|
||||
# Z-side termination
|
||||
termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
||||
'site__region'
|
||||
).filter(
|
||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
|
||||
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_Z
|
||||
).first()
|
||||
if termination_z and termination_z.connected_endpoint:
|
||||
termination_z.ip_addresses = termination_z.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
||||
|
||||
return render(request, 'circuits/circuit.html', {
|
||||
'circuit': circuit,
|
||||
return {
|
||||
'termination_a': termination_a,
|
||||
'termination_z': termination_z,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
class CircuitEditView(generic.ObjectEditView):
|
||||
queryset = Circuit.objects.all()
|
||||
model_form = forms.CircuitForm
|
||||
template_name = 'circuits/circuit_edit.html'
|
||||
|
||||
|
||||
class CircuitDeleteView(generic.ObjectDeleteView):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from dcim import models
|
||||
from netbox.api import WritableNestedSerializer
|
||||
from netbox.api.serializers import BaseModelSerializer, WritableNestedSerializer
|
||||
|
||||
__all__ = [
|
||||
'NestedCableSerializer',
|
||||
@@ -27,7 +27,7 @@ __all__ = [
|
||||
'NestedPowerPanelSerializer',
|
||||
'NestedPowerPortSerializer',
|
||||
'NestedPowerPortTemplateSerializer',
|
||||
'NestedRackGroupSerializer',
|
||||
'NestedLocationSerializer',
|
||||
'NestedRackReservationSerializer',
|
||||
'NestedRackRoleSerializer',
|
||||
'NestedRackSerializer',
|
||||
@@ -35,6 +35,7 @@ __all__ = [
|
||||
'NestedRearPortTemplateSerializer',
|
||||
'NestedRegionSerializer',
|
||||
'NestedSiteSerializer',
|
||||
'NestedSiteGroupSerializer',
|
||||
'NestedVirtualChassisSerializer',
|
||||
]
|
||||
|
||||
@@ -50,7 +51,17 @@ class NestedRegionSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Region
|
||||
fields = ['id', 'url', 'name', 'slug', 'site_count', '_depth']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'site_count', '_depth']
|
||||
|
||||
|
||||
class NestedSiteGroupSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
|
||||
site_count = serializers.IntegerField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.SiteGroup
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'site_count', '_depth']
|
||||
|
||||
|
||||
class NestedSiteSerializer(WritableNestedSerializer):
|
||||
@@ -58,21 +69,21 @@ class NestedSiteSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Site
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Racks
|
||||
#
|
||||
|
||||
class NestedRackGroupSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
|
||||
class NestedLocationSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
|
||||
rack_count = serializers.IntegerField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.RackGroup
|
||||
fields = ['id', 'url', 'name', 'slug', 'rack_count', '_depth']
|
||||
model = models.Location
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'rack_count', '_depth']
|
||||
|
||||
|
||||
class NestedRackRoleSerializer(WritableNestedSerializer):
|
||||
@@ -81,7 +92,7 @@ class NestedRackRoleSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.RackRole
|
||||
fields = ['id', 'url', 'name', 'slug', 'rack_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'rack_count']
|
||||
|
||||
|
||||
class NestedRackSerializer(WritableNestedSerializer):
|
||||
@@ -90,7 +101,7 @@ class NestedRackSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Rack
|
||||
fields = ['id', 'url', 'name', 'display_name', 'device_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'display_name', 'device_count']
|
||||
|
||||
|
||||
class NestedRackReservationSerializer(WritableNestedSerializer):
|
||||
@@ -99,7 +110,7 @@ class NestedRackReservationSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.RackReservation
|
||||
fields = ['id', 'url', 'user', 'units']
|
||||
fields = ['id', 'url', 'display', 'user', 'units']
|
||||
|
||||
def get_user(self, obj):
|
||||
return obj.user.username
|
||||
@@ -115,7 +126,7 @@ class NestedManufacturerSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Manufacturer
|
||||
fields = ['id', 'url', 'name', 'slug', 'devicetype_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'devicetype_count']
|
||||
|
||||
|
||||
class NestedDeviceTypeSerializer(WritableNestedSerializer):
|
||||
@@ -125,7 +136,7 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceType
|
||||
fields = ['id', 'url', 'manufacturer', 'model', 'slug', 'display_name', 'device_count']
|
||||
fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'device_count']
|
||||
|
||||
|
||||
class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -133,7 +144,7 @@ class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsolePortTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedConsoleServerPortTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -141,7 +152,7 @@ class NestedConsoleServerPortTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsoleServerPortTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedPowerPortTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -149,7 +160,7 @@ class NestedPowerPortTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPortTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedPowerOutletTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -157,7 +168,7 @@ class NestedPowerOutletTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerOutletTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedInterfaceTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -165,7 +176,7 @@ class NestedInterfaceTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.InterfaceTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedRearPortTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -173,7 +184,7 @@ class NestedRearPortTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.RearPortTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedFrontPortTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -181,7 +192,7 @@ class NestedFrontPortTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.FrontPortTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
|
||||
@@ -189,7 +200,7 @@ class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceBayTemplate
|
||||
fields = ['id', 'url', 'name']
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
#
|
||||
@@ -203,7 +214,7 @@ class NestedDeviceRoleSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceRole
|
||||
fields = ['id', 'url', 'name', 'slug', 'device_count', 'virtualmachine_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'device_count', 'virtualmachine_count']
|
||||
|
||||
|
||||
class NestedPlatformSerializer(WritableNestedSerializer):
|
||||
@@ -213,7 +224,7 @@ class NestedPlatformSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Platform
|
||||
fields = ['id', 'url', 'name', 'slug', 'device_count', 'virtualmachine_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'slug', 'device_count', 'virtualmachine_count']
|
||||
|
||||
|
||||
class NestedDeviceSerializer(WritableNestedSerializer):
|
||||
@@ -221,7 +232,7 @@ class NestedDeviceSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Device
|
||||
fields = ['id', 'url', 'name', 'display_name']
|
||||
fields = ['id', 'url', 'display', 'name', 'display_name']
|
||||
|
||||
|
||||
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
||||
@@ -230,7 +241,7 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsoleServerPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedConsolePortSerializer(WritableNestedSerializer):
|
||||
@@ -239,7 +250,7 @@ class NestedConsolePortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.ConsolePort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedPowerOutletSerializer(WritableNestedSerializer):
|
||||
@@ -248,7 +259,7 @@ class NestedPowerOutletSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerOutlet
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedPowerPortSerializer(WritableNestedSerializer):
|
||||
@@ -257,7 +268,7 @@ class NestedPowerPortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedInterfaceSerializer(WritableNestedSerializer):
|
||||
@@ -266,7 +277,7 @@ class NestedInterfaceSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.Interface
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedRearPortSerializer(WritableNestedSerializer):
|
||||
@@ -275,7 +286,7 @@ class NestedRearPortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.RearPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedFrontPortSerializer(WritableNestedSerializer):
|
||||
@@ -284,7 +295,7 @@ class NestedFrontPortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.FrontPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
|
||||
|
||||
|
||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
@@ -293,7 +304,7 @@ class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.DeviceBay
|
||||
fields = ['id', 'url', 'device', 'name']
|
||||
fields = ['id', 'url', 'display', 'device', 'name']
|
||||
|
||||
|
||||
class NestedInventoryItemSerializer(WritableNestedSerializer):
|
||||
@@ -303,19 +314,19 @@ class NestedInventoryItemSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.InventoryItem
|
||||
fields = ['id', 'url', 'device', 'name', '_depth']
|
||||
fields = ['id', 'url', 'display', 'device', 'name', '_depth']
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
class NestedCableSerializer(serializers.ModelSerializer):
|
||||
class NestedCableSerializer(BaseModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
|
||||
|
||||
class Meta:
|
||||
model = models.Cable
|
||||
fields = ['id', 'url', 'label']
|
||||
fields = ['id', 'url', 'display', 'label']
|
||||
|
||||
|
||||
#
|
||||
@@ -342,7 +353,7 @@ class NestedPowerPanelSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerPanel
|
||||
fields = ['id', 'url', 'name', 'powerfeed_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'powerfeed_count']
|
||||
|
||||
|
||||
class NestedPowerFeedSerializer(WritableNestedSerializer):
|
||||
@@ -350,4 +361,4 @@ class NestedPowerFeedSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.PowerFeed
|
||||
fields = ['id', 'url', 'name', 'cable']
|
||||
fields = ['id', 'url', 'display', 'name', 'cable', '_occupied']
|
||||
|
||||
@@ -3,23 +3,16 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
from timezone_field.rest_framework import TimeZoneSerializerField
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import (
|
||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||
VirtualChassis,
|
||||
)
|
||||
from dcim.utils import decompile_path_node
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from extras.api.serializers import TaggedObjectSerializer
|
||||
from dcim.models import *
|
||||
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
|
||||
from ipam.models import VLAN
|
||||
from netbox.api import (
|
||||
ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
|
||||
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||
from netbox.api.serializers import (
|
||||
NestedGroupModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer,
|
||||
WritableNestedSerializer,
|
||||
)
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
@@ -50,7 +43,7 @@ class CableTerminationSerializer(serializers.ModelSerializer):
|
||||
return None
|
||||
|
||||
|
||||
class ConnectedEndpointSerializer(ValidatedModelSerializer):
|
||||
class ConnectedEndpointSerializer(serializers.ModelSerializer):
|
||||
connected_endpoint_type = serializers.SerializerMethodField(read_only=True)
|
||||
connected_endpoint = serializers.SerializerMethodField(read_only=True)
|
||||
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
|
||||
@@ -82,23 +75,39 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
|
||||
# Regions/sites
|
||||
#
|
||||
|
||||
class RegionSerializer(serializers.ModelSerializer):
|
||||
class RegionSerializer(NestedGroupModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
|
||||
parent = NestedRegionSerializer(required=False, allow_null=True)
|
||||
site_count = serializers.IntegerField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'site_count', '_depth']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'site_count', '_depth',
|
||||
]
|
||||
|
||||
|
||||
class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class SiteGroupSerializer(NestedGroupModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
|
||||
parent = NestedRegionSerializer(required=False, allow_null=True)
|
||||
site_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SiteGroup
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'site_count', '_depth',
|
||||
]
|
||||
|
||||
|
||||
class SiteSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
|
||||
status = ChoiceField(choices=SiteStatusChoices, required=False)
|
||||
region = NestedRegionSerializer(required=False, allow_null=True)
|
||||
group = NestedSiteGroupSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
time_zone = TimeZoneField(required=False)
|
||||
time_zone = TimeZoneSerializerField(required=False)
|
||||
circuit_count = serializers.IntegerField(read_only=True)
|
||||
device_count = serializers.IntegerField(read_only=True)
|
||||
prefix_count = serializers.IntegerField(read_only=True)
|
||||
@@ -109,10 +118,10 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = [
|
||||
'id', 'url', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description',
|
||||
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
|
||||
'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn',
|
||||
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
|
||||
'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
|
||||
]
|
||||
|
||||
|
||||
@@ -120,31 +129,37 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
# Racks
|
||||
#
|
||||
|
||||
class RackGroupSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
|
||||
class LocationSerializer(NestedGroupModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
|
||||
site = NestedSiteSerializer()
|
||||
parent = NestedRackGroupSerializer(required=False, allow_null=True)
|
||||
parent = NestedLocationSerializer(required=False, allow_null=True)
|
||||
rack_count = serializers.IntegerField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
device_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RackGroup
|
||||
fields = ['id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'rack_count', '_depth']
|
||||
model = Location
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created',
|
||||
'last_updated', 'rack_count', 'device_count', '_depth',
|
||||
]
|
||||
|
||||
|
||||
class RackRoleSerializer(ValidatedModelSerializer):
|
||||
class RackRoleSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
|
||||
rack_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = ['id', 'url', 'name', 'slug', 'color', 'description', 'rack_count']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'rack_count',
|
||||
]
|
||||
|
||||
|
||||
class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class RackSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
|
||||
site = NestedSiteSerializer()
|
||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
||||
location = NestedLocationSerializer(required=False, allow_null=True, default=None)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
status = ChoiceField(choices=RackStatusChoices, required=False)
|
||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||
@@ -157,21 +172,22 @@ class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = [
|
||||
'id', 'url', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
|
||||
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
|
||||
'id', 'url', 'display', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status',
|
||||
'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||
'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||
'powerfeed_count',
|
||||
]
|
||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This
|
||||
# prevents facility_id from being interpreted as a required field.
|
||||
validators = [
|
||||
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'name'))
|
||||
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'name'))
|
||||
]
|
||||
|
||||
def validate(self, data):
|
||||
|
||||
# Validate uniqueness of (group, facility_id) since we omitted the automatically-created validator from Meta.
|
||||
# Validate uniqueness of (location, facility_id) since we omitted the automatically-created validator from Meta.
|
||||
if data.get('facility_id', None):
|
||||
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'facility_id'))
|
||||
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'facility_id'))
|
||||
validator(data, self)
|
||||
|
||||
# Enforce model validation
|
||||
@@ -191,7 +207,7 @@ class RackUnitSerializer(serializers.Serializer):
|
||||
occupied = serializers.BooleanField(read_only=True)
|
||||
|
||||
|
||||
class RackReservationSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class RackReservationSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackreservation-detail')
|
||||
rack = NestedRackSerializer()
|
||||
user = NestedUserSerializer()
|
||||
@@ -199,7 +215,10 @@ class RackReservationSerializer(TaggedObjectSerializer, CustomFieldModelSerializ
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = ['id', 'url', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags', 'custom_fields']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags',
|
||||
'custom_fields',
|
||||
]
|
||||
|
||||
|
||||
class RackElevationDetailFilterSerializer(serializers.Serializer):
|
||||
@@ -242,7 +261,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer):
|
||||
# Device types
|
||||
#
|
||||
|
||||
class ManufacturerSerializer(ValidatedModelSerializer):
|
||||
class ManufacturerSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
|
||||
devicetype_count = serializers.IntegerField(read_only=True)
|
||||
inventoryitem_count = serializers.IntegerField(read_only=True)
|
||||
@@ -251,11 +270,12 @@ class ManufacturerSerializer(ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = [
|
||||
'id', 'url', 'name', 'slug', 'description', 'devicetype_count', 'inventoryitem_count', 'platform_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated',
|
||||
'devicetype_count', 'inventoryitem_count', 'platform_count',
|
||||
]
|
||||
|
||||
|
||||
class DeviceTypeSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class DeviceTypeSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
|
||||
manufacturer = NestedManufacturerSerializer()
|
||||
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False)
|
||||
@@ -264,9 +284,9 @@ class DeviceTypeSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'id', 'url', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height', 'is_full_depth',
|
||||
'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'device_count',
|
||||
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height',
|
||||
'is_full_depth', 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'device_count',
|
||||
]
|
||||
|
||||
|
||||
@@ -281,7 +301,9 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -295,7 +317,9 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -309,7 +333,10 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||
'description', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -331,7 +358,10 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -341,7 +371,10 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'mgmt_only', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -351,7 +384,10 @@ class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'positions', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'positions', 'description', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -362,7 +398,10 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position',
|
||||
'description', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
@@ -371,14 +410,14 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
fields = ['id', 'url', 'device_type', 'name', 'label', 'description']
|
||||
fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
|
||||
class DeviceRoleSerializer(ValidatedModelSerializer):
|
||||
class DeviceRoleSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
|
||||
device_count = serializers.IntegerField(read_only=True)
|
||||
virtualmachine_count = serializers.IntegerField(read_only=True)
|
||||
@@ -386,11 +425,12 @@ class DeviceRoleSerializer(ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = [
|
||||
'id', 'url', 'name', 'slug', 'color', 'vm_role', 'description', 'device_count', 'virtualmachine_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'color', 'vm_role', 'description', 'custom_fields', 'created',
|
||||
'last_updated', 'device_count', 'virtualmachine_count',
|
||||
]
|
||||
|
||||
|
||||
class PlatformSerializer(ValidatedModelSerializer):
|
||||
class PlatformSerializer(OrganizationalModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
|
||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
||||
device_count = serializers.IntegerField(read_only=True)
|
||||
@@ -399,18 +439,19 @@ class PlatformSerializer(ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = [
|
||||
'id', 'url', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description', 'device_count',
|
||||
'virtualmachine_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description',
|
||||
'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
|
||||
]
|
||||
|
||||
|
||||
class DeviceSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class DeviceSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
device_role = NestedDeviceRoleSerializer()
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||
site = NestedSiteSerializer()
|
||||
location = NestedLocationSerializer(required=False, allow_null=True, default=None)
|
||||
rack = NestedRackSerializer(required=False, allow_null=True)
|
||||
face = ChoiceField(choices=DeviceFaceChoices, allow_blank=True, required=False)
|
||||
status = ChoiceField(choices=DeviceStatusChoices, required=False)
|
||||
@@ -424,10 +465,10 @@ class DeviceSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = [
|
||||
'id', 'url', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',
|
||||
'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
|
||||
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
|
||||
'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
|
||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
validators = []
|
||||
|
||||
@@ -460,10 +501,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
|
||||
class Meta(DeviceSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'url', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',
|
||||
'asset_tag', 'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
|
||||
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data',
|
||||
'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
|
||||
'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
|
||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||
@@ -475,7 +516,11 @@ class DeviceNAPALMSerializer(serializers.Serializer):
|
||||
method = serializers.DictField()
|
||||
|
||||
|
||||
class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
#
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsoleServerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(
|
||||
@@ -483,17 +528,23 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial
|
||||
allow_blank=True,
|
||||
required=False
|
||||
)
|
||||
speed = ChoiceField(
|
||||
choices=ConsolePortSpeedChoices,
|
||||
allow_blank=True,
|
||||
required=False
|
||||
)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
|
||||
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
class ConsolePortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(
|
||||
@@ -501,17 +552,23 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer,
|
||||
allow_blank=True,
|
||||
required=False
|
||||
)
|
||||
speed = ChoiceField(
|
||||
choices=ConsolePortSpeedChoices,
|
||||
allow_blank=True,
|
||||
required=False
|
||||
)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
|
||||
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
class PowerOutletSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(
|
||||
@@ -534,13 +591,13 @@ class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer,
|
||||
class Meta:
|
||||
model = PowerOutlet
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable',
|
||||
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
class PowerPortSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(
|
||||
@@ -553,16 +610,17 @@ class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable',
|
||||
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(choices=InterfaceTypeChoices)
|
||||
parent = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
|
||||
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||
@@ -578,34 +636,28 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only',
|
||||
'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'cable', 'cable_peer', 'cable_peer_type',
|
||||
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
|
||||
'count_ipaddresses',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address',
|
||||
'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable',
|
||||
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
|
||||
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
|
||||
'_occupied',
|
||||
]
|
||||
|
||||
# TODO: This validation should be handled by Interface.clean()
|
||||
def validate(self, data):
|
||||
|
||||
# All associated VLANs be global or assigned to the parent device's site.
|
||||
# Validate many-to-many VLAN assignments
|
||||
device = self.instance.device if self.instance else data.get('device')
|
||||
untagged_vlan = data.get('untagged_vlan')
|
||||
if untagged_vlan and untagged_vlan.site not in [device.site, None]:
|
||||
raise serializers.ValidationError({
|
||||
'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
|
||||
"global.".format(untagged_vlan)
|
||||
})
|
||||
for vlan in data.get('tagged_vlans', []):
|
||||
if vlan.site not in [device.site, None]:
|
||||
raise serializers.ValidationError({
|
||||
'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
|
||||
"be global.".format(vlan)
|
||||
'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, or "
|
||||
f"it must be global."
|
||||
})
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
|
||||
class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer):
|
||||
class RearPortSerializer(PrimaryModelSerializer, CableTerminationSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
@@ -614,8 +666,8 @@ class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Val
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer',
|
||||
'cable_peer_type', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected',
|
||||
'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
@@ -627,10 +679,10 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = ['id', 'url', 'name', 'label']
|
||||
fields = ['id', 'url', 'display', 'name', 'label']
|
||||
|
||||
|
||||
class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer):
|
||||
class FrontPortSerializer(PrimaryModelSerializer, CableTerminationSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(choices=PortTypeChoices)
|
||||
@@ -640,26 +692,30 @@ class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Va
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
fields = [
|
||||
'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable',
|
||||
'cable_peer', 'cable_peer_type', 'tags',
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description',
|
||||
'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
|
||||
class DeviceBaySerializer(TaggedObjectSerializer, ValidatedModelSerializer):
|
||||
class DeviceBaySerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
installed_device = NestedDeviceSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ['id', 'url', 'device', 'name', 'label', 'description', 'installed_device', 'tags']
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'name', 'label', 'description', 'installed_device', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Inventory items
|
||||
#
|
||||
|
||||
class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
|
||||
class InventoryItemSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
|
||||
device = NestedDeviceSerializer()
|
||||
# Provide a default value to satisfy UniqueTogetherValidator
|
||||
@@ -670,8 +726,8 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'id', 'url', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
'discovered', 'description', 'tags', '_depth',
|
||||
'id', 'url', 'display', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial',
|
||||
'asset_tag', 'discovered', 'description', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
|
||||
]
|
||||
|
||||
|
||||
@@ -679,7 +735,7 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class CableSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
|
||||
termination_a_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||
@@ -695,7 +751,7 @@ class CableSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'url', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
|
||||
'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
|
||||
'termination_b_id', 'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||
'custom_fields',
|
||||
]
|
||||
@@ -773,11 +829,10 @@ class CablePathSerializer(serializers.ModelSerializer):
|
||||
@swagger_serializer_method(serializer_or_field=serializers.ListField)
|
||||
def get_path(self, obj):
|
||||
ret = []
|
||||
for node in obj.path:
|
||||
ct_id, object_id = decompile_path_node(node)
|
||||
ct = ContentType.objects.get_for_id(ct_id)
|
||||
# TODO: Return the object URL
|
||||
ret.append(f'{ct.app_label}.{ct.model}:{object_id}')
|
||||
for node in obj.get_path():
|
||||
serializer = get_serializer_for_model(node, prefix='Nested')
|
||||
context = {'request': self.context['request']}
|
||||
ret.append(serializer(node, context=context).data)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -787,7 +842,7 @@ class CablePathSerializer(serializers.ModelSerializer):
|
||||
|
||||
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
||||
interface_a = serializers.SerializerMethodField()
|
||||
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
|
||||
interface_b = NestedInterfaceSerializer(source='_path.destination')
|
||||
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -810,24 +865,24 @@ class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class VirtualChassisSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||
master = NestedDeviceSerializer(required=False)
|
||||
member_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ['id', 'url', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
|
||||
fields = ['id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
|
||||
|
||||
|
||||
#
|
||||
# Power panels
|
||||
#
|
||||
|
||||
class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
class PowerPanelSerializer(PrimaryModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
|
||||
site = NestedSiteSerializer()
|
||||
rack_group = NestedRackGroupSerializer(
|
||||
location = NestedLocationSerializer(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
default=None
|
||||
@@ -836,15 +891,10 @@ class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = ['id', 'url', 'site', 'rack_group', 'name', 'tags', 'custom_fields', 'powerfeed_count']
|
||||
fields = ['id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count']
|
||||
|
||||
|
||||
class PowerFeedSerializer(
|
||||
TaggedObjectSerializer,
|
||||
CableTerminationSerializer,
|
||||
ConnectedEndpointSerializer,
|
||||
CustomFieldModelSerializer
|
||||
):
|
||||
class PowerFeedSerializer(PrimaryModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail')
|
||||
power_panel = NestedPowerPanelSerializer()
|
||||
rack = NestedRackSerializer(
|
||||
@@ -873,8 +923,8 @@ class PowerFeedSerializer(
|
||||
class Meta:
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'id', 'url', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
|
||||
'max_utilization', 'comments', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint',
|
||||
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
|
||||
'amperage', 'max_utilization', 'comments', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type',
|
||||
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
||||
@@ -7,10 +7,11 @@ router.APIRootView = views.DCIMRootView
|
||||
|
||||
# Sites
|
||||
router.register('regions', views.RegionViewSet)
|
||||
router.register('site-groups', views.SiteGroupViewSet)
|
||||
router.register('sites', views.SiteViewSet)
|
||||
|
||||
# Racks
|
||||
router.register('rack-groups', views.RackGroupViewSet)
|
||||
router.register('locations', views.LocationViewSet)
|
||||
router.register('rack-roles', views.RackRoleViewSet)
|
||||
router.register('racks', views.RackViewSet)
|
||||
router.register('rack-reservations', views.RackReservationViewSet)
|
||||
|
||||
@@ -2,7 +2,8 @@ import socket
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Count, F
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import F
|
||||
from django.http import HttpResponseForbidden, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_yasg import openapi
|
||||
@@ -16,13 +17,7 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
|
||||
|
||||
from circuits.models import Circuit
|
||||
from dcim import filters
|
||||
from dcim.models import (
|
||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||
VirtualChassis,
|
||||
)
|
||||
from dcim.models import *
|
||||
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
|
||||
from ipam.models import Prefix, VLAN
|
||||
from netbox.api.views import ModelViewSet
|
||||
@@ -30,7 +25,7 @@ from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||
from netbox.api.exceptions import ServiceUnavailable
|
||||
from netbox.api.metadata import ContentTypeMetadata
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.utils import get_subquery
|
||||
from utilities.utils import count_related
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import serializers
|
||||
from .exceptions import MissingFilterException
|
||||
@@ -99,7 +94,7 @@ class PassThroughPortMixin(object):
|
||||
# Regions
|
||||
#
|
||||
|
||||
class RegionViewSet(ModelViewSet):
|
||||
class RegionViewSet(CustomFieldModelViewSet):
|
||||
queryset = Region.objects.add_related_count(
|
||||
Region.objects.all(),
|
||||
Site,
|
||||
@@ -111,6 +106,22 @@ class RegionViewSet(ModelViewSet):
|
||||
filterset_class = filters.RegionFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Site groups
|
||||
#
|
||||
|
||||
class SiteGroupViewSet(CustomFieldModelViewSet):
|
||||
queryset = SiteGroup.objects.add_related_count(
|
||||
SiteGroup.objects.all(),
|
||||
Site,
|
||||
'group',
|
||||
'site_count',
|
||||
cumulative=True
|
||||
)
|
||||
serializer_class = serializers.SiteGroupSerializer
|
||||
filterset_class = filters.SiteGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Sites
|
||||
#
|
||||
@@ -119,41 +130,47 @@ class SiteViewSet(CustomFieldModelViewSet):
|
||||
queryset = Site.objects.prefetch_related(
|
||||
'region', 'tenant', 'tags'
|
||||
).annotate(
|
||||
device_count=get_subquery(Device, 'site'),
|
||||
rack_count=get_subquery(Rack, 'site'),
|
||||
prefix_count=get_subquery(Prefix, 'site'),
|
||||
vlan_count=get_subquery(VLAN, 'site'),
|
||||
circuit_count=get_subquery(Circuit, 'terminations__site'),
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
||||
).order_by(*Site._meta.ordering)
|
||||
device_count=count_related(Device, 'site'),
|
||||
rack_count=count_related(Rack, 'site'),
|
||||
prefix_count=count_related(Prefix, 'site'),
|
||||
vlan_count=count_related(VLAN, 'site'),
|
||||
circuit_count=count_related(Circuit, 'terminations__site'),
|
||||
virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
|
||||
)
|
||||
serializer_class = serializers.SiteSerializer
|
||||
filterset_class = filters.SiteFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Rack groups
|
||||
# Locations
|
||||
#
|
||||
|
||||
class RackGroupViewSet(ModelViewSet):
|
||||
queryset = RackGroup.objects.add_related_count(
|
||||
RackGroup.objects.all(),
|
||||
class LocationViewSet(CustomFieldModelViewSet):
|
||||
queryset = Location.objects.add_related_count(
|
||||
Location.objects.add_related_count(
|
||||
Location.objects.all(),
|
||||
Device,
|
||||
'location',
|
||||
'device_count',
|
||||
cumulative=True
|
||||
),
|
||||
Rack,
|
||||
'group',
|
||||
'location',
|
||||
'rack_count',
|
||||
cumulative=True
|
||||
).prefetch_related('site')
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
filterset_class = filters.RackGroupFilterSet
|
||||
serializer_class = serializers.LocationSerializer
|
||||
filterset_class = filters.LocationFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Rack roles
|
||||
#
|
||||
|
||||
class RackRoleViewSet(ModelViewSet):
|
||||
class RackRoleViewSet(CustomFieldModelViewSet):
|
||||
queryset = RackRole.objects.annotate(
|
||||
rack_count=Count('racks')
|
||||
).order_by(*RackRole._meta.ordering)
|
||||
rack_count=count_related(Rack, 'role')
|
||||
)
|
||||
serializer_class = serializers.RackRoleSerializer
|
||||
filterset_class = filters.RackRoleFilterSet
|
||||
|
||||
@@ -164,11 +181,11 @@ class RackRoleViewSet(ModelViewSet):
|
||||
|
||||
class RackViewSet(CustomFieldModelViewSet):
|
||||
queryset = Rack.objects.prefetch_related(
|
||||
'site', 'group__site', 'role', 'tenant', 'tags'
|
||||
'site', 'location', 'role', 'tenant', 'tags'
|
||||
).annotate(
|
||||
device_count=get_subquery(Device, 'rack'),
|
||||
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
||||
).order_by(*Rack._meta.ordering)
|
||||
device_count=count_related(Device, 'rack'),
|
||||
powerfeed_count=count_related(PowerFeed, 'rack')
|
||||
)
|
||||
serializer_class = serializers.RackSerializer
|
||||
filterset_class = filters.RackFilterSet
|
||||
|
||||
@@ -238,12 +255,12 @@ class RackReservationViewSet(ModelViewSet):
|
||||
# Manufacturers
|
||||
#
|
||||
|
||||
class ManufacturerViewSet(ModelViewSet):
|
||||
class ManufacturerViewSet(CustomFieldModelViewSet):
|
||||
queryset = Manufacturer.objects.annotate(
|
||||
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
|
||||
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
|
||||
platform_count=get_subquery(Platform, 'manufacturer')
|
||||
).order_by(*Manufacturer._meta.ordering)
|
||||
devicetype_count=count_related(DeviceType, 'manufacturer'),
|
||||
inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
|
||||
platform_count=count_related(Platform, 'manufacturer')
|
||||
)
|
||||
serializer_class = serializers.ManufacturerSerializer
|
||||
filterset_class = filters.ManufacturerFilterSet
|
||||
|
||||
@@ -254,10 +271,11 @@ class ManufacturerViewSet(ModelViewSet):
|
||||
|
||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
||||
device_count=Count('instances')
|
||||
).order_by(*DeviceType._meta.ordering)
|
||||
device_count=count_related(Device, 'device_type')
|
||||
)
|
||||
serializer_class = serializers.DeviceTypeSerializer
|
||||
filterset_class = filters.DeviceTypeFilterSet
|
||||
brief_prefetch_fields = ['manufacturer']
|
||||
|
||||
|
||||
#
|
||||
@@ -316,11 +334,11 @@ class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
# Device roles
|
||||
#
|
||||
|
||||
class DeviceRoleViewSet(ModelViewSet):
|
||||
class DeviceRoleViewSet(CustomFieldModelViewSet):
|
||||
queryset = DeviceRole.objects.annotate(
|
||||
device_count=get_subquery(Device, 'device_role'),
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
||||
).order_by(*DeviceRole._meta.ordering)
|
||||
device_count=count_related(Device, 'device_role'),
|
||||
virtualmachine_count=count_related(VirtualMachine, 'role')
|
||||
)
|
||||
serializer_class = serializers.DeviceRoleSerializer
|
||||
filterset_class = filters.DeviceRoleFilterSet
|
||||
|
||||
@@ -329,11 +347,11 @@ class DeviceRoleViewSet(ModelViewSet):
|
||||
# Platforms
|
||||
#
|
||||
|
||||
class PlatformViewSet(ModelViewSet):
|
||||
class PlatformViewSet(CustomFieldModelViewSet):
|
||||
queryset = Platform.objects.annotate(
|
||||
device_count=get_subquery(Device, 'platform'),
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
||||
).order_by(*Platform._meta.ordering)
|
||||
device_count=count_related(Device, 'platform'),
|
||||
virtualmachine_count=count_related(VirtualMachine, 'platform')
|
||||
)
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
filterset_class = filters.PlatformFilterSet
|
||||
|
||||
@@ -342,9 +360,9 @@ class PlatformViewSet(ModelViewSet):
|
||||
# Devices
|
||||
#
|
||||
|
||||
class DeviceViewSet(CustomFieldModelViewSet, ConfigContextQuerySetMixin):
|
||||
class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
|
||||
queryset = Device.objects.prefetch_related(
|
||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', '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', 'tags',
|
||||
)
|
||||
filterset_class = filters.DeviceFilterSet
|
||||
@@ -493,6 +511,7 @@ class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filterset_class = filters.ConsolePortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
@@ -501,50 +520,58 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
)
|
||||
serializer_class = serializers.ConsoleServerPortSerializer
|
||||
filterset_class = filters.ConsoleServerPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filterset_class = filters.PowerPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||
serializer_class = serializers.PowerOutletSerializer
|
||||
filterset_class = filters.PowerOutletFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
|
||||
queryset = Interface.objects.prefetch_related(
|
||||
'device', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
|
||||
'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
|
||||
)
|
||||
serializer_class = serializers.InterfaceSerializer
|
||||
filterset_class = filters.InterfaceFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
||||
serializer_class = serializers.FrontPortSerializer
|
||||
filterset_class = filters.FrontPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
||||
serializer_class = serializers.RearPortSerializer
|
||||
filterset_class = filters.RearPortFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class DeviceBayViewSet(ModelViewSet):
|
||||
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
||||
serializer_class = serializers.DeviceBaySerializer
|
||||
filterset_class = filters.DeviceBayFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
class InventoryItemViewSet(ModelViewSet):
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
||||
serializer_class = serializers.InventoryItemSerializer
|
||||
filterset_class = filters.InventoryItemFilterSet
|
||||
brief_prefetch_fields = ['device']
|
||||
|
||||
|
||||
#
|
||||
@@ -570,6 +597,8 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
queryset = Interface.objects.prefetch_related('device', '_path').filter(
|
||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||
_path__destination_type__app_label='dcim',
|
||||
_path__destination_type__model='interface',
|
||||
_path__destination_id__isnull=False,
|
||||
pk__lt=F('_path__destination_id')
|
||||
)
|
||||
@@ -596,10 +625,11 @@ class CableViewSet(ModelViewSet):
|
||||
|
||||
class VirtualChassisViewSet(ModelViewSet):
|
||||
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
||||
member_count=Count('members', distinct=True)
|
||||
).order_by(*VirtualChassis._meta.ordering)
|
||||
member_count=count_related(Device, 'virtual_chassis')
|
||||
)
|
||||
serializer_class = serializers.VirtualChassisSerializer
|
||||
filterset_class = filters.VirtualChassisFilterSet
|
||||
brief_prefetch_fields = ['master']
|
||||
|
||||
|
||||
#
|
||||
@@ -608,10 +638,10 @@ class VirtualChassisViewSet(ModelViewSet):
|
||||
|
||||
class PowerPanelViewSet(ModelViewSet):
|
||||
queryset = PowerPanel.objects.prefetch_related(
|
||||
'site', 'rack_group'
|
||||
'site', 'location'
|
||||
).annotate(
|
||||
powerfeed_count=Count('powerfeeds')
|
||||
).order_by(*PowerPanel._meta.ordering)
|
||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||
)
|
||||
serializer_class = serializers.PowerPanelSerializer
|
||||
filterset_class = filters.PowerPanelFilterSet
|
||||
|
||||
|
||||
@@ -217,6 +217,29 @@ class ConsolePortTypeChoices(ChoiceSet):
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortSpeedChoices(ChoiceSet):
|
||||
|
||||
SPEED_1200 = 1200
|
||||
SPEED_2400 = 2400
|
||||
SPEED_4800 = 4800
|
||||
SPEED_9600 = 9600
|
||||
SPEED_19200 = 19200
|
||||
SPEED_38400 = 38400
|
||||
SPEED_57600 = 57600
|
||||
SPEED_115200 = 115200
|
||||
|
||||
CHOICES = (
|
||||
(SPEED_1200, '1200 bps'),
|
||||
(SPEED_2400, '2400 bps'),
|
||||
(SPEED_4800, '4800 bps'),
|
||||
(SPEED_9600, '9600 bps'),
|
||||
(SPEED_19200, '19.2 kbps'),
|
||||
(SPEED_38400, '38.4 kbps'),
|
||||
(SPEED_57600, '57.6 kbps'),
|
||||
(SPEED_115200, '115.2 kbps'),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# PowerPorts
|
||||
#
|
||||
@@ -304,6 +327,20 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
TYPE_ITA_M = 'ita-m'
|
||||
TYPE_ITA_N = 'ita-n'
|
||||
TYPE_ITA_O = 'ita-o'
|
||||
# USB
|
||||
TYPE_USB_A = 'usb-a'
|
||||
TYPE_USB_B = 'usb-b'
|
||||
TYPE_USB_C = 'usb-c'
|
||||
TYPE_USB_MINI_A = 'usb-mini-a'
|
||||
TYPE_USB_MINI_B = 'usb-mini-b'
|
||||
TYPE_USB_MICRO_A = 'usb-micro-a'
|
||||
TYPE_USB_MICRO_B = 'usb-micro-b'
|
||||
TYPE_USB_3_B = 'usb-3-b'
|
||||
TYPE_USB_3_MICROB = 'usb-3-micro-b'
|
||||
# Direct current (DC)
|
||||
TYPE_DC = 'dc-terminal'
|
||||
# Proprietary
|
||||
TYPE_SAF_D_GRID = 'saf-d-grid'
|
||||
|
||||
CHOICES = (
|
||||
('IEC 60320', (
|
||||
@@ -393,6 +430,23 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
(TYPE_ITA_N, 'ITA Type N'),
|
||||
(TYPE_ITA_O, 'ITA Type O'),
|
||||
)),
|
||||
('USB', (
|
||||
(TYPE_USB_A, 'USB Type A'),
|
||||
(TYPE_USB_B, 'USB Type B'),
|
||||
(TYPE_USB_C, 'USB Type C'),
|
||||
(TYPE_USB_MINI_A, 'USB Mini A'),
|
||||
(TYPE_USB_MINI_B, 'USB Mini B'),
|
||||
(TYPE_USB_MICRO_A, 'USB Micro A'),
|
||||
(TYPE_USB_MICRO_B, 'USB Micro B'),
|
||||
(TYPE_USB_3_B, 'USB 3.0 Type B'),
|
||||
(TYPE_USB_3_MICROB, 'USB 3.0 Micro B'),
|
||||
)),
|
||||
('DC', (
|
||||
(TYPE_DC, 'DC Terminal'),
|
||||
)),
|
||||
('Proprietary', (
|
||||
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
||||
)),
|
||||
)
|
||||
|
||||
|
||||
@@ -482,8 +536,15 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
TYPE_ITA_M = 'ita-m'
|
||||
TYPE_ITA_N = 'ita-n'
|
||||
TYPE_ITA_O = 'ita-o'
|
||||
# USB
|
||||
TYPE_USB_A = 'usb-a'
|
||||
TYPE_USB_MICROB = 'usb-micro-b'
|
||||
TYPE_USB_C = 'usb-c'
|
||||
# Direct current (DC)
|
||||
TYPE_DC = 'dc-terminal'
|
||||
# Proprietary
|
||||
TYPE_HDOT_CX = 'hdot-cx'
|
||||
TYPE_SAF_D_GRID = 'saf-d-grid'
|
||||
|
||||
CHOICES = (
|
||||
('IEC 60320', (
|
||||
@@ -572,8 +633,17 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
(TYPE_ITA_N, 'ITA Type N'),
|
||||
(TYPE_ITA_O, 'ITA Type O'),
|
||||
)),
|
||||
('USB', (
|
||||
(TYPE_USB_A, 'USB Type A'),
|
||||
(TYPE_USB_MICROB, 'USB Micro B'),
|
||||
(TYPE_USB_C, 'USB Type C'),
|
||||
)),
|
||||
('DC', (
|
||||
(TYPE_DC, 'DC Terminal'),
|
||||
)),
|
||||
('Proprietary', (
|
||||
(TYPE_HDOT_CX, 'HDOT Cx'),
|
||||
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -615,6 +685,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
TYPE_10GE_XENPAK = '10gbase-x-xenpak'
|
||||
TYPE_10GE_X2 = '10gbase-x-x2'
|
||||
TYPE_25GE_SFP28 = '25gbase-x-sfp28'
|
||||
TYPE_50GE_SFP56 = '50gbase-x-sfp56'
|
||||
TYPE_40GE_QSFP_PLUS = '40gbase-x-qsfpp'
|
||||
TYPE_50GE_QSFP28 = '50gbase-x-sfp28'
|
||||
TYPE_100GE_CFP = '100gbase-x-cfp'
|
||||
@@ -656,6 +727,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
TYPE_8GFC_SFP_PLUS = '8gfc-sfpp'
|
||||
TYPE_16GFC_SFP_PLUS = '16gfc-sfpp'
|
||||
TYPE_32GFC_SFP28 = '32gfc-sfp28'
|
||||
TYPE_64GFC_QSFP_PLUS = '64gfc-qsfpp'
|
||||
TYPE_128GFC_QSFP28 = '128gfc-sfp28'
|
||||
|
||||
# InfiniBand
|
||||
@@ -718,6 +790,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
(TYPE_10GE_XENPAK, 'XENPAK (10GE)'),
|
||||
(TYPE_10GE_X2, 'X2 (10GE)'),
|
||||
(TYPE_25GE_SFP28, 'SFP28 (25GE)'),
|
||||
(TYPE_50GE_SFP56, 'SFP56 (50GE)'),
|
||||
(TYPE_40GE_QSFP_PLUS, 'QSFP+ (40GE)'),
|
||||
(TYPE_50GE_QSFP28, 'QSFP28 (50GE)'),
|
||||
(TYPE_100GE_CFP, 'CFP (100GE)'),
|
||||
@@ -771,6 +844,7 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
(TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'),
|
||||
(TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'),
|
||||
(TYPE_32GFC_SFP28, 'SFP28 (32GFC)'),
|
||||
(TYPE_64GFC_QSFP_PLUS, 'QSFP+ (64GFC)'),
|
||||
(TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'),
|
||||
)
|
||||
),
|
||||
@@ -843,8 +917,13 @@ class PortTypeChoices(ChoiceSet):
|
||||
TYPE_8P6C = '8p6c'
|
||||
TYPE_8P4C = '8p4c'
|
||||
TYPE_8P2C = '8p2c'
|
||||
TYPE_GG45 = 'gg45'
|
||||
TYPE_TERA4P = 'tera-4p'
|
||||
TYPE_TERA2P = 'tera-2p'
|
||||
TYPE_TERA1P = 'tera-1p'
|
||||
TYPE_110_PUNCH = '110-punch'
|
||||
TYPE_BNC = 'bnc'
|
||||
TYPE_F = 'f'
|
||||
TYPE_MRJ21 = 'mrj21'
|
||||
TYPE_ST = 'st'
|
||||
TYPE_SC = 'sc'
|
||||
@@ -857,6 +936,8 @@ class PortTypeChoices(ChoiceSet):
|
||||
TYPE_LSH = 'lsh'
|
||||
TYPE_LSH_APC = 'lsh-apc'
|
||||
TYPE_SPLICE = 'splice'
|
||||
TYPE_CS = 'cs'
|
||||
TYPE_SN = 'sn'
|
||||
|
||||
CHOICES = (
|
||||
(
|
||||
@@ -866,8 +947,13 @@ class PortTypeChoices(ChoiceSet):
|
||||
(TYPE_8P6C, '8P6C'),
|
||||
(TYPE_8P4C, '8P4C'),
|
||||
(TYPE_8P2C, '8P2C'),
|
||||
(TYPE_GG45, 'GG45'),
|
||||
(TYPE_TERA4P, 'TERA 4P'),
|
||||
(TYPE_TERA2P, 'TERA 2P'),
|
||||
(TYPE_TERA1P, 'TERA 1P'),
|
||||
(TYPE_110_PUNCH, '110 Punch'),
|
||||
(TYPE_BNC, 'BNC'),
|
||||
(TYPE_F, 'F Connector'),
|
||||
(TYPE_MRJ21, 'MRJ21'),
|
||||
),
|
||||
),
|
||||
@@ -884,6 +970,8 @@ class PortTypeChoices(ChoiceSet):
|
||||
(TYPE_SC, 'SC'),
|
||||
(TYPE_SC_APC, 'SC/APC'),
|
||||
(TYPE_ST, 'ST'),
|
||||
(TYPE_CS, 'CS'),
|
||||
(TYPE_SN, 'SN'),
|
||||
(TYPE_SPLICE, 'Splice'),
|
||||
)
|
||||
)
|
||||
@@ -902,6 +990,8 @@ class CableTypeChoices(ChoiceSet):
|
||||
TYPE_CAT6 = 'cat6'
|
||||
TYPE_CAT6A = 'cat6a'
|
||||
TYPE_CAT7 = 'cat7'
|
||||
TYPE_CAT7A = 'cat7a'
|
||||
TYPE_CAT8 = 'cat8'
|
||||
TYPE_DAC_ACTIVE = 'dac-active'
|
||||
TYPE_DAC_PASSIVE = 'dac-passive'
|
||||
TYPE_MRJ21_TRUNK = 'mrj21-trunk'
|
||||
@@ -926,6 +1016,8 @@ class CableTypeChoices(ChoiceSet):
|
||||
(TYPE_CAT6, 'CAT6'),
|
||||
(TYPE_CAT6A, 'CAT6a'),
|
||||
(TYPE_CAT7, 'CAT7'),
|
||||
(TYPE_CAT7A, 'CAT7a'),
|
||||
(TYPE_CAT8, 'CAT8'),
|
||||
(TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
|
||||
(TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
|
||||
(TYPE_MRJ21_TRUNK, 'MRJ21 Trunk'),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user