Compare commits

...

3 Commits

Author SHA1 Message Date
Jason Novinger
c867986be0 Merge ef29acc21e into c6672538ac 2025-12-08 09:06:11 -05:00
github-actions
c6672538ac Update source translation strings
Some checks failed
Lock threads / lock (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
Close stale issues/PRs / stale (push) Has been cancelled
Close incomplete issues / stale (push) Has been cancelled
Update translation strings / makemessages (push) Has been cancelled
2025-12-06 05:02:07 +00:00
Jason Novinger
ef29acc21e Fixes #20759: Group object types by app in permission form
Modified the ObjectPermissionForm to use optgroups for organizing
object types by application. This shortens the display names (e.g.,
"permission" instead of "Authentication and Authorization | permission")
while maintaining clear organization through visual grouping.

Changes:
- Updated get_object_types_choices() to return nested optgroup structure
- Enhanced AvailableOptions and SelectedOptions widgets to handle optgroups
- Modified TypeScript moveOptions to preserve optgroup structure
- Added hover text showing full model names
- Styled optgroups with bold, padded labels
2025-12-05 09:14:35 -06:00
8 changed files with 349 additions and 220 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
import { getElements } from '../util';
/**
* Move selected options from one select element to another.
* Move selected options from one select element to another, preserving optgroup structure.
*
* @param source Select Element
* @param target Select Element
@@ -9,14 +9,42 @@ import { getElements } from '../util';
function moveOption(source: HTMLSelectElement, target: HTMLSelectElement): void {
for (const option of Array.from(source.options)) {
if (option.selected) {
target.appendChild(option.cloneNode(true));
// Check if option is inside an optgroup
const parentOptgroup = option.parentElement as HTMLElement;
if (parentOptgroup.tagName === 'OPTGROUP') {
// Find or create matching optgroup in target
const groupLabel = parentOptgroup.getAttribute('label');
let targetOptgroup = Array.from(target.children).find(
child => child.tagName === 'OPTGROUP' && child.getAttribute('label') === groupLabel,
) as HTMLOptGroupElement;
if (!targetOptgroup) {
// Create new optgroup in target
targetOptgroup = document.createElement('optgroup');
targetOptgroup.setAttribute('label', groupLabel!);
target.appendChild(targetOptgroup);
}
// Move option to target optgroup
targetOptgroup.appendChild(option.cloneNode(true));
} else {
// Option is not in an optgroup, append directly
target.appendChild(option.cloneNode(true));
}
option.remove();
// Clean up empty optgroups in source
if (parentOptgroup.tagName === 'OPTGROUP' && parentOptgroup.children.length === 0) {
parentOptgroup.remove();
}
}
}
}
/**
* Move selected options of a select element up in order.
* Move selected options of a select element up in order, respecting optgroup boundaries.
*
* Adapted from:
* @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html
@@ -27,14 +55,21 @@ function moveOptionUp(element: HTMLSelectElement): void {
for (let i = 1; i < options.length; i++) {
const option = options[i];
if (option.selected) {
element.removeChild(option);
element.insertBefore(option, element.options[i - 1]);
const parent = option.parentElement as HTMLElement;
const previousOption = element.options[i - 1];
const previousParent = previousOption.parentElement as HTMLElement;
// Only move if previous option is in the same parent (optgroup or select)
if (parent === previousParent) {
parent.removeChild(option);
parent.insertBefore(option, previousOption);
}
}
}
}
/**
* Move selected options of a select element down in order.
* Move selected options of a select element down in order, respecting optgroup boundaries.
*
* Adapted from:
* @see https://www.tomred.net/css-html-js/reorder-option-elements-of-an-html-select.html
@@ -43,12 +78,18 @@ function moveOptionUp(element: HTMLSelectElement): void {
function moveOptionDown(element: HTMLSelectElement): void {
const options = Array.from(element.options);
for (let i = options.length - 2; i >= 0; i--) {
let option = options[i];
const option = options[i];
if (option.selected) {
let next = element.options[i + 1];
option = element.removeChild(option);
next = element.replaceChild(option, next);
element.insertBefore(next, option);
const parent = option.parentElement as HTMLElement;
const nextOption = element.options[i + 1];
const nextParent = nextOption.parentElement as HTMLElement;
// Only move if next option is in the same parent (optgroup or select)
if (parent === nextParent) {
const optionClone = parent.removeChild(option);
const nextClone = parent.replaceChild(optionClone, nextOption);
parent.insertBefore(nextClone, optionClone);
}
}
}
}

View File

@@ -32,3 +32,14 @@ form.object-edit {
border: 1px solid $red;
}
}
// Make optgroup labels sticky when scrolling through select elements
select[multiple] {
optgroup {
position: sticky;
top: 0;
background-color: var(--bs-body-bg);
font-weight: bold;
padding: 0.25rem 0.5rem;
}
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-04 05:01+0000\n"
"POT-Creation-Date: 2025-12-06 05:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -216,8 +216,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:355 netbox/dcim/forms/bulk_edit.py:753
#: netbox/dcim/forms/bulk_edit.py:958 netbox/dcim/forms/bulk_import.py:135
#: netbox/dcim/forms/bulk_import.py:237 netbox/dcim/forms/bulk_import.py:338
#: netbox/dcim/forms/bulk_import.py:614 netbox/dcim/forms/bulk_import.py:1603
#: netbox/dcim/forms/bulk_import.py:1631 netbox/dcim/forms/filtersets.py:89
#: netbox/dcim/forms/bulk_import.py:630 netbox/dcim/forms/bulk_import.py:1619
#: netbox/dcim/forms/bulk_import.py:1647 netbox/dcim/forms/filtersets.py:89
#: netbox/dcim/forms/filtersets.py:227 netbox/dcim/forms/filtersets.py:339
#: netbox/dcim/forms/filtersets.py:441 netbox/dcim/forms/filtersets.py:783
#: netbox/dcim/forms/filtersets.py:1002 netbox/dcim/forms/filtersets.py:1075
@@ -660,7 +660,7 @@ msgstr ""
#: netbox/circuits/forms/filtersets.py:321 netbox/dcim/forms/bulk_edit.py:227
#: netbox/dcim/forms/bulk_edit.py:673 netbox/dcim/forms/bulk_edit.py:889
#: netbox/dcim/forms/bulk_edit.py:1262 netbox/dcim/forms/bulk_edit.py:1289
#: netbox/dcim/forms/bulk_edit.py:1823 netbox/dcim/forms/bulk_import.py:1478
#: netbox/dcim/forms/bulk_edit.py:1823 netbox/dcim/forms/bulk_import.py:1494
#: netbox/dcim/forms/filtersets.py:1142 netbox/dcim/forms/filtersets.py:1400
#: netbox/dcim/forms/filtersets.py:1553 netbox/dcim/forms/filtersets.py:1577
#: netbox/dcim/tables/devices.py:767 netbox/dcim/tables/devices.py:823
@@ -694,11 +694,11 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1162 netbox/dcim/forms/bulk_edit.py:1206
#: netbox/dcim/forms/bulk_edit.py:1257 netbox/dcim/forms/bulk_edit.py:1284
#: netbox/dcim/forms/bulk_import.py:195 netbox/dcim/forms/bulk_import.py:274
#: netbox/dcim/forms/bulk_import.py:788 netbox/dcim/forms/bulk_import.py:814
#: netbox/dcim/forms/bulk_import.py:840 netbox/dcim/forms/bulk_import.py:860
#: netbox/dcim/forms/bulk_import.py:946 netbox/dcim/forms/bulk_import.py:1082
#: netbox/dcim/forms/bulk_import.py:1124 netbox/dcim/forms/bulk_import.py:1459
#: netbox/dcim/forms/bulk_import.py:1668 netbox/dcim/forms/filtersets.py:1033
#: netbox/dcim/forms/bulk_import.py:804 netbox/dcim/forms/bulk_import.py:830
#: netbox/dcim/forms/bulk_import.py:856 netbox/dcim/forms/bulk_import.py:876
#: netbox/dcim/forms/bulk_import.py:962 netbox/dcim/forms/bulk_import.py:1098
#: netbox/dcim/forms/bulk_import.py:1140 netbox/dcim/forms/bulk_import.py:1475
#: netbox/dcim/forms/bulk_import.py:1684 netbox/dcim/forms/filtersets.py:1033
#: netbox/dcim/forms/filtersets.py:1132 netbox/dcim/forms/filtersets.py:1253
#: netbox/dcim/forms/filtersets.py:1325 netbox/dcim/forms/filtersets.py:1350
#: netbox/dcim/forms/filtersets.py:1374 netbox/dcim/forms/filtersets.py:1394
@@ -768,9 +768,9 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1797 netbox/dcim/forms/bulk_edit.py:1846
#: netbox/dcim/forms/bulk_import.py:92 netbox/dcim/forms/bulk_import.py:151
#: netbox/dcim/forms/bulk_import.py:255 netbox/dcim/forms/bulk_import.py:363
#: netbox/dcim/forms/bulk_import.py:579 netbox/dcim/forms/bulk_import.py:739
#: netbox/dcim/forms/bulk_import.py:1232 netbox/dcim/forms/bulk_import.py:1453
#: netbox/dcim/forms/bulk_import.py:1663 netbox/dcim/forms/bulk_import.py:1727
#: netbox/dcim/forms/bulk_import.py:595 netbox/dcim/forms/bulk_import.py:755
#: netbox/dcim/forms/bulk_import.py:1248 netbox/dcim/forms/bulk_import.py:1469
#: netbox/dcim/forms/bulk_import.py:1679 netbox/dcim/forms/bulk_import.py:1743
#: netbox/dcim/forms/filtersets.py:180 netbox/dcim/forms/filtersets.py:239
#: netbox/dcim/forms/filtersets.py:356 netbox/dcim/forms/filtersets.py:462
#: netbox/dcim/forms/filtersets.py:829 netbox/dcim/forms/filtersets.py:954
@@ -849,8 +849,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:879 netbox/dcim/forms/bulk_edit.py:1851
#: netbox/dcim/forms/bulk_import.py:111 netbox/dcim/forms/bulk_import.py:156
#: netbox/dcim/forms/bulk_import.py:248 netbox/dcim/forms/bulk_import.py:368
#: netbox/dcim/forms/bulk_import.py:553 netbox/dcim/forms/bulk_import.py:1465
#: netbox/dcim/forms/bulk_import.py:1720 netbox/dcim/forms/filtersets.py:175
#: netbox/dcim/forms/bulk_import.py:569 netbox/dcim/forms/bulk_import.py:1481
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/filtersets.py:175
#: netbox/dcim/forms/filtersets.py:207 netbox/dcim/forms/filtersets.py:320
#: netbox/dcim/forms/filtersets.py:401 netbox/dcim/forms/filtersets.py:422
#: netbox/dcim/forms/filtersets.py:752 netbox/dcim/forms/filtersets.py:946
@@ -963,25 +963,25 @@ msgstr ""
#: netbox/circuits/forms/filtersets.py:316
#: netbox/circuits/forms/filtersets.py:331 netbox/core/forms/filtersets.py:73
#: netbox/core/forms/filtersets.py:141 netbox/dcim/forms/bulk_edit.py:913
#: netbox/dcim/forms/filtersets.py:174 netbox/dcim/forms/filtersets.py:206
#: netbox/dcim/forms/filtersets.py:945 netbox/dcim/forms/filtersets.py:1085
#: netbox/dcim/forms/filtersets.py:1209 netbox/dcim/forms/filtersets.py:1317
#: netbox/dcim/forms/filtersets.py:1341 netbox/dcim/forms/filtersets.py:1366
#: netbox/dcim/forms/filtersets.py:1385 netbox/dcim/forms/filtersets.py:1414
#: netbox/dcim/forms/filtersets.py:1539 netbox/dcim/forms/filtersets.py:1563
#: netbox/dcim/forms/filtersets.py:1587 netbox/dcim/forms/filtersets.py:1605
#: netbox/dcim/forms/filtersets.py:1621 netbox/dcim/forms/filtersets.py:1679
#: netbox/dcim/tables/modules.py:24 netbox/extras/forms/bulk_edit.py:94
#: netbox/extras/forms/filtersets.py:45 netbox/extras/forms/filtersets.py:142
#: netbox/extras/forms/filtersets.py:219 netbox/extras/forms/filtersets.py:236
#: netbox/extras/forms/filtersets.py:266 netbox/extras/forms/filtersets.py:297
#: netbox/extras/forms/filtersets.py:321 netbox/extras/forms/filtersets.py:542
#: netbox/ipam/forms/filtersets.py:101 netbox/ipam/forms/filtersets.py:281
#: netbox/ipam/forms/filtersets.py:330 netbox/ipam/forms/filtersets.py:406
#: netbox/ipam/forms/filtersets.py:492 netbox/ipam/forms/filtersets.py:505
#: netbox/ipam/forms/filtersets.py:530 netbox/ipam/forms/filtersets.py:601
#: netbox/ipam/forms/filtersets.py:619 netbox/netbox/tables/tables.py:288
#: netbox/templates/dcim/moduletype.html:68
#: netbox/dcim/forms/bulk_import.py:476 netbox/dcim/forms/filtersets.py:174
#: netbox/dcim/forms/filtersets.py:206 netbox/dcim/forms/filtersets.py:945
#: netbox/dcim/forms/filtersets.py:1085 netbox/dcim/forms/filtersets.py:1209
#: netbox/dcim/forms/filtersets.py:1317 netbox/dcim/forms/filtersets.py:1341
#: netbox/dcim/forms/filtersets.py:1366 netbox/dcim/forms/filtersets.py:1385
#: netbox/dcim/forms/filtersets.py:1414 netbox/dcim/forms/filtersets.py:1539
#: netbox/dcim/forms/filtersets.py:1563 netbox/dcim/forms/filtersets.py:1587
#: netbox/dcim/forms/filtersets.py:1605 netbox/dcim/forms/filtersets.py:1621
#: netbox/dcim/forms/filtersets.py:1679 netbox/dcim/tables/modules.py:24
#: netbox/extras/forms/bulk_edit.py:94 netbox/extras/forms/filtersets.py:45
#: netbox/extras/forms/filtersets.py:142 netbox/extras/forms/filtersets.py:219
#: netbox/extras/forms/filtersets.py:236 netbox/extras/forms/filtersets.py:266
#: netbox/extras/forms/filtersets.py:297 netbox/extras/forms/filtersets.py:321
#: netbox/extras/forms/filtersets.py:542 netbox/ipam/forms/filtersets.py:101
#: netbox/ipam/forms/filtersets.py:281 netbox/ipam/forms/filtersets.py:330
#: netbox/ipam/forms/filtersets.py:406 netbox/ipam/forms/filtersets.py:492
#: netbox/ipam/forms/filtersets.py:505 netbox/ipam/forms/filtersets.py:530
#: netbox/ipam/forms/filtersets.py:601 netbox/ipam/forms/filtersets.py:619
#: netbox/netbox/tables/tables.py:288 netbox/templates/dcim/moduletype.html:68
#: netbox/virtualization/forms/filtersets.py:46
#: netbox/virtualization/forms/filtersets.py:109
#: netbox/virtualization/forms/filtersets.py:204
@@ -1021,7 +1021,7 @@ msgstr ""
#: netbox/circuits/forms/bulk_edit.py:215
#: netbox/circuits/forms/model_forms.py:171
#: netbox/dcim/forms/bulk_import.py:1419 netbox/dcim/forms/bulk_import.py:1444
#: netbox/dcim/forms/bulk_import.py:1435 netbox/dcim/forms/bulk_import.py:1460
msgid "Termination type"
msgstr ""
@@ -1095,7 +1095,7 @@ msgstr ""
#: netbox/circuits/forms/filtersets.py:382
#: netbox/circuits/forms/model_forms.py:366 netbox/dcim/forms/bulk_edit.py:383
#: netbox/dcim/forms/bulk_edit.py:1351 netbox/dcim/forms/bulk_edit.py:1787
#: netbox/dcim/forms/bulk_import.py:260 netbox/dcim/forms/bulk_import.py:1201
#: netbox/dcim/forms/bulk_import.py:260 netbox/dcim/forms/bulk_import.py:1217
#: netbox/dcim/forms/filtersets.py:364 netbox/dcim/forms/filtersets.py:807
#: netbox/dcim/forms/filtersets.py:1632 netbox/dcim/forms/model_forms.py:264
#: netbox/dcim/forms/model_forms.py:1228 netbox/dcim/forms/model_forms.py:1697
@@ -1156,9 +1156,9 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:102
#: netbox/circuits/forms/bulk_import.py:229 netbox/dcim/forms/bulk_import.py:94
#: netbox/dcim/forms/bulk_import.py:153 netbox/dcim/forms/bulk_import.py:257
#: netbox/dcim/forms/bulk_import.py:365 netbox/dcim/forms/bulk_import.py:581
#: netbox/dcim/forms/bulk_import.py:741 netbox/dcim/forms/bulk_import.py:1234
#: netbox/dcim/forms/bulk_import.py:1665 netbox/ipam/forms/bulk_import.py:197
#: netbox/dcim/forms/bulk_import.py:365 netbox/dcim/forms/bulk_import.py:597
#: netbox/dcim/forms/bulk_import.py:757 netbox/dcim/forms/bulk_import.py:1250
#: netbox/dcim/forms/bulk_import.py:1681 netbox/ipam/forms/bulk_import.py:197
#: netbox/ipam/forms/bulk_import.py:265 netbox/ipam/forms/bulk_import.py:301
#: netbox/ipam/forms/bulk_import.py:512 netbox/ipam/forms/bulk_import.py:525
#: netbox/virtualization/forms/bulk_import.py:62
@@ -1172,9 +1172,9 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:174
#: netbox/circuits/forms/bulk_import.py:236
#: netbox/dcim/forms/bulk_import.py:115 netbox/dcim/forms/bulk_import.py:160
#: netbox/dcim/forms/bulk_import.py:372 netbox/dcim/forms/bulk_import.py:557
#: netbox/dcim/forms/bulk_import.py:1469 netbox/dcim/forms/bulk_import.py:1660
#: netbox/dcim/forms/bulk_import.py:1724 netbox/ipam/forms/bulk_import.py:45
#: netbox/dcim/forms/bulk_import.py:372 netbox/dcim/forms/bulk_import.py:573
#: netbox/dcim/forms/bulk_import.py:1485 netbox/dcim/forms/bulk_import.py:1676
#: netbox/dcim/forms/bulk_import.py:1740 netbox/ipam/forms/bulk_import.py:45
#: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102
#: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260
@@ -1219,7 +1219,7 @@ msgstr ""
#: netbox/circuits/forms/bulk_import.py:259
#: netbox/circuits/forms/model_forms.py:369
#: netbox/circuits/tables/virtual_circuits.py:111
#: netbox/dcim/forms/bulk_import.py:1332 netbox/dcim/forms/model_forms.py:1302
#: netbox/dcim/forms/bulk_import.py:1348 netbox/dcim/forms/model_forms.py:1302
#: netbox/dcim/forms/model_forms.py:1571 netbox/dcim/forms/model_forms.py:1738
#: netbox/dcim/forms/model_forms.py:1773 netbox/dcim/forms/model_forms.py:1903
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1169
@@ -1254,8 +1254,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:477 netbox/dcim/forms/bulk_edit.py:758
#: netbox/dcim/forms/bulk_edit.py:813 netbox/dcim/forms/bulk_edit.py:967
#: netbox/dcim/forms/bulk_import.py:242 netbox/dcim/forms/bulk_import.py:344
#: netbox/dcim/forms/bulk_import.py:620 netbox/dcim/forms/bulk_import.py:1609
#: netbox/dcim/forms/bulk_import.py:1643 netbox/dcim/forms/filtersets.py:97
#: netbox/dcim/forms/bulk_import.py:636 netbox/dcim/forms/bulk_import.py:1625
#: netbox/dcim/forms/bulk_import.py:1659 netbox/dcim/forms/filtersets.py:97
#: netbox/dcim/forms/filtersets.py:319 netbox/dcim/forms/filtersets.py:353
#: netbox/dcim/forms/filtersets.py:398 netbox/dcim/forms/filtersets.py:449
#: netbox/dcim/forms/filtersets.py:749 netbox/dcim/forms/filtersets.py:792
@@ -1956,13 +1956,13 @@ msgstr ""
#: netbox/circuits/tables/virtual_circuits.py:108
#: netbox/dcim/forms/bulk_edit.py:812 netbox/dcim/forms/bulk_edit.py:1370
#: netbox/dcim/forms/bulk_edit.py:1782 netbox/dcim/forms/bulk_edit.py:1841
#: netbox/dcim/forms/bulk_import.py:721 netbox/dcim/forms/bulk_import.py:783
#: netbox/dcim/forms/bulk_import.py:809 netbox/dcim/forms/bulk_import.py:835
#: netbox/dcim/forms/bulk_import.py:855 netbox/dcim/forms/bulk_import.py:911
#: netbox/dcim/forms/bulk_import.py:1071 netbox/dcim/forms/bulk_import.py:1119
#: netbox/dcim/forms/bulk_import.py:1136 netbox/dcim/forms/bulk_import.py:1148
#: netbox/dcim/forms/bulk_import.py:1196 netbox/dcim/forms/bulk_import.py:1318
#: netbox/dcim/forms/bulk_import.py:1714 netbox/dcim/forms/connections.py:29
#: netbox/dcim/forms/bulk_import.py:737 netbox/dcim/forms/bulk_import.py:799
#: netbox/dcim/forms/bulk_import.py:825 netbox/dcim/forms/bulk_import.py:851
#: netbox/dcim/forms/bulk_import.py:871 netbox/dcim/forms/bulk_import.py:927
#: netbox/dcim/forms/bulk_import.py:1087 netbox/dcim/forms/bulk_import.py:1135
#: netbox/dcim/forms/bulk_import.py:1152 netbox/dcim/forms/bulk_import.py:1164
#: netbox/dcim/forms/bulk_import.py:1212 netbox/dcim/forms/bulk_import.py:1334
#: netbox/dcim/forms/bulk_import.py:1730 netbox/dcim/forms/connections.py:29
#: netbox/dcim/forms/filtersets.py:133 netbox/dcim/forms/filtersets.py:951
#: netbox/dcim/forms/filtersets.py:983 netbox/dcim/forms/filtersets.py:1129
#: netbox/dcim/forms/filtersets.py:1320 netbox/dcim/forms/filtersets.py:1345
@@ -3172,9 +3172,9 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:668 netbox/dcim/forms/bulk_edit.py:702
#: netbox/dcim/forms/bulk_edit.py:1497 netbox/dcim/forms/bulk_import.py:64
#: netbox/dcim/forms/bulk_import.py:78 netbox/dcim/forms/bulk_import.py:141
#: netbox/dcim/forms/bulk_import.py:486 netbox/dcim/forms/bulk_import.py:514
#: netbox/dcim/forms/bulk_import.py:640 netbox/dcim/forms/bulk_import.py:916
#: netbox/dcim/forms/bulk_import.py:1213 netbox/dcim/forms/filtersets.py:236
#: netbox/dcim/forms/bulk_import.py:502 netbox/dcim/forms/bulk_import.py:530
#: netbox/dcim/forms/bulk_import.py:656 netbox/dcim/forms/bulk_import.py:932
#: netbox/dcim/forms/bulk_import.py:1229 netbox/dcim/forms/filtersets.py:236
#: netbox/dcim/forms/filtersets.py:714 netbox/dcim/forms/filtersets.py:725
#: netbox/dcim/forms/model_forms.py:80 netbox/dcim/forms/model_forms.py:100
#: netbox/dcim/forms/model_forms.py:180 netbox/dcim/forms/model_forms.py:519
@@ -3326,7 +3326,7 @@ msgid "Virtual interfaces"
msgstr ""
#: netbox/dcim/choices.py:1152 netbox/dcim/forms/bulk_edit.py:1505
#: netbox/dcim/forms/bulk_import.py:923 netbox/dcim/forms/model_forms.py:1112
#: netbox/dcim/forms/bulk_import.py:939 netbox/dcim/forms/model_forms.py:1112
#: netbox/dcim/tables/devices.py:723 netbox/templates/dcim/interface.html:112
#: netbox/templates/virtualization/vminterface.html:43
#: netbox/virtualization/forms/bulk_edit.py:194
@@ -3951,7 +3951,7 @@ msgstr ""
msgid "Is assigned"
msgstr ""
#: netbox/dcim/filtersets.py:1815 netbox/dcim/forms/bulk_import.py:1339
#: netbox/dcim/filtersets.py:1815 netbox/dcim/forms/bulk_import.py:1355
#: netbox/ipam/forms/bulk_import.py:338
msgid "Is primary"
msgstr ""
@@ -3972,7 +3972,7 @@ msgid "Assigned VID"
msgstr ""
#: netbox/dcim/filtersets.py:1893 netbox/dcim/forms/bulk_edit.py:1618
#: netbox/dcim/forms/bulk_import.py:1008 netbox/dcim/forms/filtersets.py:1526
#: netbox/dcim/forms/bulk_import.py:1024 netbox/dcim/forms/filtersets.py:1526
#: netbox/dcim/forms/model_forms.py:1549
#: netbox/dcim/models/device_components.py:810
#: netbox/dcim/tables/devices.py:677 netbox/ipam/filtersets.py:335
@@ -4214,8 +4214,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:732 netbox/dcim/forms/bulk_edit.py:825
#: netbox/dcim/forms/bulk_edit.py:1356 netbox/dcim/forms/bulk_edit.py:1792
#: netbox/dcim/forms/bulk_import.py:189 netbox/dcim/forms/bulk_import.py:405
#: netbox/dcim/forms/bulk_import.py:454 netbox/dcim/forms/bulk_import.py:524
#: netbox/dcim/forms/bulk_import.py:560 netbox/dcim/forms/bulk_import.py:1207
#: netbox/dcim/forms/bulk_import.py:454 netbox/dcim/forms/bulk_import.py:540
#: netbox/dcim/forms/bulk_import.py:576 netbox/dcim/forms/bulk_import.py:1223
#: netbox/dcim/forms/filtersets.py:310 netbox/dcim/forms/filtersets.py:369
#: netbox/dcim/forms/filtersets.py:501 netbox/dcim/forms/filtersets.py:639
#: netbox/dcim/forms/filtersets.py:730 netbox/dcim/forms/filtersets.py:812
@@ -4375,7 +4375,7 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:447 netbox/dcim/forms/bulk_edit.py:564
#: netbox/dcim/forms/bulk_edit.py:632 netbox/dcim/forms/bulk_edit.py:781
#: netbox/dcim/forms/bulk_import.py:296 netbox/dcim/forms/bulk_import.py:459
#: netbox/dcim/forms/bulk_import.py:654 netbox/dcim/forms/filtersets.py:380
#: netbox/dcim/forms/bulk_import.py:670 netbox/dcim/forms/filtersets.py:380
#: netbox/dcim/forms/filtersets.py:518 netbox/dcim/forms/filtersets.py:689
#: netbox/dcim/forms/filtersets.py:834 netbox/templates/dcim/device.html:104
#: netbox/templates/dcim/devicetype.html:65
@@ -4385,8 +4385,8 @@ msgstr ""
#: netbox/dcim/forms/bulk_edit.py:476 netbox/dcim/forms/bulk_edit.py:995
#: netbox/dcim/forms/bulk_import.py:351 netbox/dcim/forms/bulk_import.py:354
#: netbox/dcim/forms/bulk_import.py:627 netbox/dcim/forms/bulk_import.py:1650
#: netbox/dcim/forms/bulk_import.py:1654 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/bulk_import.py:643 netbox/dcim/forms/bulk_import.py:1666
#: netbox/dcim/forms/bulk_import.py:1670 netbox/dcim/forms/filtersets.py:106
#: netbox/dcim/forms/filtersets.py:321 netbox/dcim/forms/filtersets.py:407
#: netbox/dcim/forms/filtersets.py:421 netbox/dcim/forms/filtersets.py:459
#: netbox/dcim/forms/filtersets.py:802 netbox/dcim/forms/filtersets.py:1015
@@ -4484,10 +4484,10 @@ msgid "VM role"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:682 netbox/dcim/forms/bulk_edit.py:712
#: netbox/dcim/forms/bulk_edit.py:796 netbox/dcim/forms/bulk_import.py:496
#: netbox/dcim/forms/bulk_import.py:500 netbox/dcim/forms/bulk_import.py:531
#: netbox/dcim/forms/bulk_import.py:535 netbox/dcim/forms/bulk_import.py:660
#: netbox/dcim/forms/bulk_import.py:664 netbox/dcim/forms/filtersets.py:709
#: netbox/dcim/forms/bulk_edit.py:796 netbox/dcim/forms/bulk_import.py:512
#: netbox/dcim/forms/bulk_import.py:516 netbox/dcim/forms/bulk_import.py:547
#: netbox/dcim/forms/bulk_import.py:551 netbox/dcim/forms/bulk_import.py:676
#: netbox/dcim/forms/bulk_import.py:680 netbox/dcim/forms/filtersets.py:709
#: netbox/dcim/forms/filtersets.py:735 netbox/dcim/forms/filtersets.py:853
#: netbox/dcim/forms/model_forms.py:513 netbox/dcim/forms/model_forms.py:552
#: netbox/dcim/forms/model_forms.py:670
@@ -4499,18 +4499,18 @@ msgid "Config template"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:737 netbox/dcim/forms/bulk_edit.py:1150
#: netbox/dcim/forms/bulk_import.py:566 netbox/dcim/forms/filtersets.py:116
#: netbox/dcim/forms/bulk_import.py:582 netbox/dcim/forms/filtersets.py:116
#: netbox/dcim/forms/model_forms.py:615 netbox/dcim/forms/model_forms.py:991
#: netbox/dcim/forms/model_forms.py:1008 netbox/extras/filtersets.py:684
msgid "Device type"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:748 netbox/dcim/forms/bulk_import.py:547
#: netbox/dcim/forms/bulk_edit.py:748 netbox/dcim/forms/bulk_import.py:563
#: netbox/dcim/forms/filtersets.py:121 netbox/dcim/forms/model_forms.py:623
msgid "Device role"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:771 netbox/dcim/forms/bulk_import.py:572
#: netbox/dcim/forms/bulk_edit.py:771 netbox/dcim/forms/bulk_import.py:588
#: netbox/dcim/forms/filtersets.py:826 netbox/dcim/forms/model_forms.py:564
#: netbox/dcim/forms/model_forms.py:628 netbox/dcim/tables/devices.py:205
#: netbox/extras/filtersets.py:700 netbox/templates/dcim/device.html:192
@@ -4524,7 +4524,7 @@ msgstr ""
msgid "Platform"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:801 netbox/dcim/forms/bulk_import.py:591
#: netbox/dcim/forms/bulk_edit.py:801 netbox/dcim/forms/bulk_import.py:607
#: netbox/dcim/forms/filtersets.py:758 netbox/dcim/forms/filtersets.py:928
#: netbox/dcim/forms/model_forms.py:637 netbox/dcim/tables/devices.py:225
#: netbox/extras/filtersets.py:733 netbox/extras/forms/filtersets.py:390
@@ -4557,7 +4557,7 @@ msgstr ""
msgid "Virtualization"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:830 netbox/dcim/forms/bulk_import.py:733
#: netbox/dcim/forms/bulk_edit.py:830 netbox/dcim/forms/bulk_import.py:749
#: netbox/dcim/forms/model_forms.py:765 netbox/dcim/forms/model_forms.py:1016
msgid "Module type"
msgstr ""
@@ -4590,8 +4590,8 @@ msgstr ""
msgid "Length"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:898 netbox/dcim/forms/bulk_import.py:1472
#: netbox/dcim/forms/bulk_import.py:1475 netbox/dcim/forms/filtersets.py:1150
#: netbox/dcim/forms/bulk_edit.py:898 netbox/dcim/forms/bulk_import.py:1488
#: netbox/dcim/forms/bulk_import.py:1491 netbox/dcim/forms/filtersets.py:1150
msgid "Length unit"
msgstr ""
@@ -4600,17 +4600,17 @@ msgstr ""
msgid "Domain"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:990 netbox/dcim/forms/bulk_import.py:1637
#: netbox/dcim/forms/bulk_edit.py:990 netbox/dcim/forms/bulk_import.py:1653
#: netbox/dcim/forms/filtersets.py:1236 netbox/dcim/forms/model_forms.py:868
msgid "Power panel"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1012 netbox/dcim/forms/bulk_import.py:1673
#: netbox/dcim/forms/bulk_edit.py:1012 netbox/dcim/forms/bulk_import.py:1689
#: netbox/dcim/forms/filtersets.py:1258 netbox/templates/dcim/powerfeed.html:83
msgid "Supply"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1018 netbox/dcim/forms/bulk_import.py:1678
#: netbox/dcim/forms/bulk_edit.py:1018 netbox/dcim/forms/bulk_import.py:1694
#: netbox/dcim/forms/filtersets.py:1263 netbox/templates/dcim/powerfeed.html:95
msgid "Phase"
msgstr ""
@@ -4649,13 +4649,13 @@ msgstr ""
msgid "Allocated power draw (watts)"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1167 netbox/dcim/forms/bulk_import.py:866
#: netbox/dcim/forms/bulk_edit.py:1167 netbox/dcim/forms/bulk_import.py:882
#: netbox/dcim/forms/model_forms.py:1085 netbox/dcim/forms/model_forms.py:1439
#: netbox/dcim/forms/model_forms.py:1754 netbox/dcim/forms/object_import.py:55
msgid "Power port"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1172 netbox/dcim/forms/bulk_import.py:873
#: netbox/dcim/forms/bulk_edit.py:1172 netbox/dcim/forms/bulk_import.py:889
msgid "Feed leg"
msgstr ""
@@ -4664,7 +4664,7 @@ msgid "Management only"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1228 netbox/dcim/forms/bulk_edit.py:1545
#: netbox/dcim/forms/bulk_import.py:959 netbox/dcim/forms/filtersets.py:1482
#: netbox/dcim/forms/bulk_import.py:975 netbox/dcim/forms/filtersets.py:1482
#: netbox/dcim/forms/object_import.py:90
#: netbox/dcim/models/device_component_templates.py:446
#: netbox/dcim/models/device_components.py:782
@@ -4672,7 +4672,7 @@ msgid "PoE mode"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1234 netbox/dcim/forms/bulk_edit.py:1551
#: netbox/dcim/forms/bulk_import.py:965 netbox/dcim/forms/filtersets.py:1487
#: netbox/dcim/forms/bulk_import.py:981 netbox/dcim/forms/filtersets.py:1487
#: netbox/dcim/forms/object_import.py:95
#: netbox/dcim/models/device_component_templates.py:453
#: netbox/dcim/models/device_components.py:789
@@ -4707,8 +4707,8 @@ msgstr ""
msgid "Virtual device contexts"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/bulk_import.py:794
#: netbox/dcim/forms/bulk_import.py:820 netbox/dcim/forms/filtersets.py:1330
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/bulk_import.py:810
#: netbox/dcim/forms/bulk_import.py:836 netbox/dcim/forms/filtersets.py:1330
#: netbox/dcim/forms/filtersets.py:1355 netbox/dcim/forms/filtersets.py:1446
#: netbox/dcim/tables/devices.py:661
#: netbox/templates/circuits/inc/circuit_termination_fields.html:62
@@ -4717,7 +4717,7 @@ msgstr ""
msgid "Speed"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1559 netbox/dcim/forms/bulk_import.py:968
#: netbox/dcim/forms/bulk_edit.py:1559 netbox/dcim/forms/bulk_import.py:984
#: netbox/templates/vpn/ikepolicy.html:25
#: netbox/templates/vpn/ipsecprofile.html:21
#: netbox/templates/vpn/ipsecprofile.html:48
@@ -4731,7 +4731,7 @@ msgstr ""
msgid "Mode"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1567 netbox/dcim/forms/bulk_import.py:974
#: netbox/dcim/forms/bulk_edit.py:1567 netbox/dcim/forms/bulk_import.py:990
#: netbox/dcim/forms/model_forms.py:1515 netbox/ipam/forms/bulk_import.py:174
#: netbox/ipam/forms/filtersets.py:561 netbox/ipam/models/vlans.py:93
#: netbox/virtualization/forms/bulk_edit.py:222
@@ -4740,7 +4740,7 @@ msgstr ""
msgid "VLAN group"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1576 netbox/dcim/forms/bulk_import.py:981
#: netbox/dcim/forms/bulk_edit.py:1576 netbox/dcim/forms/bulk_import.py:997
#: netbox/dcim/forms/model_forms.py:1521 netbox/dcim/tables/devices.py:622
#: netbox/virtualization/forms/bulk_edit.py:230
#: netbox/virtualization/forms/bulk_import.py:189
@@ -4748,7 +4748,7 @@ msgstr ""
msgid "Untagged VLAN"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1585 netbox/dcim/forms/bulk_import.py:988
#: netbox/dcim/forms/bulk_edit.py:1585 netbox/dcim/forms/bulk_import.py:1004
#: netbox/dcim/forms/model_forms.py:1530 netbox/dcim/tables/devices.py:628
#: netbox/virtualization/forms/bulk_edit.py:238
#: netbox/virtualization/forms/bulk_import.py:196
@@ -4764,7 +4764,7 @@ msgstr ""
msgid "Remove tagged VLANs"
msgstr ""
#: netbox/dcim/forms/bulk_edit.py:1608 netbox/dcim/forms/bulk_import.py:1001
#: netbox/dcim/forms/bulk_edit.py:1608 netbox/dcim/forms/bulk_import.py:1017
#: netbox/dcim/forms/model_forms.py:1539
#: netbox/virtualization/forms/bulk_import.py:209
#: netbox/virtualization/forms/model_forms.py:356
@@ -4853,8 +4853,8 @@ msgstr ""
msgid "available options"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:138 netbox/dcim/forms/bulk_import.py:617
#: netbox/dcim/forms/bulk_import.py:1634 netbox/ipam/forms/bulk_import.py:493
#: netbox/dcim/forms/bulk_import.py:138 netbox/dcim/forms/bulk_import.py:633
#: netbox/dcim/forms/bulk_import.py:1650 netbox/ipam/forms/bulk_import.py:493
#: netbox/virtualization/forms/bulk_import.py:69
#: netbox/virtualization/forms/bulk_import.py:100
msgid "Assigned site"
@@ -4901,7 +4901,7 @@ msgid "Rack type model"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:299 netbox/dcim/forms/bulk_import.py:462
#: netbox/dcim/forms/bulk_import.py:657
#: netbox/dcim/forms/bulk_import.py:673
msgid "Airflow direction"
msgstr ""
@@ -4917,7 +4917,7 @@ msgstr ""
msgid "Parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:348 netbox/dcim/forms/bulk_import.py:1647
#: netbox/dcim/forms/bulk_import.py:348 netbox/dcim/forms/bulk_import.py:1663
msgid "Rack's location (if any)"
msgstr ""
@@ -4956,208 +4956,216 @@ msgstr ""
msgid "Unit for module weight"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:490
#: netbox/dcim/forms/bulk_import.py:478
msgid "Attribute values for the assigned profile, passed as a dictionary"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:493
msgid "Profile must be specified if attribute data is provided."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:506
msgid "Parent Device Role"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:492
#: netbox/dcim/forms/bulk_import.py:508
msgid "Device role not found."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:518
#: netbox/dcim/forms/bulk_import.py:534
msgid "Parent platform"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:520
#: netbox/dcim/forms/bulk_import.py:536
msgid "Platform not found."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:528
#: netbox/dcim/forms/bulk_import.py:544
msgid "Limit platform assignments to this manufacturer"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:550 netbox/dcim/forms/bulk_import.py:1717
#: netbox/dcim/forms/bulk_import.py:566 netbox/dcim/forms/bulk_import.py:1733
#: netbox/tenancy/forms/bulk_import.py:111
msgid "Assigned role"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:563
#: netbox/dcim/forms/bulk_import.py:579
msgid "Device type manufacturer"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:569
#: netbox/dcim/forms/bulk_import.py:585
msgid "Device type model"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:576
#: netbox/dcim/forms/bulk_import.py:592
#: netbox/virtualization/forms/bulk_import.py:137
msgid "Assigned platform"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:584 netbox/dcim/forms/bulk_import.py:588
#: netbox/dcim/forms/bulk_import.py:600 netbox/dcim/forms/bulk_import.py:604
#: netbox/dcim/forms/model_forms.py:651
msgid "Virtual chassis"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:595
#: netbox/dcim/forms/bulk_import.py:611
msgid "Virtualization cluster"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:624
#: netbox/dcim/forms/bulk_import.py:640
msgid "Assigned location (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:631
#: netbox/dcim/forms/bulk_import.py:647
msgid "Assigned rack (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:634
#: netbox/dcim/forms/bulk_import.py:650
msgid "Face"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:637
#: netbox/dcim/forms/bulk_import.py:653
msgid "Mounted rack face"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:644
#: netbox/dcim/forms/bulk_import.py:660
msgid "Parent device (for child devices)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:647
#: netbox/dcim/forms/bulk_import.py:663
msgid "Device bay"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:651
#: netbox/dcim/forms/bulk_import.py:667
msgid "Device bay in which this device is installed (for child devices)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:724
#: netbox/dcim/forms/bulk_import.py:740
msgid "The device in which this module is installed"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:727 netbox/dcim/forms/model_forms.py:755
#: netbox/dcim/forms/bulk_import.py:743 netbox/dcim/forms/model_forms.py:755
msgid "Module bay"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:730
#: netbox/dcim/forms/bulk_import.py:746
msgid "The module bay in which this module is installed"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:736
#: netbox/dcim/forms/bulk_import.py:752
msgid "The type of module"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:744 netbox/dcim/forms/model_forms.py:774
#: netbox/dcim/forms/bulk_import.py:760 netbox/dcim/forms/model_forms.py:774
msgid "Replicate components"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:746
#: netbox/dcim/forms/bulk_import.py:762
msgid ""
"Automatically populate components associated with this module type (enabled "
"by default)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:749 netbox/dcim/forms/model_forms.py:780
#: netbox/dcim/forms/bulk_import.py:765 netbox/dcim/forms/model_forms.py:780
msgid "Adopt components"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:751 netbox/dcim/forms/model_forms.py:783
#: netbox/dcim/forms/bulk_import.py:767 netbox/dcim/forms/model_forms.py:783
msgid "Adopt already existing components"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:791 netbox/dcim/forms/bulk_import.py:817
#: netbox/dcim/forms/bulk_import.py:843
#: netbox/dcim/forms/bulk_import.py:807 netbox/dcim/forms/bulk_import.py:833
#: netbox/dcim/forms/bulk_import.py:859
msgid "Port type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:799 netbox/dcim/forms/bulk_import.py:825
#: netbox/dcim/forms/bulk_import.py:815 netbox/dcim/forms/bulk_import.py:841
msgid "Port speed in bps"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:863
#: netbox/dcim/forms/bulk_import.py:879
msgid "Outlet type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:870
#: netbox/dcim/forms/bulk_import.py:886
msgid "Local power port which feeds this outlet"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:876
#: netbox/dcim/forms/bulk_import.py:892
msgid "Electrical phase (for three-phase circuits)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:920 netbox/dcim/forms/model_forms.py:1477
#: netbox/dcim/forms/bulk_import.py:936 netbox/dcim/forms/model_forms.py:1477
#: netbox/virtualization/forms/bulk_import.py:166
#: netbox/virtualization/forms/model_forms.py:317
msgid "Parent interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:927 netbox/dcim/forms/model_forms.py:1485
#: netbox/dcim/forms/bulk_import.py:943 netbox/dcim/forms/model_forms.py:1485
#: netbox/virtualization/forms/bulk_import.py:173
#: netbox/virtualization/forms/model_forms.py:325
msgid "Bridged interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:930
#: netbox/dcim/forms/bulk_import.py:946
msgid "Lag"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:934
#: netbox/dcim/forms/bulk_import.py:950
msgid "Parent LAG interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:937
#: netbox/dcim/forms/bulk_import.py:953
msgid "Vdcs"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:942
#: netbox/dcim/forms/bulk_import.py:958
msgid "VDC names separated by commas, encased with double quotes. Example:"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:948
#: netbox/dcim/forms/bulk_import.py:964
msgid "Physical medium"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:951 netbox/dcim/forms/filtersets.py:1453
#: netbox/dcim/forms/bulk_import.py:967 netbox/dcim/forms/filtersets.py:1453
msgid "Duplex"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:956
#: netbox/dcim/forms/bulk_import.py:972
msgid "Poe mode"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:962
#: netbox/dcim/forms/bulk_import.py:978
msgid "Poe type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:971
#: netbox/dcim/forms/bulk_import.py:987
#: netbox/virtualization/forms/bulk_import.py:179
msgid "IEEE 802.1Q operational mode (for L2 interfaces)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:978
#: netbox/dcim/forms/bulk_import.py:994
#: netbox/virtualization/forms/bulk_import.py:186
msgid "Filter VLANs available for assignment by group"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:985
#: netbox/dcim/forms/bulk_import.py:1001
#: netbox/virtualization/forms/bulk_import.py:193
msgid "Assigned untagged VLAN ID (filtered by VLAN group)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:994
#: netbox/dcim/forms/bulk_import.py:1010
#: netbox/virtualization/forms/bulk_import.py:202
msgid ""
"Assigned tagged VLAN IDs separated by commas, encased with double quotes "
"(filtered by VLAN group). Example:"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1005
#: netbox/dcim/forms/bulk_import.py:1021
#: netbox/virtualization/forms/bulk_import.py:213
msgid "Assigned Q-in-Q Service VLAN ID (filtered by VLAN group)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1012 netbox/ipam/forms/bulk_import.py:164
#: netbox/dcim/forms/bulk_import.py:1028 netbox/ipam/forms/bulk_import.py:164
#: netbox/ipam/forms/bulk_import.py:253 netbox/ipam/forms/bulk_import.py:289
#: netbox/ipam/forms/filtersets.py:210 netbox/ipam/forms/filtersets.py:293
#: netbox/ipam/forms/filtersets.py:360
@@ -5165,83 +5173,83 @@ msgstr ""
msgid "Assigned VRF"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1015
#: netbox/dcim/forms/bulk_import.py:1031
msgid "Rf role"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1018
#: netbox/dcim/forms/bulk_import.py:1034
msgid "Wireless role (AP/station)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1062
#: netbox/dcim/forms/bulk_import.py:1078
#, python-brace-format
msgid "VDC {vdc} is not assigned to device {device}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1076 netbox/dcim/forms/model_forms.py:1143
#: netbox/dcim/forms/bulk_import.py:1092 netbox/dcim/forms/model_forms.py:1143
#: netbox/dcim/forms/model_forms.py:1762 netbox/dcim/forms/object_import.py:117
msgid "Rear port"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1079
#: netbox/dcim/forms/bulk_import.py:1095
msgid "Corresponding rear port"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1084 netbox/dcim/forms/bulk_import.py:1125
#: netbox/dcim/forms/bulk_import.py:1462
#: netbox/dcim/forms/bulk_import.py:1100 netbox/dcim/forms/bulk_import.py:1141
#: netbox/dcim/forms/bulk_import.py:1478
msgid "Physical medium classification"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1153 netbox/dcim/tables/devices.py:883
#: netbox/dcim/forms/bulk_import.py:1169 netbox/dcim/tables/devices.py:883
msgid "Installed device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1157
#: netbox/dcim/forms/bulk_import.py:1173
msgid "Child device installed within this bay"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1159
#: netbox/dcim/forms/bulk_import.py:1175
msgid "Child device not found."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1217
#: netbox/dcim/forms/bulk_import.py:1233
msgid "Parent inventory item"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1220
#: netbox/dcim/forms/bulk_import.py:1236
msgid "Component type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1224
#: netbox/dcim/forms/bulk_import.py:1240
msgid "Component Type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1227
#: netbox/dcim/forms/bulk_import.py:1243
msgid "Component name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1229
#: netbox/dcim/forms/bulk_import.py:1245
msgid "Component Name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1272 netbox/dcim/forms/bulk_import.py:1290
#: netbox/dcim/forms/bulk_import.py:1288 netbox/dcim/forms/bulk_import.py:1306
msgid "Component name must be specified when component type is specified"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1282
#: netbox/dcim/forms/bulk_import.py:1298
#, python-brace-format
msgid "Component not found: {device} - {component_name}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1295
#: netbox/dcim/forms/bulk_import.py:1311
msgid "Component type must be specified when component name is specified"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1322 netbox/ipam/forms/bulk_import.py:314
#: netbox/dcim/forms/bulk_import.py:1338 netbox/ipam/forms/bulk_import.py:314
msgid "Parent device of assigned interface (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1325 netbox/ipam/forms/bulk_import.py:317
#: netbox/dcim/forms/bulk_import.py:1341 netbox/ipam/forms/bulk_import.py:317
#: netbox/virtualization/filtersets.py:259
#: netbox/virtualization/filtersets.py:310
#: netbox/virtualization/forms/bulk_edit.py:182
@@ -5255,149 +5263,149 @@ msgstr ""
msgid "Virtual machine"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1329 netbox/ipam/forms/bulk_import.py:321
#: netbox/dcim/forms/bulk_import.py:1345 netbox/ipam/forms/bulk_import.py:321
msgid "Parent VM of assigned interface (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1336 netbox/ipam/filtersets.py:1035
#: netbox/dcim/forms/bulk_import.py:1352 netbox/ipam/filtersets.py:1035
#: netbox/ipam/forms/bulk_import.py:328
msgid "Assigned interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1340
#: netbox/dcim/forms/bulk_import.py:1356
msgid "Make this the primary MAC address for the assigned interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1377
#: netbox/dcim/forms/bulk_import.py:1393
msgid "Must specify the parent device or VM when assigning an interface"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1403
#: netbox/dcim/forms/bulk_import.py:1419
msgid "Side A site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1407
#: netbox/dcim/forms/bulk_import.py:1423
#: netbox/wireless/forms/bulk_import.py:94
msgid "Site of parent device A (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1410
#: netbox/dcim/forms/bulk_import.py:1426
msgid "Side A device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1413 netbox/dcim/forms/bulk_import.py:1438
#: netbox/dcim/forms/bulk_import.py:1429 netbox/dcim/forms/bulk_import.py:1454
msgid "Device name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1416
#: netbox/dcim/forms/bulk_import.py:1432
msgid "Side A type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1422
#: netbox/dcim/forms/bulk_import.py:1438
msgid "Side A name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1423 netbox/dcim/forms/bulk_import.py:1448
#: netbox/dcim/forms/bulk_import.py:1439 netbox/dcim/forms/bulk_import.py:1464
msgid "Termination name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1428
#: netbox/dcim/forms/bulk_import.py:1444
msgid "Side B site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1432
#: netbox/dcim/forms/bulk_import.py:1448
#: netbox/wireless/forms/bulk_import.py:115
msgid "Site of parent device B (if any)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1435
#: netbox/dcim/forms/bulk_import.py:1451
msgid "Side B device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1441
#: netbox/dcim/forms/bulk_import.py:1457
msgid "Side B type"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1447
#: netbox/dcim/forms/bulk_import.py:1463
msgid "Side B name"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1456
#: netbox/dcim/forms/bulk_import.py:1472
#: netbox/wireless/forms/bulk_import.py:134
msgid "Connection status"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1481
#: netbox/dcim/forms/bulk_import.py:1497
msgid "Color name (e.g. \"Red\") or hex code (e.g. \"f44336\")"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1533
#: netbox/dcim/forms/bulk_import.py:1549
#, python-brace-format
msgid "Side {side_upper}: {device} {termination_object} is already connected"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1539
#: netbox/dcim/forms/bulk_import.py:1555
#, python-brace-format
msgid "{side_upper} side termination not found: {device} {name}"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1560
#: netbox/dcim/forms/bulk_import.py:1576
#, python-brace-format
msgid ""
"{color} did not match any used color name and was longer than six "
"characters: invalid hex."
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1585 netbox/dcim/forms/model_forms.py:904
#: netbox/dcim/forms/bulk_import.py:1601 netbox/dcim/forms/model_forms.py:904
#: netbox/dcim/tables/devices.py:1088 netbox/templates/dcim/device.html:138
#: netbox/templates/dcim/virtualchassis.html:17
#: netbox/templates/dcim/virtualchassis.html:57
msgid "Master"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1589
#: netbox/dcim/forms/bulk_import.py:1605
msgid "Master device"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1606
#: netbox/dcim/forms/bulk_import.py:1622
msgid "Name of parent site"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1640
#: netbox/dcim/forms/bulk_import.py:1656
msgid "Upstream power panel"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1670
#: netbox/dcim/forms/bulk_import.py:1686
msgid "Primary or redundant"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1675
#: netbox/dcim/forms/bulk_import.py:1691
msgid "Supply type (AC/DC)"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1680
#: netbox/dcim/forms/bulk_import.py:1696
msgid "Single or three-phase"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1731 netbox/dcim/forms/model_forms.py:1860
#: netbox/dcim/forms/bulk_import.py:1747 netbox/dcim/forms/model_forms.py:1860
#: netbox/templates/dcim/device.html:196
#: netbox/templates/dcim/virtualdevicecontext.html:30
#: netbox/templates/virtualization/virtualmachine.html:52
msgid "Primary IPv4"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1735
#: netbox/dcim/forms/bulk_import.py:1751
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1738 netbox/dcim/forms/model_forms.py:1869
#: netbox/dcim/forms/bulk_import.py:1754 netbox/dcim/forms/model_forms.py:1869
#: netbox/templates/dcim/device.html:212
#: netbox/templates/dcim/virtualdevicecontext.html:41
#: netbox/templates/virtualization/virtualmachine.html:68
msgid "Primary IPv6"
msgstr ""
#: netbox/dcim/forms/bulk_import.py:1742
#: netbox/dcim/forms/bulk_import.py:1758
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
msgstr ""

View File

@@ -283,10 +283,41 @@ class GroupForm(forms.ModelForm):
def get_object_types_choices():
return [
(ot.pk, str(ot))
for ot in ObjectType.objects.filter(OBJECTPERMISSION_OBJECT_TYPES).order_by('app_label', 'model')
]
"""
Generate choices for object types grouped by app label using optgroups.
Returns nested structure: [(app_label, [(id, model_name), ...]), ...]
"""
from django.apps import apps
choices = []
current_app = None
current_group = []
for ot in ObjectType.objects.filter(OBJECTPERMISSION_OBJECT_TYPES).order_by('app_label', 'model'):
# Get verbose app label (e.g., "NetBox Branching" instead of "netbox_branching")
try:
app_config = apps.get_app_config(ot.app_label)
app_label = app_config.verbose_name
except LookupError:
app_label = ot.app_label
# Start new optgroup when app changes
if current_app != app_label:
if current_group:
choices.append((current_app, current_group))
current_app = app_label
current_group = []
# Add model to current group using just the model's verbose name
model_class = ot.model_class()
model_name = model_class._meta.verbose_name if model_class else ot.model
current_group.append((ot.pk, model_name.title()))
# Add final group
if current_group:
choices.append((current_app, current_group))
return choices
class ObjectPermissionForm(forms.ModelForm):

View File

@@ -72,9 +72,22 @@ class AvailableOptions(forms.SelectMultiple):
will be empty.) Employed by SplitMultiSelectWidget.
"""
def optgroups(self, name, value, attrs=None):
self.choices = [
choice for choice in self.choices if str(choice[0]) not in value
]
# Handle both flat choices and optgroup choices
filtered_choices = []
for choice in self.choices:
# Check if this is an optgroup (nested tuple) or flat choice
if isinstance(choice[1], (list, tuple)):
# This is an optgroup: (group_label, [(id, name), ...])
group_label, group_choices = choice
filtered_group = [c for c in group_choices if str(c[0]) not in value]
if filtered_group: # Only include optgroup if it has choices left
filtered_choices.append((group_label, filtered_group))
else:
# This is a flat choice: (id, name)
if str(choice[0]) not in value:
filtered_choices.append(choice)
self.choices = filtered_choices
value = [] # Clear selected choices
return super().optgroups(name, value, attrs)
@@ -86,6 +99,12 @@ class AvailableOptions(forms.SelectMultiple):
return context
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
# Add title attribute to show full text on hover
option['attrs']['title'] = label
return option
class SelectedOptions(forms.SelectMultiple):
"""
@@ -93,12 +112,31 @@ class SelectedOptions(forms.SelectMultiple):
will include _all_ choices.) Employed by SplitMultiSelectWidget.
"""
def optgroups(self, name, value, attrs=None):
self.choices = [
choice for choice in self.choices if str(choice[0]) in value
]
# Handle both flat choices and optgroup choices
filtered_choices = []
for choice in self.choices:
# Check if this is an optgroup (nested tuple) or flat choice
if isinstance(choice[1], (list, tuple)):
# This is an optgroup: (group_label, [(id, name), ...])
group_label, group_choices = choice
filtered_group = [c for c in group_choices if str(c[0]) in value]
if filtered_group: # Only include optgroup if it has choices left
filtered_choices.append((group_label, filtered_group))
else:
# This is a flat choice: (id, name)
if str(choice[0]) in value:
filtered_choices.append(choice)
self.choices = filtered_choices
value = [] # Clear selected choices
return super().optgroups(name, value, attrs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
# Add title attribute to show full text on hover
option['attrs']['title'] = label
return option
class SplitMultiSelectWidget(forms.MultiWidget):
"""