Compare commits

...

370 Commits

Author SHA1 Message Date
Jeremy Stretch
952be24365 Merge pull request #13838 from netbox-community/develop
Release v3.6.2
2023-09-20 15:29:06 -04:00
Jeremy Stretch
b57a47475d Release v3.6.2 2023-09-20 15:05:29 -04:00
Jeremy Stretch
4f05cf55a5 Changelog for #11617, #12685, #13245, #13653, #13757, #13809, #13813, #13818 2023-09-20 14:47:47 -04:00
Jeremy Stretch
5dcf8502af Grammar fix 2023-09-20 14:44:04 -04:00
Jeremy Stretch
7a21541ed6 Plug NetBox Cloud in installation docs 2023-09-20 14:43:12 -04:00
Jeremy Stretch
ae4ea3443e Fixes #11617: Check for invalid CSV headers during bulk import (#13826)
* Fixes #11617: Check for invalid CSV headers during bulk import

* Add test for CSV import header validation
2023-09-20 14:40:27 -04:00
Arthur Hanson
f5dd7d853a 13809 fix ConfigRevision edit if custom validators (#13825)
* 13809 fix ConfigRevision edit, check if custom validator JSON serializable

* 13809 check json rendering for all fields

* Refactor field initialization logic to more cleanly handle statically configured values

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-20 14:11:25 -04:00
Arthur Hanson
a1e42dad10 13653 darken code color to work in light and dark modes (#13827)
* 13653 darken code color to work in light and dark modes

* 13809 changed to use mx-1 on code block
2023-09-20 14:08:12 -04:00
Arthur Hanson
6e4b4a553b 12685 use markdown for custom fields added to form (#13828)
* 12685 use markdown for custom fields added to form

* 13809 change markdown to use utilities

* Add help_text for CustomField description indicating Markdown support

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-20 14:06:04 -04:00
Arthur Hanson
7a410dfd00 13813 fix virtual chassis member count (#13823)
* 13813 fix virtual chassis member count

* 13813 add test
2023-09-20 13:57:35 -04:00
bluikko
6fb980349f 13245 add QSFP112 and OSFP-RHS interface choices 2023-09-20 10:10:51 -04:00
Arthur Hanson
8e251ac33c 13757 Fix ConfigContext reference to DeviceType (#13804)
* 13757 do prefetch to work around Django issue with vars in init (DeviceType)

* 13757 use self.__dict to access vars in init

* 13757 change test
2023-09-20 09:56:52 -04:00
Jeremy Stretch
35bcc2ce9d Revert "Fixes #13741: Enforce unique names for inventory items with no parent item"
This reverts commit 68966db23d.
2023-09-20 08:44:25 -04:00
Arthur
69215c411b 13818 add tags to l2vpntermination edit form 2023-09-19 17:42:19 -04:00
Jeremy Stretch
a08b5793f6 Correct example default dashboard config 2023-09-19 14:40:52 -04:00
Jeremy Stretch
252bf03525 Fixes #13802: Restore 'description' header text for custom fields 2023-09-18 13:35:54 -04:00
Jeremy Stretch
b9b9bb134f Changelog for #13741, #13745, #13756, #13782 2023-09-18 11:12:27 -04:00
Jeremy Stretch
68966db23d Fixes #13741: Enforce unique names for inventory items with no parent item 2023-09-18 11:10:00 -04:00
Jeremy Stretch
9aa7444bf9 Fixes #13782: Fix tag exclusion support for contact assignments 2023-09-18 11:08:49 -04:00
Arthur Hanson
b0541be107 13745 device type migration (#13747)
* 13745 update migrations to use batch_size

* 13745 update migrations to use subquery update

* 13745 refactor and update other counter migrations
2023-09-18 09:59:26 -04:00
Abhimanyu Saharan
3d1f668235 Disables module_status ordering (#13761)
* disables module_status ordering #13756

* Set accessor for module status value

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-18 09:09:29 -04:00
Jeremy Stretch
940c947d3f Changelog for #11209, #12219, #13727, #13563, #13767, #13791 2023-09-18 08:49:08 -04:00
Jeremy Stretch
c7dd4206c8 Fixes #13727: Fix exception when viewing rendered config for VM without a role assigned 2023-09-18 08:44:42 -04:00
Per von Zweigbergk
79bf12a8fe 13791 rename whitespace fix (#13793)
* Add test for bug #13791
https://github.com/netbox-community/netbox/issues/13791

* Fix #13791 by disabling striping on find and replace fields of BulkRenameForm
2023-09-18 08:33:29 -04:00
Jeremy Stretch
2dfbd72f10 Fixes #13767: Fix support for comments when creating a new service via web UI 2023-09-15 10:33:54 -04:00
Arthur
487827c776 13768 fix typo 2023-09-15 09:40:27 -04:00
Jeremy Stretch
6939bf8aed Fixes #12219: Ensure dashboard widget heading text has sufficient contrast (#13753)
* Fixes #12219: Ensure dashboard widget heading text has sufficient contrast in both light & dark modes

* Change foreground color for teal background
2023-09-13 10:56:03 -04:00
Daniel Sheppard
e4cb0c3cc2 Fixes #11209 - Fix PrefixIPAddress view with saved sort preferences (#12820)
* Fixes #11209 - Do not add available ips when IPAddressTable sort preferences are saved

* Refine check to account scenario right after clearing ordering string

* Introduce get_table_ordering() utility to determine intended ordering given a request

* Apply fix to VLAN ranges as well

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-13 10:51:24 -04:00
Daniel W. Anner
cf2f39a0a8 Documentation: LDAP Update for Active Directory (#13716)
* Adding documentation to 6-LDAP to display how to allow Active Directory logins with or without the user UPN suffix.

* Correcting misspellings and clarifying explanations

* Updating sections to include sample template

* Misc revisions

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-13 08:44:52 -04:00
Abhimanyu Saharan
b7cfb2f7d9 Adds csv dialect detection to bulk import view (#13563)
* adds csv dialect detection to bulk import view #13239

* adds sane delimiters for dialect detection #13239

* adds csv delimiter tests #13239

* adds csv delimiter on the form

* pass delimiter to clean_csv method #13239

* fix tests for csv import #13239

* fix tests for csv import #13239

* fix tests for csv import #13239

* fix tests for csv import #13239

* Improve auto-detection of import data format

* Misc cleanup

* Include tab as a supported delimiting character for auto-detection

* Move delimiting chars to a separate constant for easy reference

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-12 16:48:40 -04:00
Jeremy Stretch
39cb9c32d6 Clean up blocktrans template tags (i18n) 2023-09-11 16:17:02 -04:00
Jeremy Stretch
75b71890a4 Misc i18n cleanup 2023-09-11 15:59:50 -04:00
Jeremy Stretch
2ffa6d0188 Fixes #13701: Correct display of power feed legs under device view 2023-09-11 14:16:29 -04:00
Jeremy Stretch
026386db50 Fixes #13706: Restore extra filters dropdown on device interfaces list 2023-09-11 14:13:55 -04:00
Jeremy Stretch
b5125e512f Fixes #13721: Filter VLAN choices by selected site (if any) when creating a prefix 2023-09-11 13:52:19 -04:00
Jeremy Stretch
a8a36c0a8f PRVB 2023-09-06 14:26:19 -04:00
Jeremy Stretch
99ab054ea0 Merge pull request #13705 from netbox-community/develop
Release v3.6.1
2023-09-06 14:23:36 -04:00
Jeremy Stretch
90ab4b3c86 Release v3.6.1 2023-09-06 14:04:57 -04:00
Arthur Hanson
bb6b4d01c1 12553 prefix serializer to IPAddress (#13592)
* 12553 prefix serializer to IPAddress

* Introduce IPNetworkField to handle prefix serialization

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-06 10:49:40 -04:00
Daniel Sheppard
2d1457b94b Fixes: #13682 - Fix custom field exceptions and validation (#13685)
* Fixes: #13682 - Fix custom field exceptions and validation

* Add tests

* Remove default setting for multi-select/multi-object and return slice of choices and annotate.

* Remove redundant default choice valiadtion; introduce values property on CustomFieldChoiceSet

* Refactor test

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-06 10:47:18 -04:00
Arthur Hanson
9d851924c8 13674 fix ReportSerializer (#13688)
* 13674 fix ReportSerializer

* Remove test_methods attr from Report class

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-06 08:44:25 -04:00
Jeremy Stretch
9be5918c83 Fixes #13684: Enable modying the configuration when maintenance mode is enabled 2023-09-05 14:09:38 -04:00
Jeremy Stretch
6db6616892 Changelog for #12870, #13444, #13596, #13642, #13657 2023-09-01 17:14:59 -04:00
Abhimanyu Saharan
004daca862 Adds rename button on the list page for device components (#13564)
* adds interface rename button on the list page #13444

* adds rename view on all device components #13564

* Condense component views to a single template

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-09-01 16:58:31 -04:00
Jeremy Stretch
559f65f6b2 Add #12906 to v3.6.0 changelog 2023-09-01 13:22:07 -04:00
Jeremy Stretch
c38884fa11 Add description & expires fields to token test 2023-09-01 12:33:02 -04:00
Abhimanyu Saharan
7848beedce adds additional parameters for token provision api #12870 2023-09-01 12:33:02 -04:00
Jeremy Stretch
296166da95 Fixes #13656: Correct decoding of BinaryField content for Django 4.2 2023-09-01 11:06:19 -04:00
Jeremy Stretch
679cc8fdda Fixes #13596: Always display "render config" tab for devices & VMs 2023-08-31 14:36:03 -04:00
Jeremy Stretch
0cdc26e013 Fixes #13642: Move migration logic overrides from individual mgmt commands to core 2023-08-31 14:34:26 -04:00
Jeremy Stretch
2503568875 Changelog for #13619, #13620, #13622, #13628, #13632, #13638 2023-08-31 12:23:59 -04:00
Jeremy Stretch
78966e12a9 Fixes #13620: Show admin menu items only for staff users 2023-08-31 12:20:46 -04:00
Jeremy Stretch
f962fb3b53 Closes #13638: Add optional staff_only attribute to MenuItem (#13639)
* Closes #13638: Add optional staff_only attribute to MenuItem

* Add missing file

* Add release note
2023-08-31 11:23:44 -04:00
Jeremy Stretch
2544e2bf18 Fixes #13622: Fix exception when viewing current config and no revisions have been created 2023-08-31 11:11:56 -04:00
Jeremy Stretch
06f2c6f867 Fixes #13632: Avoid raising exception when checking if FHRP group IP address is primary 2023-08-31 11:09:49 -04:00
Abhimanyu Saharan
272d2c54d4 removes napalm references #13628 2023-08-31 09:54:35 -04:00
Jeremy Stretch
cb93abb0f4 Fixes #13626: Correct filtering of recent activity list under user view 2023-08-31 08:19:17 -04:00
Jeremy Stretch
316d991b33 Fixes #13630: Fix display of active status under user view 2023-08-31 08:16:11 -04:00
Jamie (Bear) Murphy
46f734eba2 fix error for is_oob_ip for non-device parents (#13621)
* fix error for is_oob_ip for non-device parents

* adjust oob_ip_id check to use hasattr
2023-08-31 07:57:14 -04:00
Jeremy Stretch
671a56100a PRVB 2023-08-30 14:57:16 -04:00
Jeremy Stretch
dfcfbe240d Merge pull request #13614 from netbox-community/develop
Release v3.6.0
2023-08-30 14:51:04 -04:00
Jeremy Stretch
b040fdcf2c Release v3.6.0 2023-08-30 14:27:07 -04:00
Jeremy Stretch
8525f994c0 Fix invalid links 2023-08-30 14:21:04 -04:00
Jeremy Stretch
eb9a804914 #12591: Add a dedicated view for the active config revision 2023-08-30 11:13:56 -04:00
Jeremy Stretch
210d7bb573 Display last_updated time only if defined 2023-08-30 11:13:02 -04:00
Jeremy Stretch
dc85476b9e Changelog for #11478, #13513, #13599, #13605 2023-08-30 09:36:44 -04:00
Daniel Sheppard
1854a6b76b Fix #11478 - Add vc_interfaces flag to control selection of VC interfaces (#13296)
* Add `vc_interfaces` flag to control interface queryset

* Fix test failure

* Add new filters instead of using undocumented query params

* Cleanup filterset, add test

* Rename filter and re-introduce virtual_chassis filtering method (required)

* Fix test

* Adjust tests to more accurately provide coverage

* Add breaking change note

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-30 09:33:02 -04:00
Jeremy Stretch
aebf3288d1 Fixes #13605: Specify batch size for cached counter migrations (#13610)
* Specify batch size for cached counter migrations

* Remove list() casting of querysets
2023-08-30 09:18:24 -04:00
Arthur Hanson
065a40dfb3 13599 fix cached counter for edit object (#13600)
* 13599 fix cache counter

* 13599 update test

* Merge conditionals

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-29 15:31:13 -04:00
Jeremy Stretch
83536fbb23 #12814: Add context data section to config rendering doc 2023-08-29 14:43:07 -04:00
Jeremy Stretch
420090dc6c #12590: Exclude proxy model for Token from permission object types 2023-08-29 14:41:14 -04:00
Jeremy Stretch
4ab0eb570c #11305: Add latitude & longitude to DeviceWithConfigContextSerializer 2023-08-29 14:31:42 -04:00
Jeremy Stretch
2a4e3dd09f Merge branch 'develop' into feature 2023-08-29 10:45:55 -04:00
Jeremy Stretch
0dbfbf6941 Merge pull request #13591 from netbox-community/develop
Correct version number
2023-08-28 17:07:15 -04:00
Jeremy Stretch
d515530277 Merge branch 'master' into develop 2023-08-28 17:05:59 -04:00
Jeremy Stretch
4343e0566b Correct version number 2023-08-28 17:04:37 -04:00
Jeremy Stretch
8555269f7e Merge pull request #13589 from netbox-community/develop
Release v3.5.9
2023-08-28 16:58:09 -04:00
Jeremy Stretch
f42a2ac10c Merge branch 'master' into develop 2023-08-28 16:19:44 -04:00
Jeremy Stretch
4ea3a29c0e Release v3.5.9 2023-08-28 16:13:13 -04:00
Arthur Hanson
29877c9abe 12489 Use HTMX for Location and Non-Racked Devices in Site detail view (#12491)
* 12489 use htmx for site view locations and non-racked-devices

* 12489 remove now unused queries in context

* adds device type and role to device component filter #12015

* Revert "Fixes #12463: Fix the association of completed jobs with reports & scripts in the REST API"

This reverts commit a29a07ed26.

* 12489 update nonracked_devices on rack and location templates

* 12489 fix whitespace issue

* Undo errant commits

* 12489 update site id in templates

* 12489 remove nonracked_devices include

* 12489 add has_position filter

* Use empty lookup for position field

* Remove non-racked devices list from rack view (was moved to a tab)

* Clean up location and device tables

* Restore plugins block on rack template

---------

Co-authored-by: Abhimanyu Saharan <desk.abhimanyu@gmail.com>
Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-08-28 16:03:35 -04:00
Jeremy Stretch
480f83c42d Closes #13585: Introduce 'empty' lookup for numeric value filters 2023-08-28 15:25:37 -04:00
Jeremy Stretch
faf89350ac Fixes #13569: Fix selection widgets for related interfaces when bulk editing interfaces under device view 2023-08-28 13:04:42 -04:00
Jeremy Stretch
d9c3ce935f Changelog for #12825, #13313, #13415, #13507, #13542, #13543, #13544, #13556 2023-08-28 09:10:44 -04:00
Abhimanyu Saharan
8d8f57e8b8 Adds parent filter on iprange (#13568)
* adds parent filter on iprange #13313

* lint fix

* adds filterset test

* Filter should match both start & end of IP range

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-28 09:05:43 -04:00
Abhimanyu Saharan
0a3be0b7ea adds related models count on custom field #12825 2023-08-28 08:34:33 -04:00
Abhimanyu Saharan
00ebdfe0df adds related models count on custom field #12825 2023-08-28 08:34:33 -04:00
Jeremy Stretch
d79fa131bb Closes #13415: Pass request context when rendering custom links in a table column 2023-08-25 13:14:47 -04:00
Abhimanyu Saharan
be2b24a155 fixes the swagger schema for token provisioning #13557 2023-08-25 09:45:03 -04:00
Abhimanyu Saharan
03b341dbfd adds missing status choicefield for vdc #13556 2023-08-25 09:40:04 -04:00
Arthur
ca5e69897d 13396 upgrade graphiql 2023-08-24 14:17:09 -04:00
Abhimanyu Saharan
3090dd4934 Fixed permission for config context UI view (#13547)
* fixed permission for config context UI view #13543

* removed extras.view_configcontext permission #13543
2023-08-24 14:13:31 -04:00
Abhimanyu Saharan
1f1d1ee502 adds additional safe HTTP headers to request #13542 2023-08-24 14:12:08 -04:00
Abhimanyu Saharan
1c2cf11f47 fixes global search when the content type is not found #13507 2023-08-24 14:09:48 -04:00
Jeremy Stretch
08961e751d Revert changes from #13373 pending further discussion around implementation
This reverts commit 66e4e31209.
2023-08-24 14:02:15 -04:00
Abhimanyu Saharan
88bf82be05 clear all cache when lazy is not used #13544 2023-08-24 10:12:48 -04:00
Jeremy Stretch
506884bc4d Changelog for #11272, #13516, #13530, #13536 2023-08-23 14:44:14 -04:00
Jeremy Stretch
646fa341ab Closes #13470: Remove misleading statement about access to report results 2023-08-23 14:41:38 -04:00
Jeremy Stretch
d73f7b1943 Fixes #13530: Ensure script log messages are cast as strings for proper serialization 2023-08-23 14:41:21 -04:00
Abhimanyu Saharan
a75e8416a4 adds vlan child table to vlan group #13536 2023-08-23 13:39:10 -04:00
Arthur
f743f2cfb8 11272 make position field work correctly when internationalizion enabled 2023-08-23 13:30:01 -04:00
Jeremy Stretch
7d7e8127f5 Fixes #13513: Prevent exception when rendering bookmarks widget for anonymous user 2023-08-23 10:53:56 -04:00
Jeremy Stretch
3c0a3ca703 Fixes #13516: Plugin utility functions should be importable from extras.plugins 2023-08-22 10:27:21 -04:00
Jeremy Stretch
45062697c5 Changelog for #11508, #13358, #13477, #13478, #13500, #13503 2023-08-21 15:10:12 -04:00
Arthur Hanson
66e4e31209 11508 Add group assignments for Azure SSO (#13373)
* 11508 temp azure changes

* 11508 map AzureAD groups to NetBox groups

* 11508 add is_active, reset superuser and staff based on Azure

* 11508 remove is_active, add documentation use azuread

* 11508 remove addition to settings

* 11508 review changes, add additional logging and error checking

* 11508 review changes, remove extra flag

* 11508 review changes, change SOCIAL_AUTH_ to REMOTE_AUTH_BACKEND

* 11508 clear user groups

* 11508 clear user groups

* 11508 review feedback change config key

* 11508 review changes

* 11508 review changes - add error checking

* 11508 review changes - flexible config params
2023-08-21 14:42:16 -04:00
kkthxbye-code
c86cfe3cbf Correct filter name in redirect after bulk edit
* Added modified_by_request filter to ChangeLoggedFilterSet
2023-08-21 14:35:08 -04:00
Arthur
28e112743f 13503 fix rack space utilization graph for internationalization 2023-08-21 14:21:50 -04:00
Arthur
229007082b 13510 update docs run permission image 2023-08-21 14:03:31 -04:00
Abhimanyu Saharan
4004966b16 fix content type filter on export template #13478 2023-08-17 15:29:21 -04:00
Arthur
fe95cb434a 13500 fix l2vpntermination bulk update 2023-08-17 15:25:23 -04:00
Jeremy Stretch
5709bc3b2b Release v3.6-beta2 2023-08-16 11:28:31 -04:00
Jeremy Stretch
af06510921 Closes #13412: Enable pagination of custom field choice set choices 2023-08-16 11:08:36 -04:00
Jeremy Stretch
b4acbb5e16 Closes #13439: Update API token documentation 2023-08-16 10:28:33 -04:00
Jeremy Stretch
b96e437e2b #8248: Add bookmarks widget to default dashboard 2023-08-16 10:10:31 -04:00
Alexander Haase
16e2283d19 Fix git DataSource clone authentication
Anonymous git clones (in GitLab) require the username and password not
to be set in order to successfully clone. This patch will define clone
args only, if the username passed is not empty.
2023-08-15 13:29:03 -04:00
Jeremy Stretch
c46536f469 Merge pull request #13474 from jose-d/develop-1
upgrading.md: there shouldbe OLDVER instead of NEWVER
2023-08-15 11:26:43 -04:00
Jeremy Stretch
0457520f51 Changelog for #12461 2023-08-15 11:25:56 -04:00
Jeremy Stretch
44f8a777df Merge branch 'develop' into feature 2023-08-15 11:04:03 -04:00
jose_d
9450ce4c3a upgrading.md: there shouldbe OLDVER instead of NEWVER 2023-08-15 16:19:31 +02:00
Jeremy Stretch
1c9a8ec6bd PRVB 2023-08-15 10:00:24 -04:00
Jeremy Stretch
8f5005efd5 Merge pull request #13472 from netbox-community/develop
Release v3.5.8
2023-08-15 09:56:23 -04:00
Jeremy Stretch
e61795d5c6 Release v3.5.8 2023-08-15 09:18:15 -04:00
Joel D. Tague
892c10b1f0 feat: add 200Gbps & 400Gbps interface speed options 2023-08-15 09:11:40 -04:00
Abhimanyu Saharan
752e26c7de Adds config template to vm model (#13450)
* adds config template to vm model #12461

* Add translation tags; collapse config data

* i18n cleanup

* Establish parity with DeviceRenderConfigView

* Move config_template field to RenderConfigMixin

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-14 15:43:28 -04:00
Abhimanyu Saharan
ea107b6b86 adds object view to allow changelog page to be opened #13463 2023-08-14 09:47:58 -04:00
Jeremy Stretch
b9b9c065cc Changelog for #10030, #11578, #12639 2023-08-14 08:55:47 -04:00
Jeremy Stretch
b583770765 Fixes #13451: Disable table ordering for custom link columns 2023-08-14 08:51:16 -04:00
Abhimanyu Saharan
37d6f6abca Merge pull request #13461 from netbox-community/fix/13460-spelling
Fixed spelling for Attributes
2023-08-14 01:18:37 -07:00
Abhimanyu Saharan
be3f48c677 Fixed spelling for Attributes #13460 2023-08-14 13:29:11 +05:30
kkthxbye
5de9d3f15f Fixes #12639 - Make sure name expansions throws a validation error on decrementing ranges (#13326)
* Fixes #12639 - Make sure name expansions throws a validation error on decrementing ranges

* Fix pep8

* Also fail on equal start & end values

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-11 11:53:16 -04:00
Arthur Hanson
8593715149 13319 add documentation for internationalization (#13330)
* 13319 add documentation for internationalization

* 13319 add verbose name to model

* 13319 fix typo

* Flesh out developer doc for i18n

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-11 11:27:48 -04:00
Daniel W. Anner
40afe6cf36 Feature - Schema Generation (#13353)
* Schema generation is working

* Added option to either dump to a file or the console

* Moving schema file and utilizing settings definition for file paths

* Cleaning up the imports and fixing a few pythonic issues

* Tweak command flags

* Clean up choices mapping

* Misc cleanup

* Rename & move template file

* Move management command from extras to dcim

* Update release checklist

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-11 11:00:26 -04:00
Arthur Hanson
9fd07b594c 11578 mark swagger available- apis to accept lists in post (#13445)
* 11578 change swagger for available-ips to accept lists

* 11578 change swagger for available-xxx to accept lists
2023-08-11 09:49:03 -04:00
Jeremy Stretch
dc7411e4c5 Fixes #13446: Don't disable bulk edit/delete buttons after deselecting "select all" checkbox 2023-08-11 08:56:58 -04:00
Jeremy Stretch
315c4bb1ac #13434: Fix tests 2023-08-10 14:32:48 -04:00
Jeremy Stretch
1ff1b4dc89 Changelog for #13433, #13434, #13437 2023-08-10 14:12:42 -04:00
Jeremy Stretch
a332adf962 Fixes #13434: Randomly generate initial keys prior to the creation of new tokens 2023-08-10 14:11:16 -04:00
Jeremy Stretch
856cc0f885 Fixes #13437: Display bookmark button only for relevant objects 2023-08-10 13:55:03 -04:00
Jeremy Stretch
89d8f7aa70 Add missing load tag for i18n 2023-08-10 10:32:56 -04:00
Jeremy Stretch
4d2ef0a8b5 Fixes #13433: User field on API token form should be required 2023-08-10 10:04:31 -04:00
Jeremy Stretch
23b3f72dee Apply missed string translations 2023-08-10 09:38:12 -04:00
Jeremy Stretch
ff59845821 Changelog for #12814, #13037, #13376, #13410 2023-08-09 15:38:03 -04:00
Jeremy Stretch
914588f55d Merge branch 'develop' into feature 2023-08-09 15:31:21 -04:00
Jeremy Stretch
72e1e8fab1 Changelog for #11675, #11922, #12665, #13368, #13414 2023-08-09 15:02:49 -04:00
Abhimanyu Saharan
8b01c30c51 Exposes all models in device context data (#13389)
* exposes all models in device context data #12814

* added app namespaces to the context data

* revert object to device in context data

* moved context to render method of ConfigTemplate

* removed print

* Include only registered models; permit passed context data to overwrite apps

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-09 14:57:59 -04:00
Arthur
dcdb4d27ec 12665 add semicolon to link sanitation safe string 2023-08-09 14:49:34 -04:00
kkthxbye-code
9b1406a1a7 Don't hide HIDDEN_IFUNSET custom fields from bulk import fields 2023-08-09 14:47:20 -04:00
Abhimanyu Saharan
545769ad88 Adds generic object children template (#13388)
* adds generic tab view template #12110

* Rename view_tab.html and move to generic/

* Fix console ports template

* Move bulk operations view resolution to template

* Avoid setting default template_name on ObjectChildrenView

* Move base_template and table_config context vars to base context

* removed bulk_delete_control from templates

* refactored bulk_controls view

* fixed table_config

* renamed object_tab.html to objectchildren_list.html

* removed unused import

* Refactor template blocks for bulk operation buttons

* Rename object children generic template

* Move disconnect bulk action into a separate template for device components

* Fix cluster devices & VM interfaces views

* minor button label change

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-09 14:16:03 -04:00
Jeremy Stretch
16bcb1dbb0 #13426: Employ proper feature keys for image attachment & contact filter forms 2023-08-09 10:41:40 -04:00
Jeremy Stretch
5dce5563ab #11541: Fix object_types queryset on TagSerializer 2023-08-09 10:32:08 -04:00
Jeremy Stretch
4e8a3e0a6f Closes #13426: Register all model features in the registry 2023-08-09 10:27:10 -04:00
Jeremy Stretch
646d52d498 Misc docs cleanup for v3.6 2023-08-09 10:12:40 -04:00
Jeremy Stretch
cd5012bd59 Closes #13424: Move CloningMixin into NetBoxFeatureSet 2023-08-09 10:12:13 -04:00
Jeremy Stretch
4bb0388118 Fixes #13362: Limit displayed choice set list to 50 choices 2023-08-08 09:47:34 -04:00
Jeremy Stretch
f255fe507d Fixes #13410: Fix rendering of custom choice fields with large numner of choices 2023-08-08 09:32:56 -04:00
Jeremy Stretch
f5a1f83f9f Closes #13368: Report installed plugins during server error (#13387)
* Introduce get_installed_plugins() utility

* Extend 500 error template to list installed plugins

* Move get_plugin_config() to extras.plugins.utils
2023-08-07 15:29:20 -04:00
Jeremy Stretch
36072f17a9 Define LOCALE_PATHS 2023-08-07 14:34:56 -04:00
Jeremy Stretch
f9648d8544 Closes #13400: Add 'name' property to BaseTable class 2023-08-07 10:48:41 -04:00
Jeremy Stretch
2236b86c35 Closes #11922: Populate assigned VDCs when adding a child interface 2023-08-04 15:25:59 -04:00
Jeremy Stretch
0dd319d0c8 Closes #11675: Add support for specifying import/export route targets during VRF bulk import 2023-08-04 15:25:06 -04:00
Abhimanyu Saharan
53615944c5 Adds standardized list API for scripts and reports (#13382)
* adds standardized list API for scripts and reports #13037

* adds standardized list API for scripts and reports #13037

* adds standardized list API for scripts and reports #13037

* adds module name to the display #13037
2023-08-04 15:23:15 -04:00
Jeremy Stretch
88562d7dcf Changelog for #12750, #12889, #13033, #13151, #13343, #13369 2023-08-04 13:36:33 -04:00
Abhimanyu Saharan
01bb09db67 adds delete for SyncedDataMixin when related AutoSyncRecord is available #12750 2023-08-04 13:25:56 -04:00
Jeremy Stretch
f1c182bb65 Fixes #13376: Restrict add/remove tag fields by model on bulk edit forms 2023-08-04 13:09:07 -04:00
Henrik Strand
43ce453938 Adding interface TYPE_400GE_CFP2/400gbase-x-cfp2 (#13338)
* Added 400G CFP2 to InterfaceTypeChoices

* Added new type to choises
2023-08-04 11:32:52 -04:00
Jeremy Stretch
2afce6c94b Introduce ContactsMixin 2023-08-04 10:15:50 -04:00
Jeremy Stretch
14e23c3d00 Introduce ImageAttachmentsMixin 2023-08-04 10:15:50 -04:00
Jeremy Stretch
7f22c6bf12 Include notes re: demo data and netbox-docker 2023-08-04 10:12:15 -04:00
Jeremy Stretch
93a862cded Add stadium analogy and behavior anti-patterns 2023-08-04 08:55:43 -04:00
Jeremy Stretch
9cc295827b Fixes #13369: Fix job termination status for failed reports 2023-08-04 08:12:52 -04:00
Jeremy Stretch
14988fc91c Remove redundant overrides of EXEMPT_VIEW_PERMISSIONS 2023-08-03 11:07:30 -04:00
Jeremy Stretch
31f41855f4 Closes #13367: Delete unused device component deletion templates 2023-08-03 10:49:40 -04:00
Jeremy Stretch
caedc8dbe3 Closes #13352: Translation support for model verbose names (#13354)
* Update verbose_name & verbose_name_plural Meta attributes on all models

* Alter makemigrations to ignore verbose_name & verbose_name_plural changes
2023-08-03 10:41:10 -04:00
Jeremy Stretch
24ffaf09d4 Fixes #13363: Fix API endpoint for custom field choice selector in forms 2023-08-03 08:53:46 -04:00
Jeremy Stretch
d9f3637e25 Fixes #13361: Extra choices field on custom field choice set form should not be required 2023-08-03 07:49:54 -04:00
Matej Vadnjal
a807cca29e Fixes #13033: add formatted speed column to Interfaces (#13275)
* Fixes #13033: add formatted speed column to Interfaces

* use TemplateColumn instead of own class
2023-08-02 16:08:14 -04:00
Abhimanyu Saharan
57860f26b7 Adds assigned bool for IP address API (#13301)
* adds assigned bool for ip address API #13151

* Add filterset test

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-02 15:45:09 -04:00
Abhimanyu Saharan
ab916a1819 fixes dummy payload URL for webhook test 2023-08-02 15:23:05 -04:00
Abhimanyu Saharan
a68831d3a1 fixes provider_network_id for related circuits #13343 2023-08-02 15:17:14 -04:00
Jeremy Stretch
04a2543e68 Fixes #13351: Fix missing text due to incorrectly applied translation tags 2023-08-02 14:53:32 -04:00
Jeremy Stretch
82c959570d Release v3.6-beta1 2023-08-02 13:30:08 -04:00
Jeremy Stretch
354dc4398a Update changelog 2023-08-02 11:18:52 -04:00
Jeremy Stretch
a698a93938 Closes #13350: Remove unused DeviceImportTable class 2023-08-02 11:18:06 -04:00
Jeremy Stretch
04c5e62d2b #8248: Permit users to manage their own bookmarks by default 2023-08-02 11:13:09 -04:00
Jeremy Stretch
aa747c3954 #12988: Correct URL path for CustomFieldChoiceSet API endpoint 2023-08-02 11:05:03 -04:00
Jeremy Stretch
1937c1fad6 #12175: Misc cleanup 2023-08-02 11:04:28 -04:00
Jeremy Stretch
bf20611668 #6391: Add device_role to DeviceWithConfigContextSerializer 2023-08-02 10:16:51 -04:00
Jeremy Stretch
8f271151a7 Closes #11519: Add a SQL index for IPAddress host value 2023-08-02 09:56:56 -04:00
Abhimanyu Saharan
0bb86f1e7d Replaces device_role with role on device model (#13342)
* replaces device_role with role on device model #6391

* fixes lint issue #6391

* revert the database user

* revert test_runner comment

* changes as per review

* Update references to device_role column in UserConfigs

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-02 09:55:52 -04:00
Jeremy Stretch
a4c9cbc6dd Remove hard-coded test runner 2023-08-02 08:55:38 -04:00
Jeremy Stretch
79030ecab2 #12589: Move username validation from form to NetBoxUser 2023-08-01 15:42:47 -04:00
Jeremy Stretch
ccb7568462 #8684: Drop support for 'obj' context var when rendering custom links (v3.5) 2023-08-01 15:33:25 -04:00
Jeremy Stretch
6208e0f7f6 #12591: Add extras.ConfigRevision to EXEMPT_EXCLUDE_MODELS 2023-08-01 14:56:59 -04:00
Jeremy Stretch
e64289e791 #12589: Remove obsolete admin resources 2023-08-01 14:35:28 -04:00
Jeremy Stretch
699b4dfade Update feature introduction flags 2023-08-01 14:25:25 -04:00
Jeremy Stretch
a89cec72a1 Update changelog 2023-08-01 14:13:48 -04:00
Abhimanyu Saharan
1cc78be6ca Adds custom field on webhook model (#13336)
* adds custom field on webhook model #11936

* adds tags on webhook model #11936

* Remove extraneous import; revert change to NetBoxModelForm (no longer needed)

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-01 14:05:47 -04:00
Jeremy Stretch
7b998cfeb4 #11732: Exclude _init_time from import form fields list 2023-08-01 11:53:35 -04:00
Abhimanyu Saharan
cbf4b43b35 Adds tags on contact assignment (#13328)
* adds tags on contact assignments #12882

* updated migration

* added tags on import form

* adds TagsMixin on ContactAssignmentType #12882

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-08-01 11:52:14 -04:00
Jeremy Stretch
c1ca8d5d8d Closes #12906: Make boto3 & dulwich libraries optional (#13324)
* Initial work on #12906

* Catch import errors during backend init

* Tweak error message

* Update requirements & add note to docs
2023-08-01 11:13:35 -04:00
Jeremy Stretch
43e6308d90 Closes #11732: Protect against errant overwriting of data via web UI forms 2023-08-01 09:06:51 -04:00
Arthur Hanson
e625a5667c Closes #13279: Wrap choice labels with gettext()
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-31 17:31:07 -04:00
Arthur Hanson
e284cd7e54 Closes #13150: Wrap table column headers with gettext()
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-31 14:35:28 -04:00
Jeremy Stretch
34a960505d Remove obsolete AdminGroup and AdminUser models (#12589) 2023-07-31 14:28:50 -04:00
Arthur Hanson
b7a9649269 Closes #13149: Wrap form field labels with gettext_lazy()
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-31 12:52:38 -04:00
Arthur Hanson
83bebc1bd2 Closes #13132: Wrap verbose_name and other model text with gettext_lazy() (i18n)
---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-31 11:28:07 -04:00
Jeremy Stretch
80376abedf Closes #13309: Introduce the account app (#13310)
* Introduce 'accounts' app for user-specific views & resources
* Move UserTokenTable to account app
* Move login & logout views to account app
2023-07-31 09:22:04 -04:00
Jeremy Stretch
9c6c3d3dd4 Update changelog 2023-07-31 08:35:28 -04:00
Jeremy Stretch
ab0442bd5c Fix typo 2023-07-31 08:24:03 -04:00
Abhimanyu Saharan
36f95f7842 Adds tenant on power feed (#13300)
* adds tenant on power feed

* cleanup

* adds power feed count on tenant object view

* Misc cleanup; add filterset tests

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-31 08:20:48 -04:00
Jeremy Stretch
07f68ae579 Closes #13038: Establish DEFAULT_PERMISSIONS config parameter (#13308)
* Introduce the DEFAULT_PERMISSIONS config parameter

* Establish default permissions for user token management
2023-07-30 15:04:58 -04:00
Jeremy Stretch
ca634be7ad Closes #13311: Always use get_permission_for_model() to resolve permission names 2023-07-30 14:32:02 -04:00
Jeremy Stretch
2a0d76d564 Merge branch 'develop' into feature 2023-07-30 13:36:51 -04:00
Jeremy Stretch
0b10131564 Satisfy PEP8 E721 linter complaints 2023-07-30 13:34:08 -04:00
Arthur Hanson
7c17d2e932 Closes #13102: Establish initial translation support in templates
---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-28 16:30:25 -04:00
Jeremy Stretch
cf1b1a83eb Closes #12194: Add pre-defined custom field choices (#13219)
* Initial work on custom field choice sets

* Rename choices to extra_choices (prep for #12194)

* Remove CustomField.choices

* Add & update tests

* Clean up table columns

* Add order_alphanetically boolean for choice sets

* Introduce ArrayColumn for choice lists

* Show dependent custom fields on choice set view

* Update custom fields documentation

* Introduce ArrayWidget for more convenient editing of choices

* Incorporate PR feedback

* Misc cleanup

* Initial work on predefined choices for custom fields

* Misc cleanup

* Add IATA airport codes

* #13241: Add support for custom field choice labels

* Restore ArrayColumn

* Misc cleanup

* Change extra_choices back to a nested ArrayField to preserve choice ordering

* Hack to bypass GraphQL API test utility absent support for nested ArrayFields
2023-07-28 11:24:21 -04:00
Jeremy Stretch
9d3bb585a2 Update version 2023-07-28 10:36:44 -04:00
Jeremy Stretch
d52c18ce38 Merge branch 'develop' into feature 2023-07-28 10:36:09 -04:00
Jeremy Stretch
006c353d46 PRVB 2023-07-28 10:31:54 -04:00
Jeremy Stretch
6c53ca8909 Merge pull request #13294 from netbox-community/develop
Release v3.5.7
2023-07-28 10:29:46 -04:00
Jeremy Stretch
4f984c0831 Release v3.5.7 2023-07-28 10:11:16 -04:00
Jeremy Stretch
d9dc6cec3a Changelog for #11803, #13009, #13234, #13285 2023-07-28 10:02:42 -04:00
Jeremy Stretch
90146941b5 Fixes #13285: Cast default u_height value to a decimal for validation 2023-07-28 09:49:09 -04:00
Bruno Blanes
9d0457fe1a Add Brazilian power outlet standard to choices.py (#13012)
* Add Brazilian power outlet standard to choices.py

* Eliminate possible name conflict

* Rename group and add IEC 60906-1 plug type

* Update choices.py

Add Brazilian power port standard
2023-07-28 09:26:46 -04:00
Abhimanyu Saharan
2aa51d0d94 Adds contact assignment bulk import (#13109)
* adds contact assignment bulk import #11307

* Remove unsupported tags field added by NetBoxModelImportForm

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-28 09:23:22 -04:00
Abhimanyu Saharan
7158360dfa moves non-racked devices to tab #11803 2023-07-28 08:59:15 -04:00
Jeremy Stretch
c89193d331 Closes #13080: Differentiate more clearly between old and new version placeholders in upgrade guide 2023-07-28 08:11:28 -04:00
Daniel W. Anner
eeb069048f Adding 100gbase-x-dsfp and 100gbase-x-sfpdd (#13236)
* Adding 100gbase-x-dsfp

* fixing missing comma

* Adding interface `TYPE_100GE_SFP_DD`/`100gbase-x-sfpdd`

* Update netbox/dcim/choices.py

Co-authored-by: Jeremy Stretch <jstretch@ns1.com>

---------

Co-authored-by: Jeremy Stretch <jstretch@ns1.com>
2023-07-27 19:02:08 -04:00
Jeremy Stretch
3e12fbe367 Changelog for #12625, #13051, #13097, #13167, #13233, #13237 2023-07-27 16:42:03 -04:00
kkthxbye-code
4b2922312a Allow the align property on th and td and add CSS rules for overriding text-alignment 2023-07-27 16:38:46 -04:00
Abhimanyu Saharan
0276f29067 adds sensitive_parameters to DataBackend #12625 2023-07-27 16:33:29 -04:00
Roger Miret
1d52627f71 Update ipam.md
100.64.16.9/24 isn't a valid CIDR
2023-07-27 16:07:44 -04:00
Alef Burzmali
bba4fe437c Update the install doc for PostgreSQL 15
Fixes #12768
2023-07-27 16:06:41 -04:00
Abhimanyu Saharan
0ab3f979e0 Adds faster polling for scripts and reports (#13202)
* adds faster polling for scripts and reports #13097

* changes as per review
2023-07-27 15:59:41 -04:00
kkthxbye-code
5a3d46ac8d Remove vlan_group from nullable fields in InterfaceBulkEditForm 2023-07-27 15:58:16 -04:00
Fabian Geisberger
d075e7a66a Fixes #13237 - Allow unauthenticated api access to content-types. 2023-07-27 15:47:34 -04:00
kkthxbye-code
8b8adfbbbb Use class_name instead of name to get script results 2023-07-27 15:32:29 -04:00
Jeremy Stretch
0c2e3ff898 Merge pull request #13277 from netbox-community/13272-fix-graphql-test
13272 fix graphql test
2023-07-27 13:09:33 -04:00
Arthur
83c092f685 13272 fix graphql tests 2023-07-27 14:25:49 +07:00
Abhimanyu Saharan
0f9fe96192 Adds rf_role to interface template (#13199)
* adds rf_role to interface template #13170

* fixed migration file conflict

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-26 09:13:24 -04:00
Jeremy Stretch
1bcfcad9db Update changelog 2023-07-25 16:48:41 -04:00
Jeremy Stretch
5b5444f414 Closes #13269: Cache component template counts on device types 2023-07-25 16:38:05 -04:00
Jeremy Stretch
daa8f71bb6 Closes #10197: Add a cached counter field for virtual chassis members 2023-07-25 15:50:12 -04:00
Jeremy Stretch
9b6e32896d Clean up users & account URLs 2023-07-25 15:48:40 -04:00
Jamie (Bear) Murphy
154b8236a2 Oob ip (devices) (#13013)
* initial oob_ip support for devices

* add primary ip and oob ip checkmark to ip address view

* add oob ip to device view and device edit view

* pep8

* make is_oob_ip and is_primary_ip generic for other models

* refactor oob_ip

* fix oob ip signal

* string capitalisation

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-25 14:40:40 -04:00
Arthur Hanson
7600d7b344 Closes #13228: Move token management views to primary UI 2023-07-25 13:43:40 -04:00
Arthur Hanson
149a496011 6347 Cache the number of each component type assigned to devices/VMs (#12632)
---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-25 09:39:05 -04:00
Arthur Hanson
a4acb50edd 12589 move user and group admin from admin (#12877)
Move admin views for users, groups, and object permissions from the admin site to the NetBox frontend

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-20 16:22:08 -04:00
Jeremy Stretch
96ea0ac9c7 Closes #12988: Introduce custom field choice sets (#13195)
* Initial work on custom field choice sets

* Rename choices to extra_choices (prep for #12194)

* Remove CustomField.choices

* Add & update tests

* Clean up table columns

* Add order_alphanetically boolean for choice sets

* Introduce ArrayColumn for choice lists

* Show dependent custom fields on choice set view

* Update custom fields documentation

* Introduce ArrayWidget for more convenient editing of choices

* Incorporate PR feedback

* Misc cleanup
2023-07-19 10:26:24 -04:00
Jeremy Stretch
837be4d45f Merge branch 'develop' into feature 2023-07-11 10:09:26 -04:00
Jeremy Stretch
0f0cf683c4 PRVB 2023-07-10 16:55:17 -04:00
Jeremy Stretch
ec0dbe33d3 Merge pull request #13142 from netbox-community/develop
Release v3.5.6
2023-07-10 16:53:46 -04:00
Jeremy Stretch
1c30a44b4e Release v3.5.6 2023-07-10 16:35:53 -04:00
Jeremy Stretch
252cc37f97 Changelog for #13061, #13096, #13105, #13116 2023-07-10 14:39:40 -04:00
Jeremy Stretch
f6fcf776a4 Fixes #13061: Fix display of last result for scripts & reports with a custom name defined 2023-07-10 14:13:45 -04:00
Jeremy Stretch
73348ee435 Fixes #13105: Avoid exception when attempting to allocate next available IP address from prefix marked as utilized 2023-07-10 13:53:31 -04:00
Abhimanyu Saharan
cab7b76220 Fixes form rendering when scheduling_enabled is disabled (#13123)
* fixes form rendering when scheduling_enabled is disabled #13096

* Remove requires_input property from BaseScript; render form consistently

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-10 10:30:51 -04:00
Abhimanyu Saharan
bc7678c716 fixes content type lookups when db is uninitialized #13116 2023-07-07 09:43:33 -04:00
Jeremy Stretch
63c33ff4be PRVB 2023-07-06 16:40:11 -04:00
Jeremy Stretch
da239aea13 Merge pull request #13111 from netbox-community/develop
Release v3.5.5
2023-07-06 16:38:36 -04:00
Jeremy Stretch
53a75a3dd7 Release v3.5.5 2023-07-06 16:20:14 -04:00
Abhimanyu Saharan
74fb707ad3 adds config_template to device serializer #13056 2023-07-06 16:04:21 -04:00
Jeremy Stretch
ecb4a084cc Change log for #11738, #12499, #12579, #12617, #13047, #13065, #13092, #13100 2023-07-06 14:54:37 -04:00
Jeremy Stretch
7419a8e112 Closes #11738: Annotate utilization on VLAN groups (#13108)
* Update serializers.py

* Update vlans.py

* Update vlans.py

* Update vlangroup.html

* Update vlans.py

* Update vlans.py

* Update serializers.py

* adds db annotation to calculate utilization

* optimize queries

* merge fix

* adds round function for utilization to limit decimal

* fixed object view annotation

* consolidated queryset for utilization

* lint fixes

* Renamed manager method to annotate_utilization() for consistency with other managers

---------

Co-authored-by: Abhimanyu Saharan <desk.abhimanyu@gmail.com>
2023-07-06 14:51:28 -04:00
Abhimanyu Saharan
62bdb90f61 Adds copy content button (#12584)
* adds copy content button #12499

* adds newline

* Omit hash mark from target string

* Clean up HTML element IDs

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-07-06 14:19:55 -04:00
Abhimanyu Saharan
8143c6e03b adds object change for contact assignment #13065 2023-07-06 09:45:31 -04:00
Abhimanyu Saharan
ffe4558ec5 fixes search for vdc #13100 2023-07-06 09:41:43 -04:00
Abhimanyu Saharan
16ee42ac38 fixes prechange snapshot #12617 2023-07-06 09:39:09 -04:00
Anthony Brissonnet
860be780ad Fix #12579 create cable and add another error (#13007)
* fix create cable and add another error #12579

* fix return proper parent object field

* improve code and wokflow

---------

Co-authored-by: netopsab <abrisson@cerbere.rp.sig.u-bourgogne.fr>
2023-07-06 09:28:45 -04:00
Jeremy Stretch
5f0922713f Fixes #13047: Add annotate_asn_count() to ASNRange manager 2023-07-06 08:45:02 -04:00
Arthur
4355ee6407 12092 allow setnull for bulk edit power port maximum and allocated draw 2023-07-06 08:37:54 -04:00
Jeremy Stretch
07ae7c8a6e Changelog for #11335, #12760, #12842, #12951, #12955 2023-07-05 11:43:53 -04:00
Jeremy Stretch
63ba9fb38c Fixes #11335: Default manager for ObjectChange should filter by installed apps (#11709)
* Fixes #11335: Default manager for ObjectChange should filter by installed apps

* Employ canonical model discovery mechanism

* Move filtering logic to valid_models() queryset method

* fixed import to avoid content type does not exist

* Cleanup

---------

Co-authored-by: Abhimanyu Saharan <desk.abhimanyu@gmail.com>
2023-07-05 11:39:35 -04:00
Abhimanyu Saharan
3307bd200c Fixes syntax error on reports (#12997)
* fixes syntax error on reports #12842

* remove the extra filter #12842
2023-06-29 16:02:39 -04:00
Daniel Sheppard
f69d99ea67 Fixes #12760 - Adds Vary header to cause cache to be keyed based on URL and the HX-Request header (or lack thereof) 2023-06-29 15:53:57 -04:00
Arthur
3754e00ee0 12809 document not to use underscores in model names 2023-06-29 15:49:47 -04:00
pobradovic08
dd6d9bf6e3 List multiple devices in Terminations (#13030) 2023-06-29 15:48:00 -04:00
Abhimanyu Saharan
183c7deb81 adds contact accessor fields on assignment table #12955 2023-06-29 14:50:30 -04:00
Jeremy Stretch
0a60a3fd2a Add OneMind Services as a sponsor 2023-06-29 14:47:10 -04:00
Jeremy Stretch
6e222f8dce Closes #8248: User bookmarks (#13035)
* Initial work on #8248

* Add tests

* Fix tests

* Add feature query for bookmarks

* Add BookmarksWidget

* Correct generic relation name

* Add docs for bookmarks

* Remove inheritance from ChangeLoggedModel
2023-06-29 14:36:11 -04:00
Jeremy Stretch
b13f9d27d9 Fix Repography charts 2023-06-27 10:19:12 -04:00
Jeremy Stretch
6b01b1df40 Changelog for #12849, #12945, #12961, #12977, #12983, #13011 2023-06-27 08:39:51 -04:00
Harm Geerts
34d32374a8 Fix #12983 bulk edit of M2M fields when nothing is selected
Partially revert 41c92483a0 to restore
bulk edit with m2m fields. The m2m cleaned_data yields a empty queryset
when nothing is selected. By setting the m2m relation unless set null is
checked even when nothing is selected the m2m relation is always
cleared.

This commit only sets the m2m relation when a selection is made.
2023-06-27 08:28:19 -04:00
rmanyari
c99e565426 fix remote auth backend check 2023-06-27 08:09:50 -04:00
pobradovic08
16d5107b71 Add QSFP-DD (100GE) interface type 2023-06-27 08:02:46 -04:00
Alexander Votteler
f1858a7c23 Fixes #13011: Allow comma in custom links 2023-06-27 08:00:33 -04:00
Jeremy Stretch
290ffd408a Fixes #12977: Fix URL parameters for object count dashboard widgets (#12991)
* Fixes #12977: Introduce dict_to_querydict() to ensure proper handling of QueryDicts

* Remove unused import
2023-06-26 14:21:26 -04:00
Jeremy Stretch
74d9fe1ea2 Fixes #12961: Set correct return_url for object contacts tabs 2023-06-26 12:50:54 -04:00
Jeremy Stretch
d131d9b310 Correct changelog for #12953 2023-06-23 15:31:18 -04:00
Jeremy Stretch
32fe9fe8ec Changelog for #12533, #12960, #12963, #12966, #12975, #12989 2023-06-23 15:26:48 -04:00
Jeremy Stretch
882f29192c Fixes #12975: Correct URL for VirtualDeviceContext API serializer 2023-06-23 15:23:40 -04:00
Abhimanyu Saharan
27e850a68d fix ip address assignment #12953 2023-06-23 14:39:16 -04:00
Abhimanyu Saharan
c83b2499f0 fix db maintenance mode exception #12966 2023-06-23 14:36:50 -04:00
Abhimanyu Saharan
79c8219202 fixes device interface serializer #12533 2023-06-23 14:34:08 -04:00
Abhimanyu Saharan
49af70a77d fixes choice field schema for response #12960 2023-06-23 14:21:51 -04:00
Abhimanyu Saharan
7f96c7fee7 fixes most schema warnings #12257 2023-06-23 14:19:40 -04:00
Abhimanyu Saharan
13315f36d4 fixes device type and module type tag import #12949 2023-06-23 14:17:28 -04:00
Jeremy Stretch
1056e513b1 Closes #11541: Support for limiting tag assignments by object type (#12982)
* Initial work on #11541

* Merge migrations

* Limit tags by object type during assignment

* Add tests for object type validation

* Fix form field parameters
2023-06-23 14:08:14 -04:00
Arthur Hanson
69b818ed33 12237 update to Django 4.2 / psycopg3 (#12916)
* 12237 upgrade django and psycopg

* 12237 add migration

* 12237 rename migration

* 12237 update requirements

* 12237 fix migration

* Update base requirements

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-06-23 10:38:08 -04:00
Arthur Hanson
148278a74a 12591 config params admin (#12904)
* 12591 initial commit

* 12591 detail view

* 12591 add/edit view

* 12591 edit button

* 12591 base views and forms

* 12591 form cleanup

* 12591 form cleanup

* 12591 form cleanup

* 12591 review changes

* 12591 move check for restrictedqueryset

* 12591 restore view

* 12591 restore page styling

* 12591 remove admin

* Remove edit view for ConfigRevision instances

* Order ConfigRevisions by creation time

* Correct permission name

* Use RestrictedQuerySet for ConfigRevision

* Fix redirect URL

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-06-22 14:04:24 -04:00
Jeremy Stretch
48b2ab3587 Closes #12964: Raise minimum PostgreSQL version from 11 to 12 2023-06-22 12:27:21 -04:00
Jeremy Stretch
9fa1411d74 Changelog for #9077, #11305, #12175, #12180, #12794 2023-06-22 10:55:12 -04:00
Arthur Hanson
eff4a3741c 12175 rack with starting unit > 1 (#12778)
* 12175 add rack starting unit

* 12175 rack starting unit to svg

* verify devices can still fit if change rack starting_unit

* 12175 fix migration

* 12175 fix typo and test

* 12175 fix test

* 12175 fix max height calc display

* Misc cleanup & fixes

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-06-22 09:09:01 -04:00
Arthur Hanson
518fd8cca6 12794 change User ref to get_user_model (#12905)
* 12794 change User ref to get_user_model

* 12794 call get_user_model once in tests

* 12794 call get_user_model once in tests

* 12794 use settings.AUTH_USER_MODEL for FK reference
2023-06-22 08:26:50 -04:00
Jeremy Stretch
bace24b68e 12180 available objects api (#12935)
* Introduce AvailableObjectsView and refactor 'available objects' API views

* Restore advisory PostgreSQL locks

* Move get_next_available_prefix()

* Apply OpenAPI decorators for get() and post()
2023-06-20 15:04:10 -04:00
Jeremy Stretch
e7edccd9ba Merge branch 'develop' into feature 2023-06-20 14:53:07 -04:00
Jeremy Stretch
70c2b358ad PRVB 2023-06-20 14:27:18 -04:00
Jeremy Stretch
9dab3a0d79 Merge pull request #12946 from netbox-community/develop
Release v3.5.4
2023-06-20 14:24:34 -04:00
Jeremy Stretch
54622b5f92 Release v3.5.4 2023-06-20 13:56:09 -04:00
Jeremy Stretch
cdce500d90 Changelog for #12474, #12828, #12845, #12865, #12885, #12914 2023-06-15 16:15:15 -04:00
Luke Anderson
e11991c7a4 Fix #12865 - Include Add Nav Buttons for Report and Script Objects (#12909) 2023-06-15 15:04:08 -04:00
Jeremy Stretch
6ef333ea68 Fixes #12885: Permit mounting of devices in U100 (#12901)
* Fixes #12885: Permit mounting of devices in U100

* Define a RACK_U_HEIGHT_MAX constant
2023-06-15 15:00:45 -04:00
Jeremy Stretch
7fc69f3945 Fixes #12914: Clear stored ordering from user config when cleared by request 2023-06-15 14:59:52 -04:00
Jeremy Stretch
8aeb31751a Fixes #12845: Fix pagination of related IP addresses table 2023-06-15 13:07:51 -04:00
Arthur
0b2162569f 12474 update cable terminations when moving location between sites 2023-06-15 08:53:06 -04:00
Abhimanyu Saharan
93175888f0 add color to ChangeActionChoices #12828 2023-06-15 08:48:36 -04:00
Arthur
e635f0defd Merge branch 'develop' into feature 2023-06-14 16:32:56 -07:00
Arthur
b4a3156046 9077 audit alters_data=True 2023-06-14 14:23:55 -04:00
Arthur Hanson
4f76dcd2ea 11305 Add GPS coordinates to device (#12782)
* 11305 add lat/long to devices

* 11305 update docs

* 11305 update tests
2023-06-14 14:18:50 -04:00
Jeremy Stretch
4d686e8162 Changelog for #12622, #12682, #12818, #12822, #12847 2023-06-14 13:54:40 -04:00
Dillon Henschen
0e873a01b8 Closes #12622: Fix assigning VLAN without site to Prefix (#12784)
* Issue #12622: Fix creating Prefix using VLAN without site

* Issue #12622: Fix importing Prefix using VLAN without site

This commit also adds tests to verify the import changes implemented
in this commit.

* Issue #12622: Cleanup code to filter allowed VLANs on a prefix import

* Closes #12622: Switch to VLAN selector dialog when creating Prefix
2023-06-14 13:49:00 -04:00
Jeremy Stretch
f7b0e48a09 Merge pull request #12864 from sudheesh001/fix/12847-include-adds
Fixes #12847 - Include Missing Add buttons to Views
2023-06-14 13:33:18 -04:00
Sudheesh Singanamalla
c5f71c0c19 Fixes #12847 - Include Missing Add buttons to Views
Signed-off-by: Sudheesh Singanamalla <sudheesh@cs.washington.edu>
2023-06-14 10:05:43 -07:00
Jeremy Stretch
36e0bf0490 Merge pull request #12893 from netbox-community/feat/12824-doc
Fixes typo in register_model_view docstring
2023-06-14 08:39:28 -04:00
Jeremy Stretch
28b939c001 Merge pull request #12894 from netbox-community/fix/12822-link-encode
Change link parsing from quote_plus to quote
2023-06-14 08:38:11 -04:00
Jeremy Stretch
55e31ef984 Merge pull request #12896 from netbox-community/fix/12818-perm
Fix permission
2023-06-14 07:57:32 -04:00
Jeremy Stretch
85e351146d Merge pull request #12897 from netbox-community/fix/12682-openapi-connected-device
Fix connected device api schema
2023-06-14 07:56:10 -04:00
Abhimanyu Saharan
d03bfe89c0 fix connected device api schema #12682 2023-06-14 15:45:07 +05:30
Abhimanyu Saharan
c8cbced55e fix permission #12818 2023-06-14 14:43:18 +05:30
Abhimanyu Saharan
928a34674e change link parsing from quote_plus to quote #12822 2023-06-14 14:16:04 +05:30
Abhimanyu Saharan
96cf95d176 fixes typo in register_model_view docstring #12824 2023-06-14 14:06:23 +05:30
jeremystretch
2e9586523f Changelog for #12687, #12838, #12850, #12862 2023-06-13 15:47:40 -04:00
Jeremy Stretch
a81924ac0f Merge pull request #12863 from sudheesh001/fix/12862-connection-sidebar-add
Fixes #12862 - Add Button for Wireless Links in Sidebar
2023-06-13 09:19:32 -04:00
Jeremy Stretch
74c1f7a176 Merge pull request #12874 from ITJamie/broadcast_exceptions
assigning broadcast ip error fixes for ipv6 and /31/32
2023-06-13 08:57:32 -04:00
Jamie Murphy
22a0ce3f76 broadcast error fixes for ipv6 and /31/32 2023-06-12 21:01:43 +01:00
Jeremy Stretch
43235f143d Merge pull request #12839 from candlerb/candlerb/12838
Round rack power utilization to nearest 0.1%
2023-06-12 08:12:17 -04:00
Jeremy Stretch
e7851399c6 Merge pull request #12857 from netbox-community/fix/12850-contacts-table
fix contact assignment table modal
2023-06-12 08:09:17 -04:00
Sudheesh Singanamalla
82cd6c5f4c Fixes #12862 - Add Button for Wireless Links in Sidebar
Signed-off-by: Sudheesh Singanamalla <sudheesh@cs.washington.edu>
2023-06-11 12:17:32 -07:00
Abhimanyu Saharan
210879d380 fix contact assignment table modal 2023-06-11 16:50:48 +05:30
Brian Candler
01d9e0afb6 Round rack power utilization to nearest 0.1%
Fixes #12838
2023-06-08 14:31:46 +01:00
jeremystretch
2e2ff09822 Merge branch 'develop' into feature 2023-06-02 15:43:06 -04:00
jeremystretch
4a88d5e3d9 PRVB 2023-06-02 15:42:34 -04:00
Jeremy Stretch
9fb52be85c Merge pull request #12805 from netbox-community/develop
Release v3.5.3
2023-06-02 15:37:20 -04:00
jeremystretch
46d1d5a44a Release v3.5.3 2023-06-02 14:17:01 -04:00
jeremystretch
dee4aec62d Fixes #12779: Correct arithmetic for converting inches to meters 2023-06-01 13:21:15 -04:00
jeremystretch
9f70407c7d Remove survey link 2023-06-01 13:00:09 -04:00
jeremystretch
852026bf7b Changelog for #7503, #9876, #12015, #12538, #12762 2023-05-31 16:35:06 -04:00
Abhimanyu Saharan
e7f689bc52 Fixes incorrectly handled type error when list of objects is found in data (#12593)
* fixes incorrectly handled type error when list of objects is found in data #9876

* fixes incorrectly handled type error when list of objects is found in data #9876

* fixes incorrectly handled type error when list of objects is found in data #9876
2023-05-31 15:44:59 -04:00
Daniel Sheppard
1349a25e34 Update missing changelog 2023-05-31 14:30:40 -05:00
Abhimanyu Saharan
dbd3c6de24 Fixes return_url for image attachment (#12721)
* fixes return_url for image attachment #12538

* simplified conditions

* handle nonetype error

* fixed request check

* Introduce htmx_table template tag for embedding HTMX-backed object tables

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-31 15:22:37 -04:00
Arthur
3e77daff01 12767 pin graphene-django version 2023-05-31 12:04:34 -04:00
Abhimanyu Saharan
bd88ee7063 Adds device type and role to device component filter (#12504)
* adds device type and role to device component filter #12015

* changes as per review

* Add filterset tests for device type & role filters

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-31 09:59:22 -04:00
Daniel Sheppard
a9b0b49ef9 Fixes #12702 - Adds widget to FrontPortTemplateCreateForm 2023-05-31 08:49:23 -05:00
Arthur Hanson
8b051ea2f3 7503 do device validate-create in serial (#12222)
* 7503 do device validate-create in serial

* 7503 fix single instance

* 7503 atomic transaction

* 7503 fix return data for bulk operations

* 7503 add test

* Move sequential creation logic to a mixin

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-31 09:06:09 -04:00
jeremystretch
bca9d0fa8a Closes #12599: Apply filter parameters to links in object count dashboard widgets 2023-05-30 16:31:34 -04:00
jeremystretch
9b8ab1c1f7 Fixes #12742: Object counts dashboard widget should support URL-compatible query filters 2023-05-30 15:44:43 -04:00
jeremystretch
b3bd03a1e9 Fixes #12715: Use contact assignments table to display the contacts assigned to an object 2023-05-30 14:51:16 -04:00
jeremystretch
18c863e393 Changelog for #11539, #12370, #12470, #12562, #12597, #12627, #12745 2023-05-30 09:52:14 -04:00
Abhimanyu Saharan
d7ca453f26 Adds hide-if-unset to custom field (#12723)
* adds hide-if-unset to custom field #12597

* moved hide logic from template to python

* fix indentation

* Update logic for omit_hidden under get_custom_fields()

* Update docs

* Account for False values

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-30 09:42:37 -04:00
Abhimanyu Saharan
9b9a559e0c Adds image preview back on the table (#12739)
* adds image preview on image attachment #12627

* adds bootstrap initialization for hx-trigger=load #12627

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-30 09:41:32 -04:00
kkthxbye-code
1f71d3570a Escape text passed as display values to slim-select 2023-05-30 09:09:15 -04:00
Abhimanyu Saharan
5a5fcf7d37 Changes render config card with accordian (#12724)
* changes render config card with accordian #12470

* fixed indentation #12470

* Use -flush CSS class to reduce whitespace

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
2023-05-26 09:32:58 -04:00
Abhimanyu Saharan
5869894a48 Adds ip to failed logs (#12725)
* adds ip to failed logs #12562

* added additional logging when client ip cannot be determined
2023-05-26 08:46:49 -04:00
Abhimanyu Saharan
e2f9a3c07a fixes contact assignments filter to include parent content type #12730 2023-05-26 08:42:04 -04:00
jeremystretch
b64b19a3f4 Fixes #11934: Prevent reassignment of an IP address designated as primary for its parent object 2023-05-25 16:42:24 -04:00
Jeremy Stretch
24a51dd86e Fixes #11539: Use BooleanFilter for 'empty' lookups (#11784)
* Use BooleanFilter for 'empty' lookups

* Always use BooleanFilter for 'empty' lookups

* Restore Empty lookup logic
2023-05-25 15:20:08 -04:00
jeremystretch
bf1c191b2e Fixes #12694: Strip leading & trailing whitespace from custom link URL & text 2023-05-24 15:45:24 -04:00
jeremystretch
b31b086a4d Link to the plugin ideas board 2023-05-23 15:34:03 -04:00
jeremystretch
6160e03426 PRVB 2023-05-22 17:00:29 -04:00
jeremystretch
4208b79514 Closes #12320: Remove obsolete fields napalm_driver and napalm_args from Platform 2023-05-16 09:35:27 -04:00
jeremystretch
02db0bcc2e Closes #11766: Remove obsolete custom ChoiceField and MultipleChoiceField classes 2023-05-12 16:27:50 -04:00
681 changed files with 139187 additions and 8756 deletions

View File

@@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v3.5.2
placeholder: v3.6.2
validations:
required: true
- type: dropdown

View File

@@ -3,10 +3,13 @@ blank_issues_enabled: false
contact_links:
- name: 📖 Contributing Policy
url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md
about: "Please read through our contributing policy before opening an issue or pull request"
about: "Please read through our contributing policy before opening an issue or pull request."
- name: ❓ Discussion
url: https://github.com/netbox-community/netbox/discussions
about: "If you're just looking for help, try starting a discussion instead"
about: "If you're just looking for help, try starting a discussion instead."
- name: 💡 Plugin Idea
url: https://plugin-ideas.netbox.dev
about: "Have an idea for a plugin? Head over to the ideas board!"
- name: 💬 Community Slack
url: https://netdev.chat/
about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems"
url: https://netdev.chat
about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems."

View File

@@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v3.5.2
placeholder: v3.6.2
validations:
required: true
- type: dropdown

View File

@@ -14,12 +14,25 @@
</div>
<h3></h3>
Some general tips for engaging here on GitHub:
## :information_source: Welcome to the Stadium!
In her book [Working in Public](https://www.amazon.com/Working-Public-Making-Maintenance-Software/dp/0578675862), Nadia Eghbal defines four production models for open source projects, categorized by contributor and user growth: federations, clubs, toys, and stadiums. The NetBox project fits her definition of a stadium very well:
> Stadiums are projects with low contributor growth and high user growth. While they may receive casual contributions, their regular contributor base does not grow proportionately to their users. As a result, they tend to be powered by one or a few developers.
The bulk of NetBox's development is carried out by a handful of core maintainers, with occasional contributions from collaborators in the community. We find the stadium analogy very useful in conveying the roles and obligations of both contributors and users.
If you're a contributor, actively working on the center stage, you have an obligation to produce quality content that will benefit the project as a whole. Conversely, if you're in the audience consuming the work being produced, you have the option of making requests and suggestions, but must also recognize that contributors are under no obligation to act on them.
NetBox users are welcome to participate in either role, on stage or in the crowd. We ask only that you acknowledge the role you've chosen and respect the roles of others.
### General Tips for Working on GitHub
* Register for a free [GitHub account](https://github.com/signup) if you haven't already.
* You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images.
* To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.)
* Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue.
* Familiarize yourself with [this list of discussion anti-patterns](https://github.com/bradfitz/issue-tracker-behaviors) and make every effort to avoid them.
## :bug: Reporting Bugs

View File

@@ -1,9 +1,6 @@
<div align="center">
<strong>The :ballot_box_with_check: <a href="https://forms.gle/zUHrrPo7K34yKaqC9">2023 NetBox Community Survey</a> is now open!</strong>
<p>Please take a few minutes to tell us about your NetBox deployment.</p>
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
<p>The premiere source of truth powering network automation</p>
<p>The premier source of truth powering network automation</p>
<img src="https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master" alt="CI status" />
<p></p>
</div>
@@ -55,10 +52,10 @@ as the cornerstone for network automation in thousands of organizations.
## Project Stats
<div align="center">
<a href="https://github.com/netbox-community/netbox/commits"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_timeline.svg" alt="Timeline graph"></a>
<a href="https://github.com/netbox-community/netbox/issues"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_issues.svg" alt="Issues graph"></a>
<a href="https://github.com/netbox-community/netbox/pulls"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_prs.svg" alt="Pull requests graph"></a>
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_users.svg" alt="Top contributors"></a>
<a href="https://github.com/netbox-community/netbox/commits"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/whQtEr_TGD9PhW1BPlhlEQ5jnrgQ0KJpm-LlGtpoGO0/3Kx_iWUSBRJ5-AI4QwJEJWrUDEz3KrX2lvh8aYE0WXY_timeline.svg" alt="Timeline graph"></a>
<a href="https://github.com/netbox-community/netbox/issues"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/whQtEr_TGD9PhW1BPlhlEQ5jnrgQ0KJpm-LlGtpoGO0/3Kx_iWUSBRJ5-AI4QwJEJWrUDEz3KrX2lvh8aYE0WXY_issues.svg" alt="Issues graph"></a>
<a href="https://github.com/netbox-community/netbox/pulls"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/whQtEr_TGD9PhW1BPlhlEQ5jnrgQ0KJpm-LlGtpoGO0/3Kx_iWUSBRJ5-AI4QwJEJWrUDEz3KrX2lvh8aYE0WXY_prs.svg" alt="Pull requests graph"></a>
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/whQtEr_TGD9PhW1BPlhlEQ5jnrgQ0KJpm-LlGtpoGO0/3Kx_iWUSBRJ5-AI4QwJEJWrUDEz3KrX2lvh8aYE0WXY_users.svg" alt="Top contributors"></a>
<br />Stats via <a href="https://repography.com">Repography</a>
</div>
@@ -69,10 +66,12 @@ as the cornerstone for network automation in thousands of organizations.
[![NetBox Labs](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/netbox_labs.png)](https://netboxlabs.com)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
[![DigitalOcean](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/digitalocean.png)](https://try.digitalocean.com/developer-cloud)
<br />
[![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
[![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)
<br />
[![Equinix Metal](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/equinix.png)](https://metal.equinix.com)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
[![OneMind Services](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/onemind_services.png)](https://onemindservices.com)
</div>

View File

@@ -2,13 +2,9 @@
# https://github.com/mozilla/bleach/blob/main/CHANGES
bleach
# Python client for Amazon AWS API
# https://github.com/boto/boto3/blob/develop/CHANGELOG.rst
boto3
# The Python web framework on which NetBox is built
# https://docs.djangoproject.com/en/stable/releases/
Django<4.2
Django<5.0
# Django middleware which permits cross-domain API requests
# https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst
@@ -74,17 +70,14 @@ drf-spectacular
# https://github.com/tfranzel/drf-spectacular-sidecar
drf-spectacular-sidecar
# Git client for file sync
# https://github.com/jelmer/dulwich/releases
dulwich
# RSS feed parser
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
feedparser
# Django wrapper for Graphene (GraphQL support)
# https://github.com/graphql-python/graphene-django/releases
graphene_django
# Pinned to v3.0.0 for GraphiQL UI issue (see #12762)
graphene_django==3.0.0
# WSGI HTTP server
# https://docs.gunicorn.org/en/latest/news.html
@@ -120,8 +113,8 @@ netaddr
Pillow
# PostgreSQL database adapter for Python
# https://www.psycopg.org/docs/news.html
psycopg2-binary
# https://github.com/psycopg/psycopg/blob/master/docs/news.rst
psycopg[binary,pool]
# YAML rendering library
# https://github.com/yaml/pyyaml/blob/master/CHANGES

View File

@@ -0,0 +1,564 @@
{
"type": "object",
"additionalProperties": false,
"definitions": {
"airflow": {
"type": "string",
"enum": [
"front-to-rear",
"rear-to-front",
"left-to-right",
"right-to-left",
"side-to-rear",
"passive",
"mixed"
]
},
"weight-unit": {
"type": "string",
"enum": [
"kg",
"g",
"lb",
"oz"
]
},
"subdevice-role": {
"type": "string",
"enum": [
"parent",
"child"
]
},
"console-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"de-9",
"db-25",
"rj-11",
"rj-12",
"rj-45",
"mini-din-8",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"other"
]
}
}
},
"console-server-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"de-9",
"db-25",
"rj-11",
"rj-12",
"rj-45",
"mini-din-8",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"other"
]
}
}
},
"power-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"iec-60320-c6",
"iec-60320-c8",
"iec-60320-c14",
"iec-60320-c16",
"iec-60320-c20",
"iec-60320-c22",
"iec-60309-p-n-e-4h",
"iec-60309-p-n-e-6h",
"iec-60309-p-n-e-9h",
"iec-60309-2p-e-4h",
"iec-60309-2p-e-6h",
"iec-60309-2p-e-9h",
"iec-60309-3p-e-4h",
"iec-60309-3p-e-6h",
"iec-60309-3p-e-9h",
"iec-60309-3p-n-e-4h",
"iec-60309-3p-n-e-6h",
"iec-60309-3p-n-e-9h",
"iec-60906-1",
"nbr-14136-10a",
"nbr-14136-20a",
"nema-1-15p",
"nema-5-15p",
"nema-5-20p",
"nema-5-30p",
"nema-5-50p",
"nema-6-15p",
"nema-6-20p",
"nema-6-30p",
"nema-6-50p",
"nema-10-30p",
"nema-10-50p",
"nema-14-20p",
"nema-14-30p",
"nema-14-50p",
"nema-14-60p",
"nema-15-15p",
"nema-15-20p",
"nema-15-30p",
"nema-15-50p",
"nema-15-60p",
"nema-l1-15p",
"nema-l5-15p",
"nema-l5-20p",
"nema-l5-30p",
"nema-l5-50p",
"nema-l6-15p",
"nema-l6-20p",
"nema-l6-30p",
"nema-l6-50p",
"nema-l10-30p",
"nema-l14-20p",
"nema-l14-30p",
"nema-l14-50p",
"nema-l14-60p",
"nema-l15-20p",
"nema-l15-30p",
"nema-l15-50p",
"nema-l15-60p",
"nema-l21-20p",
"nema-l21-30p",
"nema-l22-30p",
"cs6361c",
"cs6365c",
"cs8165c",
"cs8265c",
"cs8365c",
"cs8465c",
"ita-c",
"ita-e",
"ita-f",
"ita-ef",
"ita-g",
"ita-h",
"ita-i",
"ita-j",
"ita-k",
"ita-l",
"ita-m",
"ita-n",
"ita-o",
"usb-a",
"usb-b",
"usb-c",
"usb-mini-a",
"usb-mini-b",
"usb-micro-a",
"usb-micro-b",
"usb-micro-ab",
"usb-3-b",
"usb-3-micro-b",
"dc-terminal",
"saf-d-grid",
"neutrik-powercon-20",
"neutrik-powercon-32",
"neutrik-powercon-true1",
"neutrik-powercon-true1-top",
"ubiquiti-smartpower",
"hardwired",
"other"
]
}
}
},
"power-outlet": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"iec-60320-c5",
"iec-60320-c7",
"iec-60320-c13",
"iec-60320-c15",
"iec-60320-c19",
"iec-60320-c21",
"iec-60309-p-n-e-4h",
"iec-60309-p-n-e-6h",
"iec-60309-p-n-e-9h",
"iec-60309-2p-e-4h",
"iec-60309-2p-e-6h",
"iec-60309-2p-e-9h",
"iec-60309-3p-e-4h",
"iec-60309-3p-e-6h",
"iec-60309-3p-e-9h",
"iec-60309-3p-n-e-4h",
"iec-60309-3p-n-e-6h",
"iec-60309-3p-n-e-9h",
"iec-60906-1",
"nbr-14136-10a",
"nbr-14136-20a",
"nema-1-15r",
"nema-5-15r",
"nema-5-20r",
"nema-5-30r",
"nema-5-50r",
"nema-6-15r",
"nema-6-20r",
"nema-6-30r",
"nema-6-50r",
"nema-10-30r",
"nema-10-50r",
"nema-14-20r",
"nema-14-30r",
"nema-14-50r",
"nema-14-60r",
"nema-15-15r",
"nema-15-20r",
"nema-15-30r",
"nema-15-50r",
"nema-15-60r",
"nema-l1-15r",
"nema-l5-15r",
"nema-l5-20r",
"nema-l5-30r",
"nema-l5-50r",
"nema-l6-15r",
"nema-l6-20r",
"nema-l6-30r",
"nema-l6-50r",
"nema-l10-30r",
"nema-l14-20r",
"nema-l14-30r",
"nema-l14-50r",
"nema-l14-60r",
"nema-l15-20r",
"nema-l15-30r",
"nema-l15-50r",
"nema-l15-60r",
"nema-l21-20r",
"nema-l21-30r",
"nema-l22-30r",
"CS6360C",
"CS6364C",
"CS8164C",
"CS8264C",
"CS8364C",
"CS8464C",
"ita-e",
"ita-f",
"ita-g",
"ita-h",
"ita-i",
"ita-j",
"ita-k",
"ita-l",
"ita-m",
"ita-n",
"ita-o",
"ita-multistandard",
"usb-a",
"usb-micro-b",
"usb-c",
"dc-terminal",
"hdot-cx",
"saf-d-grid",
"neutrik-powercon-20a",
"neutrik-powercon-32a",
"neutrik-powercon-true1",
"neutrik-powercon-true1-top",
"ubiquiti-smartpower",
"hardwired",
"other"
]
},
"feed-leg": {
"type": "string",
"enum": [
"A",
"B",
"C"
]
}
}
},
"interface": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"virtual",
"bridge",
"lag",
"100base-fx",
"100base-lfx",
"100base-tx",
"100base-t1",
"1000base-t",
"2.5gbase-t",
"5gbase-t",
"10gbase-t",
"10gbase-cx4",
"1000base-x-gbic",
"1000base-x-sfp",
"10gbase-x-sfpp",
"10gbase-x-xfp",
"10gbase-x-xenpak",
"10gbase-x-x2",
"25gbase-x-sfp28",
"50gbase-x-sfp56",
"40gbase-x-qsfpp",
"50gbase-x-sfp28",
"100gbase-x-cfp",
"100gbase-x-cfp2",
"200gbase-x-cfp2",
"400gbase-x-cfp2",
"100gbase-x-cfp4",
"100gbase-x-cxp",
"100gbase-x-cpak",
"100gbase-x-dsfp",
"100gbase-x-sfpdd",
"100gbase-x-qsfp28",
"100gbase-x-qsfpdd",
"200gbase-x-qsfp56",
"200gbase-x-qsfpdd",
"400gbase-x-qsfp112",
"400gbase-x-qsfpdd",
"400gbase-x-osfp",
"400gbase-x-osfp-rhs",
"400gbase-x-cdfp",
"400gbase-x-cfp8",
"800gbase-x-qsfpdd",
"800gbase-x-osfp",
"1000base-kx",
"10gbase-kr",
"10gbase-kx4",
"25gbase-kr",
"40gbase-kr4",
"50gbase-kr",
"100gbase-kp4",
"100gbase-kr2",
"100gbase-kr4",
"ieee802.11a",
"ieee802.11g",
"ieee802.11n",
"ieee802.11ac",
"ieee802.11ad",
"ieee802.11ax",
"ieee802.11ay",
"ieee802.15.1",
"other-wireless",
"gsm",
"cdma",
"lte",
"sonet-oc3",
"sonet-oc12",
"sonet-oc48",
"sonet-oc192",
"sonet-oc768",
"sonet-oc1920",
"sonet-oc3840",
"1gfc-sfp",
"2gfc-sfp",
"4gfc-sfp",
"8gfc-sfpp",
"16gfc-sfpp",
"32gfc-sfp28",
"64gfc-qsfpp",
"128gfc-qsfp28",
"infiniband-sdr",
"infiniband-ddr",
"infiniband-qdr",
"infiniband-fdr10",
"infiniband-fdr",
"infiniband-edr",
"infiniband-hdr",
"infiniband-ndr",
"infiniband-xdr",
"t1",
"e1",
"t3",
"e3",
"xdsl",
"docsis",
"gpon",
"xg-pon",
"xgs-pon",
"ng-pon2",
"epon",
"10g-epon",
"cisco-stackwise",
"cisco-stackwise-plus",
"cisco-flexstack",
"cisco-flexstack-plus",
"cisco-stackwise-80",
"cisco-stackwise-160",
"cisco-stackwise-320",
"cisco-stackwise-480",
"cisco-stackwise-1t",
"juniper-vcp",
"extreme-summitstack",
"extreme-summitstack-128",
"extreme-summitstack-256",
"extreme-summitstack-512",
"other"
]
},
"poe_mode": {
"type": "string",
"enum": [
"pd",
"pse"
]
},
"poe_type": {
"type": "string",
"enum": [
"type1-ieee802.3af",
"type2-ieee802.3at",
"type3-ieee802.3bt",
"type4-ieee802.3bt",
"passive-24v-2pair",
"passive-24v-4pair",
"passive-48v-2pair",
"passive-48v-4pair"
]
}
}
},
"front-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"8p8c",
"8p6c",
"8p4c",
"8p2c",
"6p6c",
"6p4c",
"6p2c",
"4p4c",
"4p2c",
"gg45",
"tera-4p",
"tera-2p",
"tera-1p",
"110-punch",
"bnc",
"f",
"n",
"mrj21",
"fc",
"lc",
"lc-pc",
"lc-upc",
"lc-apc",
"lsh",
"lsh-pc",
"lsh-upc",
"lsh-apc",
"lx5",
"lx5-pc",
"lx5-upc",
"lx5-apc",
"mpo",
"mtrj",
"sc",
"sc-pc",
"sc-upc",
"sc-apc",
"st",
"cs",
"sn",
"sma-905",
"sma-906",
"urm-p2",
"urm-p4",
"urm-p8",
"splice",
"other"
]
}
}
},
"rear-port": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"8p8c",
"8p6c",
"8p4c",
"8p2c",
"6p6c",
"6p4c",
"6p2c",
"4p4c",
"4p2c",
"gg45",
"tera-4p",
"tera-2p",
"tera-1p",
"110-punch",
"bnc",
"f",
"n",
"mrj21",
"fc",
"lc",
"lc-pc",
"lc-upc",
"lc-apc",
"lsh",
"lsh-pc",
"lsh-upc",
"lsh-apc",
"lx5",
"lx5-pc",
"lx5-upc",
"lx5-apc",
"mpo",
"mtrj",
"sc",
"sc-pc",
"sc-upc",
"sc-apc",
"st",
"cs",
"sn",
"sma-905",
"sma-906",
"urm-p2",
"urm-p4",
"urm-p8",
"splice",
"other"
]
}
}
}
}
}

View File

@@ -68,8 +68,13 @@ When defining a permission constraint, administrators may use the special token
The `$user` token can be used only as a constraint value, or as an item within a list of values. It cannot be modified or extended to reference specific user attributes.
### Default Permissions
#### Example Constraint Definitions
!!! info "This feature was introduced in NetBox v3.6."
While permissions are typically assigned to specific groups and/or users, it is also possible to define a set of default permissions that are applied to _all_ authenticated users. This is done using the [`DEFAULT_PERMISSIONS`](../configuration/security.md#default_permissions) configuration parameter. Note that statically configuring permissions for specific users or groups is **not** supported.
### Example Constraint Definitions
| Constraints | Description |
| ----------- | ----------- |

View File

@@ -54,7 +54,7 @@ pg_dump --username netbox --password --host localhost -s netbox > netbox_schema.
By default, NetBox stores uploaded files (such as image attachments) in its media directory. To fully replicate an instance of NetBox, you'll need to copy both the database and the media files.
!!! note
These operations are not necessary if your installation is utilizing a [remote storage backend](../../configuration/optional-settings/#storage_backend).
These operations are not necessary if your installation is utilizing a [remote storage backend](../configuration/system.md#storage_backend).
### Archive the Media Directory

View File

@@ -20,7 +20,7 @@ DEFAULT_DASHBOARD = [
{
'widget': 'extras.ObjectCountsWidget',
'width': 4,
'height': 2,
'height': 3,
'title': 'Organization',
'config': {
'models': [
@@ -32,6 +32,8 @@ DEFAULT_DASHBOARD = [
},
{
'widget': 'extras.ObjectCountsWidget',
'width': 4,
'height': 3,
'title': 'IPAM',
'color': 'blue',
'config': {

View File

@@ -4,7 +4,7 @@
Default: Empty
A list of installed [NetBox plugins](../../plugins/) to enable. Plugins will not take effect unless they are listed here.
A list of installed [NetBox plugins](../plugins/index.md) to enable. Plugins will not take effect unless they are listed here.
!!! warning
Plugins extend NetBox by allowing external code to run with the same access and privileges as NetBox itself. Only install plugins from trusted sources. The NetBox maintainers make absolutely no guarantees about the integrity or security of your installation with plugins enabled.

View File

@@ -25,7 +25,7 @@ ALLOWED_HOSTS = ['*']
## DATABASE
NetBox requires access to a PostgreSQL 11 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
NetBox requires access to a PostgreSQL 12 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
* `NAME` - Database name
* `USER` - PostgreSQL username

View File

@@ -4,7 +4,7 @@
Default: True
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token immediately upon its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token prior to its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
---
@@ -90,6 +90,38 @@ CSRF_TRUSTED_ORIGINS = (
---
## DEFAULT_PERMISSIONS
!!! info "This parameter was introduced in NetBox v3.6."
Default:
```python
{
'users.view_token': ({'user': '$user'},),
'users.add_token': ({'user': '$user'},),
'users.change_token': ({'user': '$user'},),
'users.delete_token': ({'user': '$user'},),
}
```
This parameter defines object permissions that are applied automatically to _any_ authenticated user, regardless of what permissions have been defined in the database. By default, this parameter is defined to allow all users to manage their own API tokens, however it can be overriden for any purpose.
For example, to allow all users to create a device role beginning with the word "temp," you could configure the following:
```python
DEFAULT_PERMISSIONS = {
'dcim.add_devicerole': (
{'name__startswith': 'temp'},
)
}
```
!!! warning
Setting a custom value for this parameter will overwrite the default permission mapping shown above. If you want to retain the default mapping, be sure to reproduce it in your custom configuration.
---
## EXEMPT_VIEW_PERMISSIONS
Default: Empty list

View File

@@ -60,7 +60,7 @@ NetBox supports limited custom validation for custom field values. Following are
### Custom Selection Fields
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.
Each custom selection field must designate a [choice set](../models/extras/customfieldchoiceset.md) containing at least two choices. These are specified as a comma-separated list.
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.

View File

@@ -390,7 +390,7 @@ class NewBranchScript(Script):
name=f'{site.slug}-switch{i}',
site=site,
status=DeviceStatusChoices.STATUS_PLANNED,
device_role=switch_role
role=switch_role
)
switch.full_clean()
switch.save()

View File

@@ -111,7 +111,7 @@ The following methods are available to log results within a report:
The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status. Log messages also support using markdown syntax and will be rendered on the report result page.
To perform additional tasks, such as sending an email or calling a webhook, before or after a report is run, extend the `pre_run()` and/or `post_run()` methods, respectively. The status of a completed report is available as `self.failed` and the results object is `self.result`.
To perform additional tasks, such as sending an email or calling a webhook, before or after a report is run, extend the `pre_run()` and/or `post_run()` methods, respectively.
By default, reports within a module are ordered alphabetically in the reports list page. To return reports in a specific order, you can define the `report_order` variable at the end of your module. The `report_order` variable is a tuple which contains each Report class in the desired order. Any reports that are omitted from this list will be listed last.

View File

@@ -8,6 +8,10 @@ The registry can be inspected by importing `registry` from `extras.registry`.
## Stores
### `counter_fields`
A dictionary mapping of models to foreign keys with which cached counter fields are associated.
### `data_backends`
A dictionary mapping data backend types to their respective classes. These are used to interact with [remote data sources](../models/core/datasource.md).

View File

@@ -0,0 +1,123 @@
# Internationalization
Beginning with NetBox v4.0, NetBox will leverage [Django's automatic translation](https://docs.djangoproject.com/en/stable/topics/i18n/translation/) to support languages other than English. This page details the areas of the project which require special attention to ensure functioning translation support. Briefly, these include:
* The `verbose_name` and `verbose_name_plural` Meta attributes for each model
* The `verbose_name` and (if defined) `help_text` for each model field
* The `label` for each form field
* Headers for `fieldsets` on each form class
* The `verbose_name` for each table column
* All human-readable strings within templates must be wrapped with `{% trans %}` or `{% blocktrans %}`
The rest of this document elaborates on each of the items above.
## General Guidance
* Wrap human-readable strings with Django's `gettext()` or `gettext_lazy()` utility functions to enable automatic translation. Generally, `gettext_lazy()` is preferred (and sometimes required) to defer translation until the string is displayed.
* By convention, the preferred translation function is typically imported as an underscore (`_`) to minimize boilerplate code. Thus, you will often see translation as e.g. `_("Some text")`. It is still an option to import and use alternative translation functions (e.g. `pgettext()` and `ngettext()`) normally as needed.
* Avoid passing markup and other non-natural language where possible. Everything wrapped by a translation function gets exported to a messages file for translation by a human.
* Where the intended meaning of the translated string may not be obvious, use `pgettext()` or `pgettext_lazy()` to include assisting context for the translator. For example:
```python
# Context, string
pgettext("month name", "May")
```
* **Format strings do not support translation.** Avoid "f" strings for messages that must support translation. Instead, use `format()` to accomplish variable replacement:
```python
# Translation will not work
f"There are {count} objects"
# Do this instead
"There are {count} objects".format(count=count)
```
## Models
1. Import `gettext_lazy` as `_`.
2. Ensure both `verbose_name` and `verbose_name_plural` are defined under the model's `Meta` class and wrapped with the `gettext_lazy()` shortcut.
3. Ensure each model field specifies a `verbose_name` wrapped with `gettext_lazy()`.
4. Ensure any `help_text` attributes on model fields are also wrapped with `gettext_lazy()`.
```python
from django.utils.translation import gettext_lazy as _
class Circuit(PrimaryModel):
commit_rate = models.PositiveIntegerField(
...
verbose_name=_('commit rate (Kbps)'),
help_text=_("Committed rate")
)
class Meta:
verbose_name = _('circuit')
verbose_name_plural = _('circuits')
```
## Forms
1. Import `gettext_lazy` as `_`.
2. All form fields must specify a `label` wrapped with `gettext_lazy()`.
3. All headers under a form's `fieldsets` property must be wrapped with `gettext_lazy()`.
```python
from django.utils.translation import gettext_lazy as _
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
...
)
fieldsets = (
(_('Circuit'), ('provider', 'type', 'status', 'description')),
)
```
## Tables
1. Import `gettext_lazy` as `_`.
2. All table columns must specify a `verbose_name` wrapped with `gettext_lazy()`.
```python
from django.utils.translation import gettext_lazy as _
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
provider = tables.Column(
verbose_name=_('Provider'),
...
)
```
## Templates
1. Ensure translation support is enabled by including `{% load i18n %}` at the top of the template.
2. Use the [`{% trans %}`](https://docs.djangoproject.com/en/stable/topics/i18n/translation/#translate-template-tag) tag (short for "translate") to wrap short strings.
3. Longer strings may be enclosed between [`{% blocktrans %}`](https://docs.djangoproject.com/en/stable/topics/i18n/translation/#blocktranslate-template-tag) and `{% endblocktrans %}` tags to improve readability and to enable variable replacement. (Remember to include the `trimmed` argument to trim whitespace between the tags.)
4. Avoid passing HTML within translated strings where possible, as this can complicate the work needed of human translators to develop message maps.
```
{% load i18n %}
{# A short string #}
<h5 class="card-header">{% trans "Circuit List" %}</h5>
{# A longer string with a context variable #}
{% blocktrans trimmed with count=object.circuits.count %}
There are {count} circuits. Would you like to continue?
{% endblocktrans %}
```
!!! warning
The `{% blocktrans %}` tag supports only **limited variable replacement**, comparable to the `format()` method on Python strings. It does not permit access to object attributes or the use of other template tags or filters inside it. Ensure that any necessary context is passed as simple variables.
!!! info
The `{% trans %}` and `{% blocktrans %}` support the inclusion of contextual hints for translators using the `context` argument:
```nohighlight
{% trans "May" context "month name" %}
```

View File

@@ -43,10 +43,22 @@ Follow these instructions to perform a new installation of NetBox in a temporary
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below.
### Rebuild Demo Data (After Release)
After the release of a new minor version, generate a new demo data snapshot compatible with the new release. See the [`netbox-demo-data`](https://github.com/netbox-community/netbox-demo-data) repository for instructions.
---
## Patch Releases
### Notify netbox-docker Project of Any Relevant Changes
Notify the [`netbox-docker`](https://github.com/netbox-community/netbox-docker) maintainers (in **#netbox-docker**) of any changes that may be relevant to their build process, including:
* Significant changes to `upgrade.sh`
* Increases in minimum versions for service dependencies (PostgreSQL, Redis, etc.)
* Any changes to the reference installation
### Update Requirements
Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this:
@@ -58,6 +70,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec
In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above).
### Rebuild the Device Type Definition Schema
Run the following command to update the device type definition validation schema:
```nohighlight
./manage.py buildschema --write
```
This will automatically update the schema file at `contrib/generated_schema.json`.
### Update Version and Changelog
* Update the `VERSION` constant in `settings.py` to the new release version.

View File

@@ -1,7 +1,5 @@
# Configuration Rendering
!!! info "This feature was introduced in NetBox v3.5."
One of the critical aspects of operating a network is ensuring that every network node is configured correctly. By leveraging configuration templates and [context data](./context-data.md), NetBox can render complete configuration files for each device on your network.
```mermaid
@@ -39,6 +37,14 @@ Configuration templates are written in the [Jinja2 templating language](https://
When rendered for a specific NetBox device, the template's `device` variable will be populated with the device instance, and `ntp_servers` will be pulled from the device's available context data. The resulting output will be a valid configuration segment that can be applied directly to a compatible network device.
### Context Data
The objet for which the configuration is being rendered is made available as template context as `device` or `virtualmachine` for devices and virtual machines, respectively. Additionally, NetBox model classes can be accessed by the app or plugin in which they reside. For example:
```
There are {{ dcim.Site.objects.count() }} sites.
```
## Rendering Templates
### Device Configurations

View File

@@ -18,6 +18,12 @@ The `tag` filter can be specified multiple times to match only objects which hav
GET /api/dcim/devices/?tag=monitored&tag=deprecated
```
## Bookmarks
!!! info "This feature was introduced in NetBox v3.6."
Users can bookmark their most commonly visited objects for convenient access. Bookmarks are listed under a user's profile, and can be displayed with custom filtering and ordering on the user's personal dashboard.
## Custom Fields
While NetBox provides a rather extensive data model out of the box, the need may arise to store certain additional data associated with NetBox objects. For example, you might need to record the invoice ID alongside an installed device, or record an approving authority when creating a new IP prefix. NetBox administrators can create custom fields on built-in objects to meet these needs.

View File

@@ -38,7 +38,7 @@ An example hierarchy might look like this:
* 100.64.16.1/24 (address)
* 100.64.16.2/24 (address)
* 100.64.16.3/24 (address)
* 100.64.16.9/24 (prefix)
* 100.64.19.0/24 (prefix)
* 100.64.32.0/20 (prefix)
* 100.64.32.1/24 (address)
* 100.64.32.10-99/24 (range)

View File

@@ -1,7 +1,5 @@
# Synchronized Data
!!! info "This feature was introduced in NetBox v3.5."
Several models in NetBox support the automatic synchronization of local data from a designated remote source. For example, [configuration templates](./configuration-rendering.md) defined in NetBox can source their content from text files stored in a remote git repository. This accomplished using the core [data source](../models/core/datasource.md) and [data file](../models/core/datafile.md) models.
To enable remote data synchronization, the NetBox administrator first designates one or more remote data sources. NetBox currently supports the following source types:
@@ -12,6 +10,10 @@ To enable remote data synchronization, the NetBox administrator first designates
(Local disk paths are considered "remote" in this context as they exist outside NetBox's database. These paths could also be mapped to external network shares.)
!!! info
Data backends which connect to external sources typically require the installation of one or more supporting Python libraries. The Git backend requires the [`dulwich`](https://www.dulwich.io/) package, and the S3 backend requires the [`boto3`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) package. These must be installed within NetBox's environment to enable these backends.
Each type of remote source has its own configuration parameters. For instance, a git source will ask the user to specify a branch and authentication credentials. Once the source has been created, a synchronization job is run to automatically replicate remote files in the local database.
The following NetBox models can be associated with replicated data files:

View File

@@ -1,6 +1,6 @@
![NetBox](netbox_logo.svg "NetBox logo"){style="height: 100px; margin-bottom: 3em"}
# The Premiere Network Source of Truth
# The Premier Network Source of Truth
NetBox is the leading solution for modeling and documenting modern networks. By combining the traditional disciplines of IP address management (IPAM) and datacenter infrastructure management (DCIM) with powerful APIs and extensions, NetBox provides the ideal "source of truth" to power network automation. Read on to discover why thousands of organizations worldwide put NetBox at the heart of their infrastructure.

View File

@@ -2,8 +2,8 @@
This section entails the installation and configuration of a local PostgreSQL database. If you already have a PostgreSQL database service in place, skip to [the next section](2-redis.md).
!!! warning "PostgreSQL 11 or later required"
NetBox requires PostgreSQL 11 or later. Please note that MySQL and other relational databases are **not** supported.
!!! warning "PostgreSQL 12 or later required"
NetBox requires PostgreSQL 12 or later. Please note that MySQL and other relational databases are **not** supported.
## Installation
@@ -35,7 +35,7 @@ This section entails the installation and configuration of a local PostgreSQL da
sudo systemctl enable postgresql
```
Before continuing, verify that you have installed PostgreSQL 11 or later:
Before continuing, verify that you have installed PostgreSQL 12 or later:
```no-highlight
psql -V
@@ -55,6 +55,9 @@ Within the shell, enter the following commands to create the database and user (
CREATE DATABASE netbox;
CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
ALTER DATABASE netbox OWNER TO netbox;
-- the next two commands are needed on PostgreSQL 15 and later
\connect netbox;
GRANT CREATE ON SCHEMA public TO netbox;
```
!!! danger "Use a strong password"

View File

@@ -211,6 +211,22 @@ By default, NetBox will use the local filesystem to store uploaded files. To use
sudo sh -c "echo 'django-storages' >> /opt/netbox/local_requirements.txt"
```
### Remote Data Sources
NetBox supports integration with several remote data sources via configurable backends. Each of these requires the installation of one or more additional libraries.
* Amazon S3: [`boto3`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
* Git: [`dulwich`](https://www.dulwich.io/)
For example, to enable the Amazon S3 backend, add `boto3` to your local requirements file:
```no-highlight
sudo sh -c "echo 'boto3' >> /opt/netbox/local_requirements.txt"
```
!!! info
These packages were previously required in NetBox v3.5 but now are optional.
## Run the Upgrade Script
Once NetBox has been configured, we're ready to proceed with the actual installation. We'll run the packaged upgrade script (`upgrade.sh`) to perform the following actions:

View File

@@ -148,6 +148,126 @@ AUTH_LDAP_CACHE_TIMEOUT = 3600
!!! warning
Authentication will fail if the groups (the distinguished names) do not exist in the LDAP directory.
## Authenticating with Active Directory
Integrating Active Directory for authentication can be a bit challenging as it may require handling different login formats. This solution will allow users to log in either using their full User Principal Name (UPN) or their username alone, by filtering the DN according to either the `sAMAccountName` or the `userPrincipalName`. The following configuration options will allow your users to enter their usernames in the format `username` or `username@domain.tld`.
Just as before, the configuration options are defined in the file ldap_config.py. First, modify the `AUTH_LDAP_USER_SEARCH` option to match the following:
```python
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=Users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(|(userPrincipalName=%(user)s)(sAMAccountName=%(user)s))"
)
```
In addition, `AUTH_LDAP_USER_DN_TEMPLATE` should be set to `None` as described in the previous sections. Next, modify `AUTH_LDAP_USER_ATTR_MAP` to match the following:
```python
AUTH_LDAP_USER_ATTR_MAP = {
"username": "sAMAccountName",
"email": "mail",
"first_name": "givenName",
"last_name": "sn",
}
```
Finally, we need to add one more configuration option, `AUTH_LDAP_USER_QUERY_FIELD`. The following should be added to your LDAP configuration file:
```python
AUTH_LDAP_USER_QUERY_FIELD = "username"
```
With these configuration options, your users will be able to log in either with or without the UPN suffix.
### Example Configuration
!!! info
This configuration is intended to serve as a template, but may need to be modified in accordance with your environment.
```python
import ldap
from django_auth_ldap.config import LDAPSearch, NestedGroupOfNamesType
# Server URI
AUTH_LDAP_SERVER_URI = "ldaps://ad.example.com:3269"
# The following may be needed if you are binding to Active Directory.
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_REFERRALS: 0
}
# Set the DN and password for the NetBox service account.
AUTH_LDAP_BIND_DN = "CN=NETBOXSA,OU=Service Accounts,DC=example,DC=com"
AUTH_LDAP_BIND_PASSWORD = "demo"
# Include this setting if you want to ignore certificate errors. This might be needed to accept a self-signed cert.
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
LDAP_IGNORE_CERT_ERRORS = False
# Include this setting if you want to validate the LDAP server certificates against a CA certificate directory on your server
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, LDAP_CA_CERT_DIR)
LDAP_CA_CERT_DIR = '/etc/ssl/certs'
# Include this setting if you want to validate the LDAP server certificates against your own CA.
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT_FILE)
LDAP_CA_CERT_FILE = '/path/to/example-CA.crt'
# This search matches users with the sAMAccountName equal to the provided username. This is required if the user's
# username is not in their DN (Active Directory).
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=Users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(|(userPrincipalName=%(user)s)(sAMAccountName=%(user)s))"
)
# If a user's DN is producible from their username, we don't need to search.
AUTH_LDAP_USER_DN_TEMPLATE = None
# You can map user attributes to Django attributes as so.
AUTH_LDAP_USER_ATTR_MAP = {
"username": "sAMAccountName",
"email": "mail",
"first_name": "givenName",
"last_name": "sn",
}
AUTH_LDAP_USER_QUERY_FIELD = "username"
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
# hierarchy.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(objectClass=group)"
)
AUTH_LDAP_GROUP_TYPE = NestedGroupOfNamesType()
# Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = "CN=NETBOX_USERS,DC=example,DC=com"
# Mirror LDAP group assignments.
AUTH_LDAP_MIRROR_GROUPS = True
# Define special user types using groups. Exercise great caution when assigning superuser status.
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=groups,dc=example,dc=com",
"is_staff": "cn=staff,ou=groups,dc=example,dc=com",
"is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
}
# For more granular permissions, we can map LDAP groups to Django groups.
AUTH_LDAP_FIND_GROUP_PERMS = True
# Cache groups for one hour to reduce LDAP traffic
AUTH_LDAP_CACHE_TIMEOUT = 3600
AUTH_LDAP_ALWAYS_UPDATE_USER = True
```
## 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`.

View File

@@ -1,5 +1,8 @@
# Installation
!!! info "NetBox Cloud"
The instructions below are for installing NetBox as a standalone, self-hosted application. For a Cloud-delivered solution, check out [NetBox Cloud](https://netboxlabs.com/netbox-cloud/) by NetBox Labs.
The installation instructions provided here have been tested to work on Ubuntu 22.04 and CentOS 8.3. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors.
<iframe width="560" height="315" src="https://www.youtube.com/embed/_y5JRiW_PLM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
@@ -18,7 +21,7 @@ The following sections detail how to set up a new instance of NetBox:
| Dependency | Minimum Version |
|------------|-----------------|
| Python | 3.8 |
| PostgreSQL | 11 |
| PostgreSQL | 12 |
| Redis | 4.0 |
Below is a simplified overview of the NetBox application stack for reference:

View File

@@ -15,12 +15,12 @@ Prior to upgrading your NetBox instance, be sure to carefully review all [releas
## 2. Update Dependencies to Required Versions
NetBox v3.0 and later require the following:
NetBox requires the following dependencies:
| Dependency | Minimum Version |
|------------|-----------------|
| Python | 3.8 |
| PostgreSQL | 11 |
| PostgreSQL | 12 |
| Redis | 4.0 |
## 3. Install the Latest Release
@@ -48,36 +48,40 @@ Download the [latest stable release](https://github.com/netbox-community/netbox/
Download and extract the latest version:
```no-highlight
wget https://github.com/netbox-community/netbox/archive/vX.Y.Z.tar.gz
sudo tar -xzf vX.Y.Z.tar.gz -C /opt
sudo ln -sfn /opt/netbox-X.Y.Z/ /opt/netbox
# Set $NEWVER to the NetBox version being installed
NEWVER=3.5.0
wget https://github.com/netbox-community/netbox/archive/v$NEWVER.tar.gz
sudo tar -xzf v$NEWVER.tar.gz -C /opt
sudo ln -sfn /opt/netbox-$NEWVER/ /opt/netbox
```
Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if present) from the current installation to the new version:
```no-highlight
sudo cp /opt/netbox-X.Y.Z/local_requirements.txt /opt/netbox/
sudo cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
sudo cp /opt/netbox-X.Y.Z/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
# Set $OLDVER to the NetBox version currently installed
OLDVER=3.4.9
sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/
sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
```
Be sure to replicate your uploaded media as well. (The exact action necessary will depend on where you choose to store your media, but in general moving or copying the media directory will suffice.)
```no-highlight
sudo cp -pr /opt/netbox-X.Y.Z/netbox/media/ /opt/netbox/netbox/
sudo cp -pr /opt/netbox-$OLDVER/netbox/media/ /opt/netbox/netbox/
```
Also make sure to copy or link any custom scripts and reports that you've made. Note that if these are stored outside the project root, you will not need to copy them. (Check the `SCRIPTS_ROOT` and `REPORTS_ROOT` parameters in the configuration file above if you're unsure.)
```no-highlight
sudo cp -r /opt/netbox-X.Y.Z/netbox/scripts /opt/netbox/netbox/
sudo cp -r /opt/netbox-X.Y.Z/netbox/reports /opt/netbox/netbox/
sudo cp -r /opt/netbox-$OLDVER/netbox/scripts /opt/netbox/netbox/
sudo cp -r /opt/netbox-$OLDVER/netbox/reports /opt/netbox/netbox/
```
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
```no-highlight
sudo cp /opt/netbox-X.Y.Z/gunicorn.py /opt/netbox/
sudo cp /opt/netbox-$OLDVER/gunicorn.py /opt/netbox/
```
### Option B: Clone the Git Repository

View File

@@ -570,27 +570,26 @@ The NetBox REST API primarily employs token-based authentication. For convenienc
A token is a unique identifier mapped to a NetBox user account. Each user may have one or more tokens which he or she can use for authentication when making REST API requests. To create a token, navigate to the API tokens page under your user profile.
!!! note
All users can create and manage REST API tokens under the user control panel in the UI. The ability to view, add, change, or delete tokens via the REST API itself is controlled by the relevant model permissions, assigned to users and/or groups in the admin UI. These permissions should be used with great care to avoid accidentally permitting a user to create tokens for other user accounts.
By default, all users can create and manage their own REST API tokens under the user control panel in the UI or via the REST API. This ability can be disabled by overriding the [`DEFAULT_PERMISSIONS`](../configuration/security.md#default_permissions) configuration parameter.
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
By default, a token can be used to perform all actions via the API that a user would be permitted to do via the web UI. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
Additionally, a token can be set to expire at a specific time. This can be useful if an external client needs to be granted temporary access to NetBox.
!!! warning "Restricting Token Retrieval"
!!! info "Restricting Token Retrieval"
The ability to retrieve the key value of a previously-created API token can be restricted by disabling the [`ALLOW_TOKEN_RETRIEVAL`](../configuration/security.md#allow_token_retrieval) configuration parameter.
### Restricting Write Operations
By default, a token can be used to perform all actions via the API that a user would be permitted to do via the web UI. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
#### Client IP Restriction
Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.)
#### Creating Tokens for Other Users
It is possible to provision authentication tokens for other users via the REST API. To do, so the requesting user must have the `users.grant_token` permission assigned. While all users have inherent permission to create their own tokens, this permission is required to enable the creation of tokens for other users.
![Adding the grant action to a permission](../media/admin_ui_grant_permission.png)
It is possible to provision authentication tokens for other users via the REST API. To do, so the requesting user must have the `users.grant_token` permission assigned. While all users have inherent permission by default to create their own tokens, this permission is required to enable the creation of tokens for other users.
!!! warning "Exercise Caution"
The ability to create tokens on behalf of other users enables the requestor to access the created token. This ability is intended e.g. for the provisioning of tokens by automated services, and should be used with extreme caution to avoid a security compromise.
@@ -627,7 +626,7 @@ When a token is used to authenticate a request, its `last_updated` time updated
### Initial Token Provisioning
Ideally, each user should provision his or her own REST API token(s) via the web UI. However, you may encounter where a token must be created by a user via the REST API itself. NetBox provides a special endpoint to provision tokens using a valid username and password combination.
Ideally, each user should provision his or her own API token(s) via the web UI. However, you may encounter a scenario where a token must be created by a user via the REST API itself. NetBox provides a special endpoint to provision tokens using a valid username and password combination. (Note that the user must have permission to create API tokens regardless of the interface used.)
To provision a token via the REST API, make a `POST` request to the `/api/users/tokens/provision/` endpoint:
@@ -671,8 +670,6 @@ This header specifies the API version in use. This will always match the version
### `X-Request-ID`
!!! info "This feature was introduced in NetBox v3.5."
This header specifies the unique ID assigned to the received API request. It can be very handy for correlating a request with change records. For example, after creating several new objects, you can filter against the object changes API endpoint to retrieve the resulting change records:
```

View File

@@ -75,5 +75,5 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
| HTTP service | nginx or Apache |
| WSGI service | gunicorn or uWSGI |
| Application | Django/Python |
| Database | PostgreSQL 11+ |
| Database | PostgreSQL 12+ |
| Task queuing | Redis/django-rq |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,7 +1,5 @@
# Provider Accounts
!!! info "This model was introduced in NetBox v3.5."
This model can be used to represent individual accounts associated with a provider.
## Fields

View File

@@ -61,6 +61,10 @@ If installed in a rack, this field indicates the base rack unit in which the dev
!!! tip
Devices with a height of more than one rack unit should be set to the lowest-numbered rack unit that they occupy.
### Latitude & Longitude
GPS coordinates of the device for geolocation.
### Status
The device's operational status.
@@ -83,6 +87,10 @@ Each device may designate one primary IPv4 address and/or one primary IPv6 addre
!!! tip
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
### Out-of-band (OOB) IP Address
Each device may designate its out-of-band IP address. Out-of-band IPs are typically used to access network infrastructure via a physically separate management network.
### Cluster
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)

View File

@@ -23,17 +23,3 @@ If designated, this platform will be available for use only to devices assigned
### Configuration Template
The default [configuration template](../extras/configtemplate.md) for devices assigned to this platform.
### NAPALM Driver
!!! warning "Deprecated Field"
NAPALM integration was removed from NetBox core in v3.5 and is now available as a [plugin](https://github.com/netbox-community/netbox-napalm). This field will be removed in NetBox v3.6.
The [NAPALM driver](https://napalm.readthedocs.io/en/latest/support/index.html) associated with this platform.
### NAPALM Arguments
!!! warning "Deprecated Field"
NAPALM integration was removed from NetBox core in v3.5 and is now available as a [plugin](https://github.com/netbox-community/netbox-napalm). This field will be removed in NetBox v3.6.
Any additional arguments to send when invoking the NAPALM driver assigned to this platform.

View File

@@ -61,6 +61,10 @@ The canonical distance between the two vertical rails on a face. (This is typica
The height of the rack, measured in units.
### Starting Unit
The number of the numerically lowest unit in the rack. This value defaults to one, but may be higher in certain situations. For example, you may want to model only a select range of units within a shared physical rack (e.g. U13 through U24).
### Outer Dimensions
The external width and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.

View File

@@ -0,0 +1,15 @@
# Bookmarks
!!! info "This feature was introduced in NetBox v3.6."
A user can bookmark individual objects for convenient access. Bookmarks are listed under a user's profile and can be displayed using a dashboard widget.
## Fields
### User
The user to whom the bookmark belongs.
### Object
The bookmarked object.

View File

@@ -68,19 +68,20 @@ Defines how filters are evaluated against custom field values.
Controls how and whether the custom field is displayed within the NetBox user interface.
| Option | Description |
|------------|--------------------------------------|
| Read/write | Display and permit editing (default) |
| Read-only | Display field but disallow editing |
| Hidden | Do not display field in the UI |
| Option | Description |
|-------------------|--------------------------------------------------|
| Read/write | Display and permit editing (default) |
| Read-only | Display field but disallow editing |
| Hidden | Do not display field in the UI |
| Hidden (if unset) | Display in the UI only when a value has been set |
### Default
The default value to populate for the custom field when creating new objects (optional). This value must be expressed as JSON. If this is a choice or multi-choice field, this must be one of the available choices.
### Choices
### Choice Set
For choice and multi-choice custom fields only. A comma-delimited list of the available choices.
For selection and multi-select custom fields only, this is the [set of choices](./customfieldchoiceset.md) which are valid for the field.
### Cloneable

View File

@@ -0,0 +1,29 @@
# Custom Field Choice Sets
!!! info "This feature was introduced in NetBox v3.6."
Single- and multi-selection [custom fields](../../customization/custom-fields.md) must define a set of valid choices from which the user may choose when defining the field value. These choices are defined as sets that may be reused among multiple custom fields.
A choice set must define a base choice set and/or a set of arbitrary extra choices.
## Fields
### Name
The human-friendly name of the choice set.
### Base Choices
The set of pre-defined choices to include. Available sets are listed below. This is an optional setting.
* IATA airport codes
* ISO 3166 - Two-letter country codes
* UN/LOCODE - Five-character location identifiers
### Extra Choices
A set of custom choices that will be appended to the base choice set (if any).
### Order Alphabetically
If enabled, the choices list will be automatically ordered alphabetically. If disabled, choices will appear in the order in which they were defined.

View File

@@ -15,3 +15,11 @@ A unique URL-friendly identifier. (This value will be used for filtering.) This
### Color
The color to use when displaying the tag in the NetBox UI.
### Object Types
!!! info "This feature was introduced in NetBox v3.6."
The assignment of a tag may be limited to a prescribed set of objects. For example, it may be desirable to limit the application of a specific tag to only devices and virtual machines.
If no object types are specified, the tag will be assignable to any type of object.

View File

@@ -1,7 +1,5 @@
# ASN Ranges
!!! info "This model was introduced in NetBox v3.5."
Ranges can be defined to group [AS numbers](./asn.md) numerically and to facilitate their automatic provisioning. Each range must be assigned to a [RIR](./rir.md).
## Fields

View File

@@ -1,7 +1,5 @@
# Dashboard Widgets
!!! info "This feature was introduced in NetBox v3.5."
Each NetBox user can customize his or her personal dashboard by adding and removing widgets and by manipulating the size and position of each. Plugins can register their own dashboard widgets to complement those already available natively.
## The DashboardWidget Class

View File

@@ -165,19 +165,6 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
options:
members: false
## Choice Fields
!!! warning "Obsolete Fields"
NetBox's custom `ChoiceField` and `MultipleChoiceField` classes are no longer necessary thanks to improvements made to the user interface. Django's native form fields can be used instead. These custom field classes will be removed in NetBox v3.6.
::: utilities.forms.fields.ChoiceField
options:
members: false
::: utilities.forms.fields.MultipleChoiceField
options:
members: false
## Dynamic Object Fields
::: utilities.forms.fields.DynamicModelChoiceField

View File

@@ -19,11 +19,16 @@ class MyModel(models.Model):
Every model includes by default a numeric primary key. This value is generated automatically by the database, and can be referenced as `pk` or `id`.
!!! note
Model names should adhere to [PEP8](https://www.python.org/dev/peps/pep-0008/#class-names) standards and be CapWords (no underscores). Using underscores in model names will result in problems with permissions.
## Enabling NetBox Features
Plugin models can leverage certain NetBox features by inheriting from NetBox's `NetBoxModel` class. This class extends the plugin model to enable features unique to NetBox, including:
* Bookmarks
* Change logging
* Cloning
* Custom fields
* Custom links
* Custom validation
@@ -102,6 +107,8 @@ For more information about database migrations, see the [Django documentation](h
!!! warning
Please note that only the classes which appear in this documentation are currently supported. Although other classes may be present within the `features` module, they are not yet supported for use by plugins.
::: netbox.models.features.BookmarksMixin
::: netbox.models.features.ChangeLoggingMixin
::: netbox.models.features.CloningMixin

View File

@@ -64,12 +64,15 @@ item1 = PluginMenuItem(
A `PluginMenuItem` has the following attributes:
| Attribute | Required | Description |
|---------------|----------|------------------------------------------------------|
| `link` | Yes | Name of the URL path to which this menu item links |
| `link_text` | Yes | The text presented to the user |
| `permissions` | - | A list of permissions required to display this link |
| `buttons` | - | An iterable of PluginMenuButton instances to include |
| Attribute | Required | Description |
|---------------|----------|----------------------------------------------------------------------------------------------------------|
| `link` | Yes | Name of the URL path to which this menu item links |
| `link_text` | Yes | The text presented to the user |
| `permissions` | - | A list of permissions required to display this link |
| `staff_only` | - | Display only for users who have `is_staff` set to true (any specified permissions will also be required) |
| `buttons` | - | An iterable of PluginMenuButton instances to include |
!!! info "The `staff_only` attribute was introduced in NetBox v3.6.1."
## Menu Buttons

View File

@@ -61,13 +61,14 @@ These lookup expressions can be applied by adding a suffix to the desired field'
Numeric based fields (ASN, VLAN ID, etc) support these lookup expressions:
| Filter | Description |
|--------|-------------|
| `n` | Not equal to |
| `lt` | Less than |
| `lte` | Less than or equal to |
| `gt` | Greater than |
| `gte` | Greater than or equal to |
| Filter | Description |
|---------|--------------------------|
| `n` | Not equal to |
| `lt` | Less than |
| `lte` | Less than or equal to |
| `gt` | Greater than |
| `gte` | Greater than or equal to |
| `empty` | Is empty/null (boolean) |
Here is an example of a numeric field lookup expression that will return all VLANs with a VLAN ID greater than 900:
@@ -79,18 +80,18 @@ GET /api/ipam/vlans/?vid__gt=900
String based (char) fields (Name, Address, etc) support these lookup expressions:
| Filter | Description |
|--------|-------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty (boolean) |
| Filter | Description |
|---------|----------------------------------------|
| `n` | Not equal to |
| `ic` | Contains (case-insensitive) |
| `nic` | Does not contain (case-insensitive) |
| `isw` | Starts with (case-insensitive) |
| `nisw` | Does not start with (case-insensitive) |
| `iew` | Ends with (case-insensitive) |
| `niew` | Does not end with (case-insensitive) |
| `ie` | Exact match (case-insensitive) |
| `nie` | Inverse exact match (case-insensitive) |
| `empty` | Is empty/null (boolean) |
Here is an example of a lookup expression on a string field that will return all devices with `switch` in the name:

View File

@@ -10,6 +10,15 @@ Minor releases are published in April, August, and December of each calendar yea
This page contains a history of all major and minor releases since NetBox v2.0. For more detail on a specific patch release, please see the release notes page for that specific minor release.
#### [Version 3.6](./version-3.6.md) (August 2023)
* Relocated Admin UI Views ([#12589](https://github.com/netbox-community/netbox/issues/12589), [#12590](https://github.com/netbox-community/netbox/issues/12590), [#12591](https://github.com/netbox-community/netbox/issues/12591), [#13044](https://github.com/netbox-community/netbox/issues/13044))
* Configurable Default Permissions ([#13038](https://github.com/netbox-community/netbox/issues/13038))
* User Bookmarks ([#8248](https://github.com/netbox-community/netbox/issues/8248))
* Custom Field Choice Sets ([#12988](https://github.com/netbox-community/netbox/issues/12988))
* Pre-Defined Location Choices for Custom Fields ([#12194](https://github.com/netbox-community/netbox/issues/12194))
* Restrict Tag Usage by Object Type ([#11541](https://github.com/netbox-community/netbox/issues/11541))
#### [Version 3.5](./version-3.5.md) (April 2023)
* Customizable Dashboard ([#9416](https://github.com/netbox-community/netbox/issues/9416))

View File

@@ -1,5 +1,183 @@
# NetBox v3.5
## v3.5.9 (2023-08-28)
### Enhancements
* [#12489](https://github.com/netbox-community/netbox/issues/12489) - Dynamically render location and device lists under site and location views
* [#12825](https://github.com/netbox-community/netbox/issues/12825) - Display assigned values count per obejct type under custom field view
* [#13313](https://github.com/netbox-community/netbox/issues/13313) - Enable filtering IP ranges by containing prefix
* [#13415](https://github.com/netbox-community/netbox/issues/13415) - Include request object in custom link renderer on tables
* [#13536](https://github.com/netbox-community/netbox/issues/13536) - Move child VLANs list to a separate tab under VLAN group view
* [#13542](https://github.com/netbox-community/netbox/issues/13542) - Pass additional HTTP headers through to custom script context
* [#13585](https://github.com/netbox-community/netbox/issues/13585) - Introduce `empty` lookup for numeric value filters
### Bug Fixes
* [#11272](https://github.com/netbox-community/netbox/issues/11272) - Fix localization support for device position field
* [#13358](https://github.com/netbox-community/netbox/issues/13358) - Git backend should send HTTP auth headers only if credentials have been defined
* [#13477](https://github.com/netbox-community/netbox/issues/13477) - Fix filtering of modified objects after bulk import/update
* [#13478](https://github.com/netbox-community/netbox/issues/13478) - Fix filtering of export templates by content type under web UI
* [#13500](https://github.com/netbox-community/netbox/issues/13500) - Fix form validation for bulk update of L2VPN terminations via bulk import form
* [#13503](https://github.com/netbox-community/netbox/issues/13503) - Fix utilization graph proportions when localization is enabled
* [#13507](https://github.com/netbox-community/netbox/issues/13507) - Avoid raising exception for invalid content type during global search
* [#13516](https://github.com/netbox-community/netbox/issues/13516) - Plugin utility functions should be importable from `extras.plugins`
* [#13530](https://github.com/netbox-community/netbox/issues/13530) - Ensure script log messages can be serialized as JSON data
* [#13543](https://github.com/netbox-community/netbox/issues/13543) - Config context tab under device/VM view should not require `extras.view_configcontext` permission
* [#13544](https://github.com/netbox-community/netbox/issues/13544) - Ensure `reindex` command clears all cached values when not in lazy mode
* [#13556](https://github.com/netbox-community/netbox/issues/13556) - Correct REST API representation of VDC status choice
* [#13569](https://github.com/netbox-community/netbox/issues/13569) - Fix selection widgets for related interfaces when bulk editing interfaces under device view
---
## v3.5.8 (2023-08-15)
### Enhancements
* [#10030](https://github.com/netbox-community/netbox/issues/10030) - Ship a validation schema for the device type library with each release
* [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import
* [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI
* [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type
* [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table
* [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses
* [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page
* [#13442](https://github.com/netbox-community/netbox/issues/13442) - Add 200 and 400 Gbps speeds to dropdown choices on interface form
### Bug Fixes
* [#11578](https://github.com/netbox-community/netbox/issues/11578) - Fix schema definition for available IP & VLAN REST API endpoints
* [#12639](https://github.com/netbox-community/netbox/issues/12639) - Raise validation error for invalid alphanumeric ranges when creating objects
* [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links
* [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted
* [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view
* [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports
* [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms
* [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox
* [#13451](https://github.com/netbox-community/netbox/issues/13451) - Disable table ordering for custom link columns
---
## v3.5.7 (2023-07-28)
### Enhancements
* [#11803](https://github.com/netbox-community/netbox/issues/11803) - Move non-rack devices list to a separate tab under the rack view
* [#12625](https://github.com/netbox-community/netbox/issues/12625) - Mask sensitive parameters when viewing a configured data source
* [#13009](https://github.com/netbox-community/netbox/issues/13009) - Add IEC 10609-1 and NBR 14136 power port & outlet types
* [#13097](https://github.com/netbox-community/netbox/issues/13097) - Implement a faster initial poll for report & script results
* [#13234](https://github.com/netbox-community/netbox/issues/13234) - Add 100GBASE-X-DSFP and 100GBASE-X-SFPDD interface types
### Bug Fixes
* [#13051](https://github.com/netbox-community/netbox/issues/13051) - Fix Markdown support for table cell alignment
* [#13167](https://github.com/netbox-community/netbox/issues/13167) - Fix missing script results when fetched via REST API
* [#13233](https://github.com/netbox-community/netbox/issues/13233) - Remove extraneous VLAN group field from bulk edit form for interfaces
* [#13237](https://github.com/netbox-community/netbox/issues/13237) - Permit unauthenticated access to content types REST API endpoint when `LOGIN_REQUIRED` is false
* [#13285](https://github.com/netbox-community/netbox/issues/13285) - Fix exception when importing device type missing rack unit height value
---
## v3.5.6 (2023-07-10)
### Bug Fixes
* [#13061](https://github.com/netbox-community/netbox/issues/13061) - Fix display of last result for scripts & reports with a custom name defined
* [#13096](https://github.com/netbox-community/netbox/issues/13096) - Hide scheduling fields for all scripts with scheduling disabled
* [#13105](https://github.com/netbox-community/netbox/issues/13105) - Fix exception when attempting to allocate next available IP address from prefix marked as utilized
* [#13116](https://github.com/netbox-community/netbox/issues/13116) - Catch ProgrammingError exception when starting NetBox without pre-populated content types
---
## v3.5.5 (2023-07-06)
### Enhancements
* [#11738](https://github.com/netbox-community/netbox/issues/11738) - Annotate VLAN group utilization
* [#12499](https://github.com/netbox-community/netbox/issues/12499) - Add "copy to clipboard" buttons in UI for IP addresses
* [#12945](https://github.com/netbox-community/netbox/issues/12945) - Add 100GE QSFP-DD interface type
* [#12955](https://github.com/netbox-community/netbox/issues/12955) - Include additional contact details on contact assignments table
* [#13065](https://github.com/netbox-community/netbox/issues/13065) - Associate contact assignments with their objects in the change log
### Bug Fixes
* [#11335](https://github.com/netbox-community/netbox/issues/11335) - Exclude stale content types when retrieving changelog records
* [#12533](https://github.com/netbox-community/netbox/issues/12533) - Fix REST API validation of null values for several interface attributes
* [#12579](https://github.com/netbox-community/netbox/issues/12579) - Fix exception when clicking "create and add another" to add a cable
* [#12617](https://github.com/netbox-community/netbox/issues/12617) - Populate prechange snapshot on parent object when assigning/removing primary IP address
* [#12760](https://github.com/netbox-community/netbox/issues/12760) - Avoid rendering partial HTMX responses when restoring browser tabs
* [#12842](https://github.com/netbox-community/netbox/issues/12842) - Improve handling of exceptions when loading reports
* [#12849](https://github.com/netbox-community/netbox/issues/12849) - Fix LDAP group permissions assignment for API clients
* [#12951](https://github.com/netbox-community/netbox/issues/12951) - Display consistent parent information for each termination under cable view
* [#12953](https://github.com/netbox-community/netbox/issues/12953) - Fix designation of primary IP addresses during interface assignment
* [#12960](https://github.com/netbox-community/netbox/issues/12960) - Fix OpenAPI schema for various choice fields
* [#12961](https://github.com/netbox-community/netbox/issues/12961) - Set correct return URL for object contacts tabs
* [#12966](https://github.com/netbox-community/netbox/issues/12966) - Avoid catching database exceptions when maintenance mode is disabled
* [#12975](https://github.com/netbox-community/netbox/issues/12975) - Correct URL for VirtualDeviceContext API serializer
* [#12977](https://github.com/netbox-community/netbox/issues/12977) - Fix URL parameters for object count dashboard widgets
* [#12983](https://github.com/netbox-community/netbox/issues/12983) - Avoid erroneously clearing many-to-many assignments during bulk edit
* [#12989](https://github.com/netbox-community/netbox/issues/12989) - Fix bulk import of tags for device & module types
* [#13011](https://github.com/netbox-community/netbox/issues/13011) - Do not escape commas when rendering custom links
* [#13047](https://github.com/netbox-community/netbox/issues/13047) - Correct ASN count under ASN ranges list
* [#13056](https://github.com/netbox-community/netbox/issues/13056) - Add `config_template` field to device API serializer
* [#13092](https://github.com/netbox-community/netbox/issues/13092) - Allow nullifying power port max & allocated draw values during bulk edit
* [#13100](https://github.com/netbox-community/netbox/issues/13100) - Fix ValueError exception when searching for virtual device context for non-numeric values
---
## v3.5.4 (2023-06-20)
### Enhancements
* [#12828](https://github.com/netbox-community/netbox/issues/12828) - Define colors for staged change action choices
* [#12847](https://github.com/netbox-community/netbox/issues/12847) - Include "add" button on all device & virtual machine component list views
* [#12862](https://github.com/netbox-community/netbox/issues/12862) - Add menu navigation button to add wireless links directly
* [#12865](https://github.com/netbox-community/netbox/issues/12865) - Add "add" buttons for reports & scripts to navigation menu
### Bug Fixes
* [#12474](https://github.com/netbox-community/netbox/issues/12474) - Update cable terminations when assigning a location to a new site
* [#12622](https://github.com/netbox-community/netbox/issues/12622) - Permit the assignment of non-site VLANs to prefixes assigned to a site
* [#12682](https://github.com/netbox-community/netbox/issues/12682) - Correct OpenAPI schema for connected device API endpoint
* [#12687](https://github.com/netbox-community/netbox/issues/12687) - Allow the assignment of all /31 IP addresses to interfaces
* [#12818](https://github.com/netbox-community/netbox/issues/12818) - Fix permissions evaluation when queuing a data sync job
* [#12822](https://github.com/netbox-community/netbox/issues/12822) - Fix encoding of whitespace in custom link URLs
* [#12838](https://github.com/netbox-community/netbox/issues/12838) - Correct rounding of rack power utilization values
* [#12845](https://github.com/netbox-community/netbox/issues/12845) - Fix pagination of objects for related IP addresses table
* [#12850](https://github.com/netbox-community/netbox/issues/12850) - Fix table configuration modal for the contact assignments list
* [#12885](https://github.com/netbox-community/netbox/issues/12885) - Permit mounting of devices in rack unit 100
* [#12914](https://github.com/netbox-community/netbox/issues/12914) - Clear stored ordering from user config when cleared by request
---
## v3.5.3 (2023-06-02)
### Enhancements
* [#9876](https://github.com/netbox-community/netbox/issues/9876) - Improve support for matching tags in conditional rules
* [#12015](https://github.com/netbox-community/netbox/issues/12015) - Add device type & role filters for device components
* [#12470](https://github.com/netbox-community/netbox/issues/12470) - Collapse context data by default when viewing a rendered device configuration
* [#12562](https://github.com/netbox-community/netbox/issues/12562) - Record client IP address when logging authentication failures
* [#12597](https://github.com/netbox-community/netbox/issues/12597) - Add an option to hide custom fields only if unset
* [#12599](https://github.com/netbox-community/netbox/issues/12599) - Apply filter parameters to links in object count dashboard widgets
### Bug Fixes
* [#7503](https://github.com/netbox-community/netbox/issues/7503) - Improve rack space validation when creating multiple devices via REST API
* [#11539](https://github.com/netbox-community/netbox/issues/11539) - Fix exception when applying "empty" filter lookup with invalid value
* [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object
* [#12538](https://github.com/netbox-community/netbox/issues/12538) - Redirect user to originating view after editing/deleting an image attachment
* [#12627](https://github.com/netbox-community/netbox/issues/12627) - Restore hover preview for embedded image attachment tables
* [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text
* [#12702](https://github.com/netbox-community/netbox/issues/12702) - Fix sizing of rear port selection widget on front port template creation form
* [#12715](https://github.com/netbox-community/netbox/issues/12715) - Use contact assignments table to display the contacts assigned to an object
* [#12730](https://github.com/netbox-community/netbox/issues/12730) - Fix extraneous contacts listed in object contact assignments view
* [#12742](https://github.com/netbox-community/netbox/issues/12742) - Object counts dashboard widget should support URL-compatible query filters
* [#12762](https://github.com/netbox-community/netbox/issues/12762) - Fix GraphiQL UI by reverting graphene-django to earlier version
* [#12745](https://github.com/netbox-community/netbox/issues/12745) - Escape display text in API-backed selection widgets
* [#12779](https://github.com/netbox-community/netbox/issues/12779) - Correct arithmetic for converting inches to meters
---
## v3.5.2 (2023-05-22)
### Enhancements

View File

@@ -0,0 +1,198 @@
# NetBox v3.6
## v3.6.2 (2023-09-20)
### Enhancements
* [#13245](https://github.com/netbox-community/netbox/issues/13245) - Add interface types for QSFP112 and OSFP-RHS
* [#13563](https://github.com/netbox-community/netbox/issues/13563) - Add support for other delimiting characters when using CSV import
### Bug Fixes
* [#11209](https://github.com/netbox-community/netbox/issues/11209) - Hide available IP/VLAN listing when sorting under a parent prefix or VLAN range
* [#11617](https://github.com/netbox-community/netbox/issues/11617) - Raise validation error on the presence of an unknown CSV header during bulk import
* [#12219](https://github.com/netbox-community/netbox/issues/12219) - Fix dashboard widget heading contrast under dark mode
* [#12685](https://github.com/netbox-community/netbox/issues/12685) - Render Markdown in custom field help text on object edit forms
* [#13653](https://github.com/netbox-community/netbox/issues/13653) - Tweak color of error text to improve legibility
* [#13701](https://github.com/netbox-community/netbox/issues/13701) - Correct display of power feed legs under device view
* [#13706](https://github.com/netbox-community/netbox/issues/13706) - Restore extra filters dropdown on device interfaces list
* [#13721](https://github.com/netbox-community/netbox/issues/13721) - Filter VLAN choices by selected site (if any) when creating a prefix
* [#13727](https://github.com/netbox-community/netbox/issues/13727) - Fix exception when viewing rendered config for VM without a role assigned
* [#13745](https://github.com/netbox-community/netbox/issues/13745) - Optimize counter field migrations for large databases
* [#13756](https://github.com/netbox-community/netbox/issues/13756) - Fix exception when sorting module bay list by installed module status
* [#13757](https://github.com/netbox-community/netbox/issues/13757) - Fix RecursionError exception when assigning config context to a device type
* [#13767](https://github.com/netbox-community/netbox/issues/13767) - Fix support for comments when creating a new service via web UI
* [#13782](https://github.com/netbox-community/netbox/issues/13782) - Fix tag exclusion support for contact assignments
* [#13791](https://github.com/netbox-community/netbox/issues/13791) - Preserve whitespace in values when performing bulk rename of objects via web UI
* [#13809](https://github.com/netbox-community/netbox/issues/13809) - Avoid TypeError exception when editing active configuration with statically defined `CUSTOM_VALIDATORS`
* [#13813](https://github.com/netbox-community/netbox/issues/13813) - Fix member count for newly created virtual chassis
* [#13818](https://github.com/netbox-community/netbox/issues/13818) - Restore missing tags field on L2VPN termination edit form
---
## v3.6.1 (2023-09-06)
### Enhancements
* [#12870](https://github.com/netbox-community/netbox/issues/12870) - Support setting token expiration time using the provisioning API endpoint
* [#13444](https://github.com/netbox-community/netbox/issues/13444) - Add bulk rename functionality to the global device component lists
* [#13638](https://github.com/netbox-community/netbox/issues/13638) - Add optional `staff_only` attribute to MenuItem
### Bug Fixes
* [#12553](https://github.com/netbox-community/netbox/issues/12552) - Ensure `family` attribute is always returned when creating aggregates and prefixes via REST API
* [#13619](https://github.com/netbox-community/netbox/issues/13619) - Fix exception when viewing IP address assigned to a virtual machine
* [#13596](https://github.com/netbox-community/netbox/issues/13596) - Always display "render config" tab for devices and virtual machines
* [#13620](https://github.com/netbox-community/netbox/issues/13620) - Show admin menu items only for staff users
* [#13622](https://github.com/netbox-community/netbox/issues/13622) - Fix exception when viewing current config and no revisions have been created
* [#13626](https://github.com/netbox-community/netbox/issues/13626) - Correct filtering of recent activity list under user view
* [#13628](https://github.com/netbox-community/netbox/issues/13628) - Remove stale references to obsolete NAPALM integration
* [#13630](https://github.com/netbox-community/netbox/issues/13630) - Fix display of active status under user view
* [#13632](https://github.com/netbox-community/netbox/issues/13632) - Avoid raising exception when checking if FHRP group IP address is primary
* [#13642](https://github.com/netbox-community/netbox/issues/13642) - Suppress warning about unreflected model changes when applying migrations
* [#13657](https://github.com/netbox-community/netbox/issues/13657) - Fix decoding of data file content
* [#13674](https://github.com/netbox-community/netbox/issues/13674) - Fix retrieving individual report via REST API
* [#13682](https://github.com/netbox-community/netbox/issues/13682) - Fix error message returned when validation of custom field default value fails
* [#13684](https://github.com/netbox-community/netbox/issues/13684) - Enable modifying the configuration when maintenance mode is enabled
---
## v3.6.0 (2023-08-30)
### Breaking Changes
* PostgreSQL 11 is no longer supported (dropped in Django 4.2). NetBox v3.6 requires PostgreSQL 12 or later.
* The `boto3` and `dulwich` packages are no longer installed automatically. If needed for S3/git remote data backend support, add them to `local_requirements.txt` to ensure their installation.
* The `device_role` field on the Device model has been renamed to `role`. The `device_role` field has been temporarily retained on the REST API serializer for devices for backward compatibility, but is read-only.
* The `choices` array field has been removed from the CustomField model. Any defined choices are automatically migrated to CustomFieldChoiceSets, accessible via the new `choice_set` field on the CustomField model.
* The `napalm_driver` and `napalm_args` fields (which were deprecated in v3.5) have been removed from the Platform model.
* The `device` and `device_id` filter for interfaces will no longer include interfaces from virtual chassis peers. Two new filters, `virtual_chassis_member` and `virtual_chassis_member_id`, have been introduced to match all interfaces belonging to the specified device's virtual chassis (if any).
* Reports and scripts are now returned within a `results` list when fetched via the REST API, consistent with other models.
* Superusers can no longer retrieve API token keys via the web UI if [`ALLOW_TOKEN_RETRIEVAL`](https://docs.netbox.dev/en/stable/configuration/security/#allow_token_retrieval) is disabled. (The admin view has been removed per [#13044](https://github.com/netbox-community/netbox/issues/13044).)
### New Features
#### Relocated Admin UI Views ([#12589](https://github.com/netbox-community/netbox/issues/12589), [#12590](https://github.com/netbox-community/netbox/issues/12590), [#12591](https://github.com/netbox-community/netbox/issues/12591), [#13044](https://github.com/netbox-community/netbox/issues/13044))
Management views for the following object types, previously available only under the backend admin interface, have been relocated to the primary user interface:
* Users
* Groups
* Object permissions
* API tokens
* Configuration revisions
This migration provides a more consistent user experience and unlocks advanced functionality not feasible using Django's built-in views. The admin UI is scheduled for complete removal in NetBox v4.0.
#### Configurable Default Permissions ([#13038](https://github.com/netbox-community/netbox/issues/13038))
Administrators now have the option of configuring default permissions for _all_ users globally, regardless of explicit permission or group assignments granted in the database. This is accomplished by defining the `DEFAULT_PERMISSIONS` configuration parameter. By default, all users are granted permission to manage their own bookmarks and API tokens.
#### User Bookmarks ([#8248](https://github.com/netbox-community/netbox/issues/8248))
Users can now bookmark their favorite objects in NetBox. Bookmarks are accessible under each user's personal bookmarks list, and can also be added as a dashboard widget.
#### Custom Field Choice Sets ([#12988](https://github.com/netbox-community/netbox/issues/12988))
Selection and multi-select custom fields now employ discrete, reusable choice sets containing the valid options for each field. A choice set may be shared by multiple custom fields. Additionally, each choice within a set can now specify both a raw value and a human-friendly label (see [#13241](https://github.com/netbox-community/netbox/issues/13241)). Pre-existing custom field choices are migrated to choice sets automatically during the upgrade process.
#### Pre-Defined Location Choices for Custom Fields ([#12194](https://github.com/netbox-community/netbox/issues/12194))
Users now have the option to employ one of several pre-defined sets of choices when creating a custom field. These include:
* IATA airport codes
* ISO 3166 country codes
* UN/LOCODE location identifiers
When defining a choice set, one of the above can be employed as the base set, with the option to define extra, custom choices as well.
#### Restrict Tag Usage by Object Type ([#11541](https://github.com/netbox-community/netbox/issues/11541))
Tags may now be restricted to use with designated object types. Tags that have no specific object types assigned may be used with any object that supports tag assignment.
### Enhancements
* [#6347](https://github.com/netbox-community/netbox/issues/6347) - Cache the number of assigned components for devices and virtual machines
* [#8137](https://github.com/netbox-community/netbox/issues/8137) - Add a field for designating the out-of-band (OOB) IP address for devices
* [#10197](https://github.com/netbox-community/netbox/issues/10197) - Cache the number of member devices on each virtual chassis
* [#11305](https://github.com/netbox-community/netbox/issues/11305) - Add GPS coordinate fields to the device model
* [#11478](https://github.com/netbox-community/netbox/issues/11478) - Introduce `virtual_chassis_member` filter for interfaces & restore default behavior for `device` filter
* [#11519](https://github.com/netbox-community/netbox/issues/11519) - Add a SQL index for IP address host values to optimize queries
* [#11732](https://github.com/netbox-community/netbox/issues/11732) - Prevent inadvertent overwriting of object attributes by competing users
* [#11936](https://github.com/netbox-community/netbox/issues/11936) - Introduce support for tags and custom fields on webhooks
* [#12175](https://github.com/netbox-community/netbox/issues/12175) - Permit racks to start numbering at values greater than one
* [#12210](https://github.com/netbox-community/netbox/issues/12210) - Add tenancy assignment for power feeds
* [#12461](https://github.com/netbox-community/netbox/issues/12461) - Add config template rendering for virtual machines
* [#12814](https://github.com/netbox-community/netbox/issues/12814) - Expose NetBox models within ConfigTemplate rendering context
* [#12882](https://github.com/netbox-community/netbox/issues/12882) - Add tag support for contact assignments
* [#13037](https://github.com/netbox-community/netbox/issues/13037) - Return reports & scripts within a `results` list when fetched via the REST API
* [#13170](https://github.com/netbox-community/netbox/issues/13170) - Add `rf_role` to InterfaceTemplate
* [#13269](https://github.com/netbox-community/netbox/issues/13269) - Cache the number of assigned component templates for device types
### Bug Fixes
* [#13513](https://github.com/netbox-community/netbox/issues/13513) - Prevent exception when rendering bookmarks widget for anonymous user
* [#13599](https://github.com/netbox-community/netbox/issues/13599) - Fix errant counter increments when editing device/VM components
* [#13605](https://github.com/netbox-community/netbox/issues/13605) - Optimize cached counter migrations to avoid excessive memory consumption
### Other Changes
* Work has begun on introducing translation and localization support in NetBox. This work is being performed in preparation for release 4.0.
* [#6391](https://github.com/netbox-community/netbox/issues/6391) - Rename the `device_role` field on Device to `role` for consistency with VirtualMachine
* [#9077](https://github.com/netbox-community/netbox/issues/9077) - Prevent the errant execution of dangerous instance methods in Django templates
* [#11766](https://github.com/netbox-community/netbox/issues/11766) - Remove obsolete custom `ChoiceField` and `MultipleChoiceField` classes
* [#12180](https://github.com/netbox-community/netbox/issues/12180) - All API endpoints for available objects (e.g. IP addresses) now inherit from a common parent view
* [#12237](https://github.com/netbox-community/netbox/issues/12237) - Upgrade Django to v4.2
* [#12320](https://github.com/netbox-community/netbox/issues/12320) - Remove obsolete fields `napalm_driver` and `napalm_args` from Platform
* [#12794](https://github.com/netbox-community/netbox/issues/12794) - Avoid direct imports of Django's stock user model
* [#12906](https://github.com/netbox-community/netbox/issues/12906) - The `boto3` (AWS) and `dulwich` (git) packages for remote data sources are now optional requirements
* [#12964](https://github.com/netbox-community/netbox/issues/12964) - Drop support for PostgreSQL 11
* [#13309](https://github.com/netbox-community/netbox/issues/13309) - User account-specific resources have been moved to a new `account` app for better organization
### REST API Changes
* Introduced the following endpoints:
* `/api/extras/bookmarks/`
* `/api/extras/custom-field-choice-sets/`
* Added the `/api/extras/custom-fields/{id}/choices/` endpoint for select and multi-select custom fields
* dcim.Device
* Renamed `device_role` to `device`. Added a read-only `device_role` field for limited backward compatibility.
* Added the `latitude` and `longitude` fields (for GPS coordinates)
* Added the `oob_ip` field for out-of-band IP address assignment
* dcim.DeviceType
* Added read-only counter fields for assigned component templates:
* `console_port_template_count`
* `console_server_port_template_count`
* `power_port_template_count`
* `power_outlet_template_count`
* `interface_template_count`
* `front_port_template_count`
* `rear_port_template_count`
* `device_bay_template_count`
* `module_bay_template_count`
* `inventory_item_template_count`
* dcim.InterfaceTemplate
* Added the `rf_role` field
* dcim.Platform
* Removed the `napalm_driver` and `napalm_args` fields
* dcim.PowerFeed
* Added the `tenant` field
* dcim.Rack
* Added the `starting_unit` field
* dcim.VirtualChassis
* Added the read-only `member_count` field
* extras.CustomField
* Removed the `choices` array field
* Added the `choice_set` foreign key field (to ChoiceSet)
* extras.Report
* Reports are now returned within a `results` list
* extras.Script
* Scripts are now returned within a `results` list
* extras.Tag
* Added the `object_types` field for optional restriction to specific object types
* extras.Webhook
* Added `custom_fields` and `tags` support
* tenancy.ContactAssignment
* Added `tags` support
* virtualization.VirtualMachine
* Added the `oob_ip` field for out-of-band IP address assignment

View File

@@ -206,10 +206,12 @@ nav:
- VirtualChassis: 'models/dcim/virtualchassis.md'
- VirtualDeviceContext: 'models/dcim/virtualdevicecontext.md'
- Extras:
- Bookmark: 'models/extras/bookmark.md'
- Branch: 'models/extras/branch.md'
- ConfigContext: 'models/extras/configcontext.md'
- ConfigTemplate: 'models/extras/configtemplate.md'
- CustomField: 'models/extras/customfield.md'
- CustomFieldChoiceSet: 'models/extras/customfieldchoiceset.md'
- CustomLink: 'models/extras/customlink.md'
- ExportTemplate: 'models/extras/exporttemplate.md'
- ImageAttachment: 'models/extras/imageattachment.md'
@@ -269,10 +271,12 @@ nav:
- Application Registry: 'development/application-registry.md'
- User Preferences: 'development/user-preferences.md'
- Web UI: 'development/web-ui.md'
- Internationalization: 'development/internationalization.md'
- Release Checklist: 'development/release-checklist.md'
- git Cheat Sheet: 'development/git-cheat-sheet.md'
- Release Notes:
- Summary: 'release-notes/index.md'
- Version 3.6: 'release-notes/version-3.6.md'
- Version 3.5: 'release-notes/version-3.5.md'
- Version 3.4: 'release-notes/version-3.4.md'
- Version 3.3: 'release-notes/version-3.3.md'

View File

View File

@@ -0,0 +1,27 @@
# Generated by Django 4.1.10 on 2023-07-30 17:49
from django.db import migrations
class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0004_netboxgroup_netboxuser'),
]
operations = [
migrations.CreateModel(
name='UserToken',
fields=[
],
options={
'verbose_name': 'token',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('users.token',),
),
]

View File

15
netbox/account/models.py Normal file
View File

@@ -0,0 +1,15 @@
from django.urls import reverse
from users.models import Token
class UserToken(Token):
"""
Proxy model for users to manage their own API tokens.
"""
class Meta:
proxy = True
verbose_name = 'token'
def get_absolute_url(self):
return reverse('account:usertoken', args=[self.pk])

55
netbox/account/tables.py Normal file
View File

@@ -0,0 +1,55 @@
from django.utils.translation import gettext as _
from account.models import UserToken
from netbox.tables import NetBoxTable, columns
__all__ = (
'UserTokenTable',
)
TOKEN = """<samp><span id="token_{{ record.pk }}">{{ record }}</span></samp>"""
ALLOWED_IPS = """{{ value|join:", " }}"""
COPY_BUTTON = """
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
{% copy_content record.pk prefix="token_" color="success" %}
{% endif %}
"""
class UserTokenTable(NetBoxTable):
"""
Table for users to manager their own API tokens under account views.
"""
key = columns.TemplateColumn(
verbose_name=_('Key'),
template_code=TOKEN,
)
write_enabled = columns.BooleanColumn(
verbose_name=_('Write Enabled')
)
created = columns.DateColumn(
verbose_name=_('Created'),
)
expires = columns.DateColumn(
verbose_name=_('Expires'),
)
last_used = columns.DateTimeColumn(
verbose_name=_('Last Used'),
)
allowed_ips = columns.TemplateColumn(
verbose_name=_('Allowed IPs'),
template_code=ALLOWED_IPS
)
actions = columns.ActionsColumn(
actions=('edit', 'delete'),
extra_buttons=COPY_BUTTON
)
class Meta(NetBoxTable.Meta):
model = UserToken
fields = (
'pk', 'id', 'key', 'description', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
)

18
netbox/account/urls.py Normal file
View File

@@ -0,0 +1,18 @@
from django.urls import include, path
from utilities.urls import get_model_urls
from . import views
app_name = 'account'
urlpatterns = [
# Account views
path('profile/', views.ProfileView.as_view(), name='profile'),
path('bookmarks/', views.BookmarkListView.as_view(), name='bookmarks'),
path('preferences/', views.UserConfigView.as_view(), name='preferences'),
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
path('api-tokens/<int:pk>/', include(get_model_urls('account', 'usertoken'))),
]

298
netbox/account/views.py Normal file
View File

@@ -0,0 +1,298 @@
import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import update_last_login
from django.contrib.auth.signals import user_logged_in
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render, resolve_url
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
from account.models import UserToken
from extras.models import Bookmark, ObjectChange
from extras.tables import BookmarkTable, ObjectChangeTable
from netbox.authentication import get_auth_backend_display, get_saml_idps
from netbox.config import get_config
from netbox.views import generic
from users import forms, tables
from users.models import UserConfig
from utilities.views import register_model_view
#
# Login/logout
#
class LoginView(View):
"""
Perform user authentication via the web UI.
"""
template_name = 'login.html'
@method_decorator(sensitive_post_parameters('password'))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def gen_auth_data(self, name, url, params):
display_name, icon_name = get_auth_backend_display(name)
return {
'display_name': display_name,
'icon_name': icon_name,
'url': f'{url}?{urlencode(params)}',
}
def get_auth_backends(self, request):
auth_backends = []
saml_idps = get_saml_idps()
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
url = reverse('social:begin', args=[name])
params = {}
if next := request.GET.get('next'):
params['next'] = next
if name.lower() == 'saml' and saml_idps:
for idp in saml_idps:
params['idp'] = idp
data = self.gen_auth_data(name, url, params)
data['display_name'] = f'{data["display_name"]} ({idp})'
auth_backends.append(data)
else:
auth_backends.append(self.gen_auth_data(name, url, params))
return auth_backends
def get(self, request):
form = forms.LoginForm(request)
if request.user.is_authenticated:
logger = logging.getLogger('netbox.auth.login')
return self.redirect_to_next(request, logger)
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def post(self, request):
logger = logging.getLogger('netbox.auth.login')
form = forms.LoginForm(request, data=request.POST)
if form.is_valid():
logger.debug("Login form validation was successful")
# If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
# last_login time upon authentication.
if get_config().MAINTENANCE_MODE:
logger.warning("Maintenance mode enabled: disabling update of most recent login time")
user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
# Authenticate user
auth_login(request, form.get_user())
logger.info(f"User {request.user} successfully authenticated")
messages.success(request, f"Logged in as {request.user}.")
# Ensure the user has a UserConfig defined. (This should normally be handled by
# create_userconfig() on user creation.)
if not hasattr(request.user, 'config'):
config = get_config()
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
return self.redirect_to_next(request, logger)
else:
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def redirect_to_next(self, request, logger):
data = request.POST if request.method == "POST" else request.GET
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
logger.debug(f"Redirecting user to {redirect_url}")
else:
if redirect_url:
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_url}")
redirect_url = reverse('home')
return HttpResponseRedirect(redirect_url)
class LogoutView(View):
"""
Deauthenticate a web user.
"""
def get(self, request):
logger = logging.getLogger('netbox.auth.logout')
# Log out the user
username = request.user
auth_logout(request)
logger.info(f"User {username} has logged out")
messages.info(request, "You have logged out.")
# Delete session key cookie (if set) upon logout
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
response.delete_cookie('session_key')
return response
#
# User profiles
#
class ProfileView(LoginRequiredMixin, View):
template_name = 'account/profile.html'
def get(self, request):
# Compile changelog table
changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter(
user=request.user
).prefetch_related(
'changed_object_type'
)[:20]
changelog_table = ObjectChangeTable(changelog)
return render(request, self.template_name, {
'changelog_table': changelog_table,
'active_tab': 'profile',
})
class UserConfigView(LoginRequiredMixin, View):
template_name = 'account/preferences.html'
def get(self, request):
userconfig = request.user.config
form = forms.UserConfigForm(instance=userconfig)
return render(request, self.template_name, {
'form': form,
'active_tab': 'preferences',
})
def post(self, request):
userconfig = request.user.config
form = forms.UserConfigForm(request.POST, instance=userconfig)
if form.is_valid():
form.save()
messages.success(request, "Your preferences have been updated.")
return redirect('account:preferences')
return render(request, self.template_name, {
'form': form,
'active_tab': 'preferences',
})
class ChangePasswordView(LoginRequiredMixin, View):
template_name = 'account/password.html'
def get(self, request):
# LDAP users cannot change their password here
if getattr(request.user, 'ldap_username', None):
messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
return redirect('account:profile')
form = forms.PasswordChangeForm(user=request.user)
return render(request, self.template_name, {
'form': form,
'active_tab': 'password',
})
def post(self, request):
form = forms.PasswordChangeForm(user=request.user, data=request.POST)
if form.is_valid():
form.save()
update_session_auth_hash(request, form.user)
messages.success(request, "Your password has been changed successfully.")
return redirect('account:profile')
return render(request, self.template_name, {
'form': form,
'active_tab': 'change_password',
})
#
# Bookmarks
#
class BookmarkListView(LoginRequiredMixin, generic.ObjectListView):
table = BookmarkTable
template_name = 'account/bookmarks.html'
def get_queryset(self, request):
return Bookmark.objects.filter(user=request.user)
def get_extra_context(self, request):
return {
'active_tab': 'bookmarks',
}
#
# User views for token management
#
class UserTokenListView(LoginRequiredMixin, View):
def get(self, request):
tokens = UserToken.objects.filter(user=request.user)
table = tables.UserTokenTable(tokens)
table.configure(request)
return render(request, 'account/token_list.html', {
'tokens': tokens,
'active_tab': 'api-tokens',
'table': table,
})
@register_model_view(UserToken)
class UserTokenView(LoginRequiredMixin, View):
def get(self, request, pk):
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
key = token.key if settings.ALLOW_TOKEN_RETRIEVAL else None
return render(request, 'account/token.html', {
'object': token,
'key': key,
})
@register_model_view(UserToken, 'edit')
class UserTokenEditView(generic.ObjectEditView):
queryset = UserToken.objects.all()
form = forms.UserTokenForm
default_return_url = 'account:usertoken_list'
def alter_object(self, obj, request, url_args, url_kwargs):
if not obj.pk:
obj.user = request.user
return obj
@register_model_view(UserToken, 'delete')
class UserTokenDeleteView(generic.ObjectDeleteView):
queryset = UserToken.objects.all()
default_return_url = 'account:usertoken_list'

View File

@@ -1,3 +1,5 @@
from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
@@ -16,12 +18,12 @@ class CircuitStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONED = 'decommissioned'
CHOICES = [
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_PROVISIONING, 'Provisioning', 'blue'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_OFFLINE, 'Offline', 'red'),
(STATUS_DEPROVISIONING, 'Deprovisioning', 'yellow'),
(STATUS_DECOMMISSIONED, 'Decommissioned', 'gray'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_PROVISIONING, _('Provisioning'), 'blue'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_OFFLINE, _('Offline'), 'red'),
(STATUS_DEPROVISIONING, _('Deprovisioning'), 'yellow'),
(STATUS_DECOMMISSIONED, _('Decommissioned'), 'gray'),
]

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
from circuits.models import *
@@ -26,12 +26,11 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField(
label=_('Comments')
)
comments = CommentField()
model = Provider
fieldsets = (
@@ -44,16 +43,16 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField(
label=_('Comments')
)
comments = CommentField()
model = ProviderAccount
fieldsets = (
@@ -66,6 +65,7 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
required=False
)
@@ -75,12 +75,11 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
label=_('Service ID')
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField(
label=_('Comments')
)
comments = CommentField()
model = ProviderNetwork
fieldsets = (
@@ -93,6 +92,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
@@ -106,14 +106,17 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
type = DynamicModelChoiceField(
label=_('Type'),
queryset=CircuitType.objects.all(),
required=False
)
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
required=False
)
provider_account = DynamicModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
required=False,
query_params={
@@ -121,19 +124,23 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
}
)
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(CircuitStatusChoices),
required=False,
initial=''
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
install_date = forms.DateField(
label=_('Install date'),
required=False,
widget=DatePicker()
)
termination_date = forms.DateField(
label=_('Termination date'),
required=False,
widget=DatePicker()
)
@@ -145,18 +152,17 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
)
)
description = forms.CharField(
label=_('Description'),
max_length=100,
required=False
)
comments = CommentField(
label=_('Comments')
)
comments = CommentField()
model = Circuit
fieldsets = (
('Circuit', ('provider', 'type', 'status', 'description')),
('Service Parameters', ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
('Tenancy', ('tenant',)),
(_('Circuit'), ('provider', 'type', 'status', 'description')),
(_('Service Parameters'), ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
(_('Tenancy'), ('tenant',)),
)
nullable_fields = (
'tenant', 'commit_rate', 'description', 'comments',

View File

@@ -3,7 +3,7 @@ from django import forms
from circuits.choices import CircuitStatusChoices
from circuits.models import *
from dcim.models import Site
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
from utilities.forms import BootstrapMixin
@@ -31,6 +31,7 @@ class ProviderImportForm(NetBoxModelImportForm):
class ProviderAccountImportForm(NetBoxModelImportForm):
provider = CSVModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
to_field_name='name',
help_text=_('Assigned provider')
@@ -45,6 +46,7 @@ class ProviderAccountImportForm(NetBoxModelImportForm):
class ProviderNetworkImportForm(NetBoxModelImportForm):
provider = CSVModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
to_field_name='name',
help_text=_('Assigned provider')
@@ -67,26 +69,31 @@ class CircuitTypeImportForm(NetBoxModelImportForm):
class CircuitImportForm(NetBoxModelImportForm):
provider = CSVModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
to_field_name='name',
help_text=_('Assigned provider')
)
provider_account = CSVModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
to_field_name='name',
help_text=_('Assigned provider account'),
required=False
)
type = CSVModelChoiceField(
label=_('Type'),
queryset=CircuitType.objects.all(),
to_field_name='name',
help_text=_('Type of circuit')
)
status = CSVChoiceField(
label=_('Status'),
choices=CircuitStatusChoices,
help_text=_('Operational status')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
@@ -103,11 +110,13 @@ class CircuitImportForm(NetBoxModelImportForm):
class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
required=False
)
provider_network = CSVModelChoiceField(
label=_('Provider network'),
queryset=ProviderNetwork.objects.all(),
to_field_name='name',
required=False

View File

@@ -23,9 +23,9 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
model = Provider
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id')),
('ASN', ('asn',)),
('Contacts', ('contact', 'contact_role', 'contact_group')),
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
(_('ASN'), ('asn',)),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -62,7 +62,7 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
model = ProviderAccount
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('provider_id', 'account')),
(_('Attributes'), ('provider_id', 'account')),
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
@@ -70,6 +70,7 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
label=_('Provider')
)
account = forms.CharField(
label=_('Account'),
required=False
)
tag = TagFilterField(model)
@@ -79,7 +80,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
model = ProviderNetwork
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('provider_id', 'service_id')),
(_('Attributes'), ('provider_id', 'service_id')),
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
@@ -87,6 +88,7 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
label=_('Provider')
)
service_id = forms.CharField(
label=_('Service id'),
max_length=100,
required=False
)
@@ -102,11 +104,11 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
model = Circuit
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Provider', ('provider_id', 'provider_account_id', 'provider_network_id')),
('Attributes', ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
('Location', ('region_id', 'site_group_id', 'site_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
(_('Provider'), ('provider_id', 'provider_account_id', 'provider_network_id')),
(_('Attributes'), ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
)
type_id = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(),
@@ -135,6 +137,7 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
label=_('Provider network')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=CircuitStatusChoices,
required=False
)
@@ -158,10 +161,12 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
label=_('Site')
)
install_date = forms.DateField(
label=_('Install date'),
required=False,
widget=DatePicker
)
termination_date = forms.DateField(
label=_('Termination date'),
required=False,
widget=DatePicker
)

View File

@@ -1,4 +1,4 @@
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
from circuits.models import *
@@ -29,7 +29,7 @@ class ProviderForm(NetBoxModelForm):
comments = CommentField()
fieldsets = (
('Provider', ('name', 'slug', 'asns', 'description', 'tags')),
(_('Provider'), ('name', 'slug', 'asns', 'description', 'tags')),
)
class Meta:
@@ -41,6 +41,7 @@ class ProviderForm(NetBoxModelForm):
class ProviderAccountForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all()
)
comments = CommentField()
@@ -54,12 +55,13 @@ class ProviderAccountForm(NetBoxModelForm):
class ProviderNetworkForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all()
)
comments = CommentField()
fieldsets = (
('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
(_('Provider Network'), ('provider', 'name', 'service_id', 'description', 'tags')),
)
class Meta:
@@ -73,7 +75,7 @@ class CircuitTypeForm(NetBoxModelForm):
slug = SlugField()
fieldsets = (
('Circuit Type', (
(_('Circuit Type'), (
'name', 'slug', 'description', 'tags',
)),
)
@@ -87,10 +89,12 @@ class CircuitTypeForm(NetBoxModelForm):
class CircuitForm(TenancyForm, NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
selector=True
)
provider_account = DynamicModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
required=False,
query_params={
@@ -103,9 +107,9 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
comments = CommentField()
fieldsets = (
('Circuit', ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
('Service Parameters', ('install_date', 'termination_date', 'commit_rate')),
('Tenancy', ('tenant_group', 'tenant')),
(_('Circuit'), ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
(_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
(_('Tenancy'), ('tenant_group', 'tenant')),
)
class Meta:
@@ -125,15 +129,18 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
class CircuitTerminationForm(NetBoxModelForm):
circuit = DynamicModelChoiceField(
label=_('Circuit'),
queryset=Circuit.objects.all(),
selector=True
)
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
required=False,
selector=True
)
provider_network = DynamicModelChoiceField(
label=_('Provider network'),
queryset=ProviderNetwork.objects.all(),
required=False,
selector=True

View File

@@ -1,14 +1,12 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from circuits.choices import *
from dcim.models import CabledObjectModel
from netbox.models import (
ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, PrimaryModel, TagsMixin,
)
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ImageAttachmentsMixin, TagsMixin
__all__ = (
'Circuit',
@@ -25,8 +23,13 @@ class CircuitType(OrganizationalModel):
def get_absolute_url(self):
return reverse('circuits:circuittype', args=[self.pk])
class Meta:
ordering = ('name',)
verbose_name = _('circuit type')
verbose_name_plural = _('circuit types')
class Circuit(PrimaryModel):
class Circuit(ContactsMixin, ImageAttachmentsMixin, 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, and may optionally be assigned to a particular
@@ -34,8 +37,8 @@ class Circuit(PrimaryModel):
"""
cid = models.CharField(
max_length=100,
verbose_name='Circuit ID',
help_text=_("Unique circuit ID")
verbose_name=_('circuit ID'),
help_text=_('Unique circuit ID')
)
provider = models.ForeignKey(
to='circuits.Provider',
@@ -55,6 +58,7 @@ class Circuit(PrimaryModel):
related_name='circuits'
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=CircuitStatusChoices,
default=CircuitStatusChoices.STATUS_ACTIVE
@@ -69,28 +73,20 @@ class Circuit(PrimaryModel):
install_date = models.DateField(
blank=True,
null=True,
verbose_name='Installed'
verbose_name=_('installed')
)
termination_date = models.DateField(
blank=True,
null=True,
verbose_name='Terminates'
verbose_name=_('terminates')
)
commit_rate = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name='Commit rate (Kbps)',
verbose_name=_('commit rate (Kbps)'),
help_text=_("Committed rate")
)
# Generic relations
contacts = GenericRelation(
to='tenancy.ContactAssignment'
)
images = GenericRelation(
to='extras.ImageAttachment'
)
# Cache associated CircuitTerminations
termination_a = models.ForeignKey(
to='circuits.CircuitTermination',
@@ -130,6 +126,8 @@ class Circuit(PrimaryModel):
name='%(app_label)s_%(class)s_unique_provideraccount_cid'
),
)
verbose_name = _('circuit')
verbose_name_plural = _('circuits')
def __str__(self):
return self.cid
@@ -162,7 +160,7 @@ class CircuitTermination(
term_side = models.CharField(
max_length=1,
choices=CircuitTerminationSideChoices,
verbose_name='Termination'
verbose_name=_('termination')
)
site = models.ForeignKey(
to='dcim.Site',
@@ -179,30 +177,31 @@ class CircuitTermination(
null=True
)
port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)',
verbose_name=_('port speed (Kbps)'),
blank=True,
null=True,
help_text=_("Physical circuit speed")
help_text=_('Physical circuit speed')
)
upstream_speed = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name='Upstream speed (Kbps)',
verbose_name=_('upstream speed (Kbps)'),
help_text=_('Upstream speed, if different from port speed')
)
xconnect_id = models.CharField(
max_length=50,
blank=True,
verbose_name='Cross-connect ID',
help_text=_("ID of the local cross-connect")
verbose_name=_('cross-connect ID'),
help_text=_('ID of the local cross-connect')
)
pp_info = models.CharField(
max_length=100,
blank=True,
verbose_name='Patch panel/port(s)',
help_text=_("Patch panel ID and port number(s)")
verbose_name=_('patch panel/port(s)'),
help_text=_('Patch panel ID and port number(s)')
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
@@ -215,6 +214,8 @@ class CircuitTermination(
name='%(app_label)s_%(class)s_unique_circuit_term_side'
),
)
verbose_name = _('circuit termination')
verbose_name_plural = _('circuit terminations')
def __str__(self):
return f'{self.circuit}: Termination {self.term_side}'

View File

@@ -1,10 +1,10 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from netbox.models import PrimaryModel
from netbox.models.features import ContactsMixin
__all__ = (
'ProviderNetwork',
@@ -13,17 +13,19 @@ __all__ = (
)
class Provider(PrimaryModel):
class Provider(ContactsMixin, 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.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True,
help_text=_("Full name of the provider")
help_text=_('Full name of the provider')
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100,
unique=True
)
@@ -33,15 +35,12 @@ class Provider(PrimaryModel):
blank=True
)
# Generic relations
contacts = GenericRelation(
to='tenancy.ContactAssignment'
)
clone_fields = ()
class Meta:
ordering = ['name']
verbose_name = _('provider')
verbose_name_plural = _('providers')
def __str__(self):
return self.name
@@ -50,7 +49,7 @@ class Provider(PrimaryModel):
return reverse('circuits:provider', args=[self.pk])
class ProviderAccount(PrimaryModel):
class ProviderAccount(ContactsMixin, PrimaryModel):
"""
This is a discrete account within a provider. Each Circuit belongs to a Provider Account.
"""
@@ -61,18 +60,14 @@ class ProviderAccount(PrimaryModel):
)
account = models.CharField(
max_length=100,
verbose_name='Account ID'
verbose_name=_('account ID')
)
name = models.CharField(
verbose_name=_('name'),
max_length=100,
blank=True
)
# Generic relations
contacts = GenericRelation(
to='tenancy.ContactAssignment'
)
clone_fields = ('provider', )
class Meta:
@@ -88,6 +83,8 @@ class ProviderAccount(PrimaryModel):
condition=~Q(name="")
),
)
verbose_name = _('provider account')
verbose_name_plural = _('provider accounts')
def __str__(self):
if self.name:
@@ -104,6 +101,7 @@ class ProviderNetwork(PrimaryModel):
unimportant to the user.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100
)
provider = models.ForeignKey(
@@ -114,7 +112,7 @@ class ProviderNetwork(PrimaryModel):
service_id = models.CharField(
max_length=100,
blank=True,
verbose_name='Service ID'
verbose_name=_('service ID')
)
class Meta:
@@ -125,6 +123,8 @@ class ProviderNetwork(PrimaryModel):
name='%(app_label)s_%(class)s_unique_provider_name'
),
)
verbose_name = _('provider network')
verbose_name_plural = _('provider networks')
def __str__(self):
return self.name

View File

@@ -1,3 +1,4 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from circuits.models import *
@@ -24,7 +25,8 @@ CIRCUITTERMINATION_LINK = """
class CircuitTypeTable(NetBoxTable):
name = tables.Column(
linkify=True
linkify=True,
verbose_name=_('Name'),
)
tags = columns.TagColumn(
url_name='circuits:circuittype_list'
@@ -32,7 +34,7 @@ class CircuitTypeTable(NetBoxTable):
circuit_count = columns.LinkedCountColumn(
viewname='circuits:circuit_list',
url_params={'type_id': 'pk'},
verbose_name='Circuits'
verbose_name=_('Circuits')
)
class Meta(NetBoxTable.Meta):
@@ -46,28 +48,31 @@ class CircuitTypeTable(NetBoxTable):
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
cid = tables.Column(
linkify=True,
verbose_name='Circuit ID'
verbose_name=_('Circuit ID')
)
provider = tables.Column(
verbose_name=_('Provider'),
linkify=True
)
provider_account = tables.Column(
linkify=True,
verbose_name='Account'
verbose_name=_('Account')
)
status = columns.ChoiceFieldColumn()
termination_a = tables.TemplateColumn(
template_code=CIRCUITTERMINATION_LINK,
verbose_name='Side A'
verbose_name=_('Side A')
)
termination_z = tables.TemplateColumn(
template_code=CIRCUITTERMINATION_LINK,
verbose_name='Side Z'
verbose_name=_('Side Z')
)
commit_rate = CommitRateColumn(
verbose_name='Commit Rate'
verbose_name=_('Commit Rate')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:circuit_list'
)

View File

@@ -1,4 +1,5 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from circuits.models import *
from django_tables2.utils import Accessor
from tenancy.tables import ContactsColumnMixin
@@ -14,35 +15,38 @@ __all__ = (
class ProviderTable(ContactsColumnMixin, NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
accounts = columns.ManyToManyColumn(
linkify_item=True,
verbose_name='Accounts'
verbose_name=_('Accounts')
)
account_count = columns.LinkedCountColumn(
accessor=tables.A('accounts__count'),
viewname='circuits:provideraccount_list',
url_params={'account_id': 'pk'},
verbose_name='Account Count'
verbose_name=_('Account Count')
)
asns = columns.ManyToManyColumn(
linkify_item=True,
verbose_name='ASNs'
verbose_name=_('ASNs')
)
asn_count = columns.LinkedCountColumn(
accessor=tables.A('asns__count'),
viewname='ipam:asn_list',
url_params={'provider_id': 'pk'},
verbose_name='ASN Count'
verbose_name=_('ASN Count')
)
circuit_count = columns.LinkedCountColumn(
accessor=Accessor('count_circuits'),
viewname='circuits:circuit_list',
url_params={'provider_id': 'pk'},
verbose_name='Circuits'
verbose_name=_('Circuits')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:provider_list'
)
@@ -58,19 +62,25 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
account = tables.Column(
linkify=True
linkify=True,
verbose_name=_('Account'),
)
name = tables.Column(
verbose_name=_('Name'),
)
name = tables.Column()
provider = tables.Column(
verbose_name=_('Provider'),
linkify=True
)
circuit_count = columns.LinkedCountColumn(
accessor=Accessor('count_circuits'),
viewname='circuits:circuit_list',
url_params={'provider_account_id': 'pk'},
verbose_name='Circuits'
verbose_name=_('Circuits')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:provideraccount_list'
)
@@ -86,12 +96,16 @@ class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
class ProviderNetworkTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
provider = tables.Column(
verbose_name=_('Provider'),
linkify=True
)
comments = columns.MarkdownColumn()
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
tags = columns.TagColumn(
url_name='circuits:providernetwork_list'
)

View File

@@ -163,7 +163,7 @@ class ProviderNetworkView(generic.ObjectView):
related_models = (
(
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
'providernetwork_id',
'provider_network_id',
),
)

View File

@@ -1,5 +1,6 @@
import re
import typing
from collections import OrderedDict
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.openapi import AutoSchema
@@ -28,14 +29,19 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
target_class = 'netbox.api.fields.ChoiceField'
def map_serializer_field(self, auto_schema, direction):
build_cf = build_choice_field(self.target)
if direction == 'request':
return build_choice_field(self.target)
return build_cf
elif direction == "response":
value = build_cf
label = {**build_basic_type(OpenApiTypes.STR), "enum": list(OrderedDict.fromkeys(self.target.choices.values()))}
return build_object_type(
properties={
"value": build_basic_type(OpenApiTypes.STR),
"label": build_basic_type(OpenApiTypes.STR),
"value": value,
"label": label
}
)

View File

@@ -33,7 +33,7 @@ class DataSourceViewSet(NetBoxModelViewSet):
"""
Enqueue a job to synchronize the DataSource.
"""
if not request.user.has_perm('extras.sync_datasource'):
if not request.user.has_perm('core.sync_datasource'):
raise PermissionDenied("Syncing data sources requires the core.sync_datasource permission.")
datasource = get_object_or_404(DataSource, pk=pk)

View File

@@ -1,4 +1,15 @@
from django.apps import AppConfig
from django.db import models
from django.db.migrations.operations import AlterModelOptions
from utilities.migration import custom_deconstruct
# Ignore verbose_name & verbose_name_plural Meta options when calculating model migrations
AlterModelOptions.ALTER_OPTION_KEYS.remove('verbose_name')
AlterModelOptions.ALTER_OPTION_KEYS.remove('verbose_name_plural')
# Use our custom destructor to ignore certain attributes when calculating field migrations
models.Field.deconstruct = custom_deconstruct
class CoreConfig(AppConfig):

View File

@@ -1,4 +1,4 @@
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
@@ -14,8 +14,8 @@ class DataSourceTypeChoices(ChoiceSet):
CHOICES = (
(LOCAL, _('Local'), 'gray'),
(GIT, _('Git'), 'blue'),
(AMAZON_S3, _('Amazon S3'), 'blue'),
(GIT, 'Git', 'blue'),
(AMAZON_S3, 'Amazon S3', 'blue'),
)
@@ -63,12 +63,12 @@ class JobStatusChoices(ChoiceSet):
STATUS_FAILED = 'failed'
CHOICES = (
(STATUS_PENDING, 'Pending', 'cyan'),
(STATUS_SCHEDULED, 'Scheduled', 'gray'),
(STATUS_RUNNING, 'Running', 'blue'),
(STATUS_COMPLETED, 'Completed', 'green'),
(STATUS_ERRORED, 'Errored', 'red'),
(STATUS_FAILED, 'Failed', 'red'),
(STATUS_PENDING, _('Pending'), 'cyan'),
(STATUS_SCHEDULED, _('Scheduled'), 'gray'),
(STATUS_RUNNING, _('Running'), 'blue'),
(STATUS_COMPLETED, _('Completed'), 'green'),
(STATUS_ERRORED, _('Errored'), 'red'),
(STATUS_FAILED, _('Failed'), 'red'),
)
TERMINAL_STATE_CHOICES = (

View File

@@ -6,13 +6,9 @@ from contextlib import contextmanager
from pathlib import Path
from urllib.parse import urlparse
import boto3
from botocore.config import Config as Boto3Config
from django import forms
from django.conf import settings
from django.utils.translation import gettext as _
from dulwich import porcelain
from dulwich.config import ConfigDict
from netbox.registry import registry
from .choices import DataSourceTypeChoices
@@ -41,10 +37,22 @@ def register_backend(name):
class DataBackend:
parameters = {}
sensitive_parameters = []
# Prevent Django's template engine from calling the backend
# class when referenced via DataSource.backend_class
do_not_call_in_templates = True
def __init__(self, url, **kwargs):
self.url = url
self.params = kwargs
self.config = self.init_config()
def init_config(self):
"""
Hook to initialize the instance's configuration.
"""
return
@property
def url_scheme(self):
@@ -57,6 +65,7 @@ class DataBackend:
@register_backend(DataSourceTypeChoices.LOCAL)
class LocalBackend(DataBackend):
@contextmanager
def fetch(self):
logger.debug(f"Data source type is local; skipping fetch")
@@ -72,13 +81,13 @@ class GitBackend(DataBackend):
required=False,
label=_('Username'),
widget=forms.TextInput(attrs={'class': 'form-control'}),
help_text=_("Only used for cloning with HTTP / HTTPS"),
help_text=_("Only used for cloning with HTTP(S)"),
),
'password': forms.CharField(
required=False,
label=_('Password'),
widget=forms.TextInput(attrs={'class': 'form-control'}),
help_text=_("Only used for cloning with HTTP / HTTPS"),
help_text=_("Only used for cloning with HTTP(S)"),
),
'branch': forms.CharField(
required=False,
@@ -86,31 +95,43 @@ class GitBackend(DataBackend):
widget=forms.TextInput(attrs={'class': 'form-control'})
)
}
sensitive_parameters = ['password']
def init_config(self):
from dulwich.config import ConfigDict
# Initialize backend config
config = ConfigDict()
# Apply HTTP proxy (if configured)
if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
if proxy := settings.HTTP_PROXIES.get(self.url_scheme):
config.set("http", "proxy", proxy)
return config
@contextmanager
def fetch(self):
from dulwich import porcelain
local_path = tempfile.TemporaryDirectory()
config = ConfigDict()
clone_args = {
"branch": self.params.get('branch'),
"config": config,
"config": self.config,
"depth": 1,
"errstream": porcelain.NoneStream(),
"quiet": True,
}
if self.url_scheme in ('http', 'https'):
clone_args.update(
{
"username": self.params.get('username'),
"password": self.params.get('password'),
}
)
if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
if proxy := settings.HTTP_PROXIES.get(self.url_scheme):
config.set("http", "proxy", proxy)
if self.params.get('username'):
clone_args.update(
{
"username": self.params.get('username'),
"password": self.params.get('password'),
}
)
logger.debug(f"Cloning git repo: {self.url}")
try:
@@ -135,18 +156,24 @@ class S3Backend(DataBackend):
widget=forms.TextInput(attrs={'class': 'form-control'})
),
}
sensitive_parameters = ['aws_secret_access_key']
REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com'
@contextmanager
def fetch(self):
local_path = tempfile.TemporaryDirectory()
def init_config(self):
from botocore.config import Config as Boto3Config
# Build the S3 configuration
s3_config = Boto3Config(
# Initialize backend config
return Boto3Config(
proxies=settings.HTTP_PROXIES,
)
@contextmanager
def fetch(self):
import boto3
local_path = tempfile.TemporaryDirectory()
# Initialize the S3 resource and bucket
aws_access_key_id = self.params.get('aws_access_key_id')
aws_secret_access_key = self.params.get('aws_secret_access_key')
@@ -155,7 +182,7 @@ class S3Backend(DataBackend):
region_name=self._region_name,
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
config=s3_config
config=self.config
)
bucket = s3.Bucket(self._bucket_name)

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from core.choices import DataSourceTypeChoices
from core.models import *
@@ -15,6 +15,7 @@ __all__ = (
class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
type = forms.ChoiceField(
label=_('Type'),
choices=add_blank_choice(DataSourceTypeChoices),
required=False,
initial=''
@@ -25,16 +26,17 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
label=_('Enforce unique space')
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField(
label=_('Comments')
)
comments = CommentField()
parameters = forms.JSONField(
label=_('Parameters'),
required=False
)
ignore_rules = forms.CharField(
label=_('Ignore rules'),
required=False,
widget=forms.Textarea()
)

View File

@@ -1,7 +1,7 @@
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from core.choices import *
from core.models import *
@@ -23,17 +23,20 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm):
model = DataSource
fieldsets = (
(None, ('q', 'filter_id')),
('Data Source', ('type', 'status')),
(_('Data Source'), ('type', 'status')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=DataSourceTypeChoices,
required=False
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=DataSourceStatusChoices,
required=False
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -45,7 +48,7 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
model = DataFile
fieldsets = (
(None, ('q', 'filter_id')),
('File', ('source_id',)),
(_('File'), ('source_id',)),
)
source_id = DynamicModelMultipleChoiceField(
queryset=DataSource.objects.all(),
@@ -57,8 +60,8 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
class JobFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
(None, ('q', 'filter_id')),
('Attributes', ('object_type', 'status')),
('Creation', (
(_('Attributes'), ('object_type', 'status')),
(_('Creation'), (
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
'started__after', 'completed__before', 'completed__after', 'user',
)),
@@ -69,43 +72,52 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
required=False,
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=JobStatusChoices,
required=False
)
created__after = forms.DateTimeField(
label=_('Created after'),
required=False,
widget=DateTimePicker()
)
created__before = forms.DateTimeField(
label=_('Created before'),
required=False,
widget=DateTimePicker()
)
scheduled__after = forms.DateTimeField(
label=_('Scheduled after'),
required=False,
widget=DateTimePicker()
)
scheduled__before = forms.DateTimeField(
label=_('Scheduled before'),
required=False,
widget=DateTimePicker()
)
started__after = forms.DateTimeField(
label=_('Started after'),
required=False,
widget=DateTimePicker()
)
started__before = forms.DateTimeField(
label=_('Started before'),
required=False,
widget=DateTimePicker()
)
completed__after = forms.DateTimeField(
label=_('Completed after'),
required=False,
widget=DateTimePicker()
)
completed__before = forms.DateTimeField(
label=_('Completed before'),
required=False,
widget=DateTimePicker()
)
user = DynamicModelMultipleChoiceField(
queryset=User.objects.all(),
queryset=get_user_model().objects.all(),
required=False,
label=_('User'),
widget=APISelectMultiple(

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from core.models import DataFile, DataSource
from utilities.forms.fields import DynamicModelChoiceField

View File

@@ -1,6 +1,7 @@
import copy
from django import forms
from django.utils.translation import gettext_lazy as _
from core.forms.mixins import SyncedDataMixin
from core.models import *
@@ -38,11 +39,11 @@ class DataSourceForm(NetBoxModelForm):
@property
def fieldsets(self):
fieldsets = [
('Source', ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
(_('Source'), ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
]
if self.backend_fields:
fieldsets.append(
('Backend Parameters', self.backend_fields)
(_('Backend Parameters'), self.backend_fields)
)
return fieldsets
@@ -79,8 +80,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
)
fieldsets = (
('File Upload', ('upload_file',)),
('Data Source', ('data_source', 'data_file', 'auto_sync_enabled')),
(_('File Upload'), ('upload_file',)),
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
)
class Meta:

View File

@@ -1,12 +1,6 @@
# noinspection PyUnresolvedReferences
from django.conf import settings
from django.core.management.base import CommandError
from django.core.management.commands.makemigrations import Command as _Command
from django.db import models
from utilities.migration import custom_deconstruct
models.Field.deconstruct = custom_deconstruct
class Command(_Command):

View File

@@ -1,7 +0,0 @@
# noinspection PyUnresolvedReferences
from django.core.management.commands.migrate import Command
from django.db import models
from utilities.migration import custom_deconstruct
models.Field.deconstruct = custom_deconstruct

View File

@@ -5,7 +5,7 @@ import sys
from django import get_version
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
@@ -60,7 +60,7 @@ class Command(BaseCommand):
# Additional objects to include
namespace['ContentType'] = ContentType
namespace['User'] = User
namespace['User'] = get_user_model()
# Load convenience commands
namespace.update({

View File

@@ -39,10 +39,12 @@ class DataSource(JobsMixin, PrimaryModel):
A remote source, such as a git repository, from which DataFiles are synchronized.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
type = models.CharField(
verbose_name=_('type'),
max_length=50,
choices=DataSourceTypeChoices,
default=DataSourceTypeChoices.LOCAL
@@ -52,23 +54,28 @@ class DataSource(JobsMixin, PrimaryModel):
verbose_name=_('URL')
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=DataSourceStatusChoices,
default=DataSourceStatusChoices.NEW,
editable=False
)
enabled = models.BooleanField(
verbose_name=_('enabled'),
default=True
)
ignore_rules = models.TextField(
verbose_name=_('ignore rules'),
blank=True,
help_text=_("Patterns (one per line) matching files to ignore when syncing")
)
parameters = models.JSONField(
verbose_name=_('parameters'),
blank=True,
null=True
)
last_synced = models.DateTimeField(
verbose_name=_('last synced'),
blank=True,
null=True,
editable=False
@@ -76,6 +83,8 @@ class DataSource(JobsMixin, PrimaryModel):
class Meta:
ordering = ('name',)
verbose_name = _('data source')
verbose_name_plural = _('data sources')
def __str__(self):
return f'{self.name}'
@@ -97,6 +106,10 @@ class DataSource(JobsMixin, PrimaryModel):
def url_scheme(self):
return urlparse(self.source_url).scheme.lower()
@property
def backend_class(self):
return registry['data_backends'].get(self.type)
@property
def is_local(self):
return self.type == DataSourceTypeChoices.LOCAL
@@ -132,17 +145,15 @@ class DataSource(JobsMixin, PrimaryModel):
)
def get_backend(self):
backend_cls = registry['data_backends'].get(self.type)
backend_params = self.parameters or {}
return backend_cls(self.source_url, **backend_params)
return self.backend_class(self.source_url, **backend_params)
def sync(self):
"""
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
"""
if self.status == DataSourceStatusChoices.SYNCING:
raise SyncError(f"Cannot initiate sync; syncing already in progress.")
raise SyncError("Cannot initiate sync; syncing already in progress.")
# Emit the pre_sync signal
pre_sync.send(sender=self.__class__, instance=self)
@@ -151,7 +162,12 @@ class DataSource(JobsMixin, PrimaryModel):
DataSource.objects.filter(pk=self.pk).update(status=self.status)
# Replicate source data locally
backend = self.get_backend()
try:
backend = self.get_backend()
except ModuleNotFoundError as e:
raise SyncError(
f"There was an error initializing the backend. A dependency needs to be installed: {e}"
)
with backend.fetch() as local_path:
logger.debug(f'Syncing files from source root {local_path}')
@@ -200,6 +216,7 @@ class DataSource(JobsMixin, PrimaryModel):
# Emit the post_sync signal
post_sync.send(sender=self.__class__, instance=self)
sync.alters_data = True
def _walk(self, root):
"""
@@ -238,9 +255,11 @@ class DataFile(models.Model):
updated, or deleted only by calling DataSource.sync().
"""
created = models.DateTimeField(
verbose_name=_('created'),
auto_now_add=True
)
last_updated = models.DateTimeField(
verbose_name=_('last updated'),
editable=False
)
source = models.ForeignKey(
@@ -250,20 +269,23 @@ class DataFile(models.Model):
editable=False
)
path = models.CharField(
verbose_name=_('path'),
max_length=1000,
editable=False,
help_text=_("File path relative to the data source's root")
)
size = models.PositiveIntegerField(
editable=False
editable=False,
verbose_name=_('size')
)
hash = models.CharField(
verbose_name=_('hash'),
max_length=64,
editable=False,
validators=[
RegexValidator(regex='^[0-9a-f]{64}$', message=_("Length must be 64 hexadecimal characters."))
],
help_text=_("SHA256 hash of the file data")
help_text=_('SHA256 hash of the file data')
)
data = models.BinaryField()
@@ -280,6 +302,8 @@ class DataFile(models.Model):
indexes = [
models.Index(fields=('source', 'path'), name='core_datafile_source_path'),
]
verbose_name = _('data file')
verbose_name_plural = _('data files')
def __str__(self):
return self.path
@@ -289,8 +313,10 @@ class DataFile(models.Model):
@property
def data_as_string(self):
if not self.data:
return None
try:
return self.data.tobytes().decode('utf-8')
return self.data.decode('utf-8')
except UnicodeDecodeError:
return None
@@ -361,3 +387,5 @@ class AutoSyncRecord(models.Model):
indexes = (
models.Index(fields=('object_type', 'object_id')),
)
verbose_name = _('auto sync record')
verbose_name_plural = _('auto sync records')

View File

@@ -23,20 +23,24 @@ class ManagedFile(SyncedDataMixin, models.Model):
to provide additional functionality.
"""
created = models.DateTimeField(
verbose_name=_('created'),
auto_now_add=True
)
last_updated = models.DateTimeField(
verbose_name=_('last updated'),
editable=False,
blank=True,
null=True
)
file_root = models.CharField(
verbose_name=_('file root'),
max_length=1000,
choices=ManagedFileRootPathChoices
)
file_path = models.FilePathField(
verbose_name=_('file path'),
editable=False,
help_text=_("File path relative to the designated root path")
help_text=_('File path relative to the designated root path')
)
objects = RestrictedQuerySet.as_manager()
@@ -52,6 +56,8 @@ class ManagedFile(SyncedDataMixin, models.Model):
indexes = [
models.Index(fields=('file_root', 'file_path'), name='core_managedfile_root_path'),
]
verbose_name = _('managed file')
verbose_name_plural = _('managed files')
def __str__(self):
return self.name

View File

@@ -1,7 +1,7 @@
import uuid
import django_rq
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.validators import MinValueValidator
@@ -43,48 +43,57 @@ class Job(models.Model):
for_concrete_model=False
)
name = models.CharField(
verbose_name=_('name'),
max_length=200
)
created = models.DateTimeField(
verbose_name=_('created'),
auto_now_add=True
)
scheduled = models.DateTimeField(
verbose_name=_('scheduled'),
null=True,
blank=True
)
interval = models.PositiveIntegerField(
verbose_name=_('interval'),
blank=True,
null=True,
validators=(
MinValueValidator(1),
),
help_text=_("Recurrence interval (in minutes)")
help_text=_('Recurrence interval (in minutes)')
)
started = models.DateTimeField(
verbose_name=_('started'),
null=True,
blank=True
)
completed = models.DateTimeField(
verbose_name=_('completed'),
null=True,
blank=True
)
user = models.ForeignKey(
to=User,
to=settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
related_name='+',
blank=True,
null=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=30,
choices=JobStatusChoices,
default=JobStatusChoices.STATUS_PENDING
)
data = models.JSONField(
verbose_name=_('data'),
null=True,
blank=True
)
job_id = models.UUIDField(
verbose_name=_('job ID'),
unique=True
)
@@ -92,6 +101,8 @@ class Job(models.Model):
class Meta:
ordering = ['-created']
verbose_name = _('job')
verbose_name_plural = _('jobs')
def __str__(self):
return str(self.job_id)

View File

@@ -1,3 +1,4 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from core.models import *
@@ -11,11 +12,18 @@ __all__ = (
class DataSourceTable(NetBoxTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
type = columns.ChoiceFieldColumn()
status = columns.ChoiceFieldColumn()
enabled = columns.BooleanColumn()
type = columns.ChoiceFieldColumn(
verbose_name=_('Type'),
)
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
enabled = columns.BooleanColumn(
verbose_name=_('Enabled'),
)
tags = columns.TagColumn(
url_name='core:datasource_list'
)
@@ -34,12 +42,16 @@ class DataSourceTable(NetBoxTable):
class DataFileTable(NetBoxTable):
source = tables.Column(
verbose_name=_('Source'),
linkify=True
)
path = tables.Column(
verbose_name=_('Path'),
linkify=True
)
last_updated = columns.DateTimeColumn()
last_updated = columns.DateTimeColumn(
verbose_name=_('Last updated'),
)
actions = columns.ActionsColumn(
actions=('delete',)
)

View File

@@ -1,5 +1,5 @@
import django_tables2 as tables
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from netbox.tables import NetBoxTable, columns
from ..models import Job
@@ -7,23 +7,38 @@ from ..models import Job
class JobTable(NetBoxTable):
id = tables.Column(
verbose_name=_('ID'),
linkify=True
)
name = tables.Column(
verbose_name=_('Name'),
linkify=True
)
object_type = columns.ContentTypeColumn(
verbose_name=_('Type')
)
object = tables.Column(
verbose_name=_('Object'),
linkify=True
)
status = columns.ChoiceFieldColumn()
created = columns.DateTimeColumn()
scheduled = columns.DateTimeColumn()
interval = columns.DurationColumn()
started = columns.DateTimeColumn()
completed = columns.DateTimeColumn()
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
created = columns.DateTimeColumn(
verbose_name=_('Created'),
)
scheduled = columns.DateTimeColumn(
verbose_name=_('Scheduled'),
)
interval = columns.DurationColumn(
verbose_name=_('Interval'),
)
started = columns.DateTimeColumn(
verbose_name=_('Started'),
)
completed = columns.DateTimeColumn(
verbose_name=_('Completed'),
)
actions = columns.ActionsColumn(
actions=('delete',)
)

View File

@@ -25,4 +25,7 @@ urlpatterns = (
path('jobs/<int:pk>/', views.JobView.as_view(), name='job'),
path('jobs/<int:pk>/delete/', views.JobDeleteView.as_view(), name='job_delete'),
# Configuration
path('config/', views.ConfigView.as_view(), name='config'),
)

View File

@@ -1,6 +1,8 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from extras.models import ConfigRevision
from netbox.config import get_config
from netbox.views import generic
from netbox.views.generic.base import BaseObjectView
from utilities.utils import count_related
@@ -141,3 +143,19 @@ class JobBulkDeleteView(generic.BulkDeleteView):
queryset = Job.objects.all()
filterset = filtersets.JobFilterSet
table = tables.JobTable
#
# Config Revisions
#
class ConfigView(generic.ObjectView):
queryset = ConfigRevision.objects.all()
def get_object(self, **kwargs):
if config := self.queryset.first():
return config
# Instantiate a dummy default config if none has been created yet
return ConfigRevision(
data=get_config().defaults
)

View File

@@ -214,9 +214,9 @@ class RackSerializer(NetBoxModelSerializer):
model = Rack
fields = [
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
'asset_tag', 'type', 'width', 'u_height', 'weight', 'max_weight', 'weight_unit', 'desc_units',
'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags',
'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'weight', 'max_weight', 'weight_unit',
'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments',
'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
]
@@ -327,12 +327,28 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
device_count = serializers.IntegerField(read_only=True)
# Counter fields
console_port_template_count = serializers.IntegerField(read_only=True)
console_server_port_template_count = serializers.IntegerField(read_only=True)
power_port_template_count = serializers.IntegerField(read_only=True)
power_outlet_template_count = serializers.IntegerField(read_only=True)
interface_template_count = serializers.IntegerField(read_only=True)
front_port_template_count = serializers.IntegerField(read_only=True)
rear_port_template_count = serializers.IntegerField(read_only=True)
device_bay_template_count = serializers.IntegerField(read_only=True)
module_bay_template_count = serializers.IntegerField(read_only=True)
inventory_item_template_count = serializers.IntegerField(read_only=True)
class Meta:
model = DeviceType
fields = [
'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height',
'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
'console_port_template_count', 'console_server_port_template_count', 'power_port_template_count',
'power_outlet_template_count', 'interface_template_count', 'front_port_template_count',
'rear_port_template_count', 'device_bay_template_count', 'module_bay_template_count',
'inventory_item_template_count',
]
@@ -498,12 +514,18 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
allow_blank=True,
allow_null=True
)
rf_role = ChoiceField(
choices=WirelessRoleChoices,
required=False,
allow_blank=True,
allow_null=True
)
class Meta:
model = InterfaceTemplate
fields = [
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only',
'description', 'bridge', 'poe_mode', 'poe_type', 'created', 'last_updated',
'description', 'bridge', 'poe_mode', 'poe_type', 'rf_role', 'created', 'last_updated',
]
@@ -635,15 +657,16 @@ class PlatformSerializer(NetBoxModelSerializer):
class Meta:
model = Platform
fields = [
'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args',
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
]
class DeviceSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
device_type = NestedDeviceTypeSerializer()
device_role = NestedDeviceRoleSerializer()
role = NestedDeviceRoleSerializer()
device_role = NestedDeviceRoleSerializer(read_only=True, help_text='Deprecated in v3.6 in favor of `role`.')
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
platform = NestedPlatformSerializer(required=False, allow_null=True)
site = NestedSiteSerializer()
@@ -663,19 +686,35 @@ class DeviceSerializer(NetBoxModelSerializer):
primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
oob_ip = NestedIPAddressSerializer(required=False, allow_null=True)
parent_device = serializers.SerializerMethodField()
cluster = NestedClusterSerializer(required=False, allow_null=True)
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
vc_position = serializers.IntegerField(allow_null=True, max_value=255, min_value=0, default=None)
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
# Counter fields
console_port_count = serializers.IntegerField(read_only=True)
console_server_port_count = serializers.IntegerField(read_only=True)
power_port_count = serializers.IntegerField(read_only=True)
power_outlet_count = serializers.IntegerField(read_only=True)
interface_count = serializers.IntegerField(read_only=True)
front_port_count = serializers.IntegerField(read_only=True)
rear_port_count = serializers.IntegerField(read_only=True)
device_bay_count = serializers.IntegerField(read_only=True)
module_bay_count = serializers.IntegerField(read_only=True)
inventory_item_count = serializers.IntegerField(read_only=True)
class Meta:
model = Device
fields = [
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
'id', 'url', 'display', 'name', 'device_type', 'role', 'device_role', 'tenant', 'platform', 'serial',
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device',
'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags',
'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
'device_bay_count', 'module_bay_count', 'inventory_item_count',
]
@extend_schema_field(NestedDeviceSerializer)
@@ -689,16 +728,22 @@ class DeviceSerializer(NetBoxModelSerializer):
data['device_bay'] = NestedDeviceBaySerializer(instance=device_bay, context=context).data
return data
def get_device_role(self, obj):
return obj.role
class DeviceWithConfigContextSerializer(DeviceSerializer):
config_context = serializers.SerializerMethodField(read_only=True)
class Meta(DeviceSerializer.Meta):
fields = [
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
'id', 'url', 'display', 'name', 'device_type', 'role', 'device_role', 'tenant', 'platform', 'serial',
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device',
'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'config_context',
'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', 'console_port_count',
'console_server_port_count', 'power_port_count', 'power_outlet_count', 'interface_count',
'front_port_count', 'rear_port_count', 'device_bay_count', 'module_bay_count', 'inventory_item_count',
]
@extend_schema_field(serializers.JSONField(allow_null=True))
@@ -707,12 +752,13 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
class VirtualDeviceContextSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
device = NestedDeviceSerializer()
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
status = ChoiceField(choices=VirtualDeviceContextStatusChoices)
# Related object counts
interface_count = serializers.IntegerField(read_only=True)
@@ -741,10 +787,6 @@ class ModuleSerializer(NetBoxModelSerializer):
]
class DeviceNAPALMSerializer(serializers.Serializer):
method = serializers.JSONField()
#
# Device components
#
@@ -880,12 +922,12 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
parent = NestedInterfaceSerializer(required=False, allow_null=True)
bridge = NestedInterfaceSerializer(required=False, allow_null=True)
lag = NestedInterfaceSerializer(required=False, allow_null=True)
mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True, allow_null=True)
mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True)
duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True, allow_null=True)
rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True, allow_null=True)
rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True, allow_null=True)
poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True, allow_null=True)
poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True, allow_null=True)
rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True)
rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True)
poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True)
poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
@@ -907,9 +949,10 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
mac_address = serializers.CharField(
required=False,
default=None,
allow_blank=True,
allow_null=True
)
wwn = serializers.CharField(required=False, default=None)
wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
class Meta:
model = Interface
@@ -993,7 +1036,8 @@ class ModuleBaySerializer(NetBoxModelSerializer):
class Meta:
model = ModuleBay
fields = [
'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags', 'custom_fields',
'id', 'url', 'display', 'device', 'name', 'installed_module', 'label', 'position', 'description', 'tags',
'custom_fields',
'created', 'last_updated',
]
@@ -1136,13 +1180,15 @@ class CablePathSerializer(serializers.ModelSerializer):
class VirtualChassisSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
# Counter fields
member_count = serializers.IntegerField(read_only=True)
class Meta:
model = VirtualChassis
fields = [
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
'member_count', 'created', 'last_updated',
'created', 'last_updated', 'member_count',
]
@@ -1192,6 +1238,10 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
choices=PowerFeedPhaseChoices,
default=lambda: PowerFeedPhaseChoices.PHASE_SINGLE,
)
tenant = NestedTenantSerializer(
required=False,
allow_null=True
)
class Meta:
model = PowerFeed
@@ -1199,5 +1249,5 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'description',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
'tenant', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
]

View File

@@ -1,12 +1,12 @@
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.decorators import action
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.routers import APIRootView
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.viewsets import ViewSet
from circuits.models import Circuit
@@ -14,7 +14,6 @@ from dcim import filtersets
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
from dcim.models import *
from dcim.svg import CableTraceSVG
from extras.api.nested_serializers import NestedConfigTemplateSerializer
from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
from ipam.models import Prefix, VLAN
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
@@ -22,6 +21,7 @@ from netbox.api.metadata import ContentTypeMetadata
from netbox.api.pagination import StripCountAnnotationsPaginator
from netbox.api.renderers import TextRenderer
from netbox.api.viewsets import NetBoxModelViewSet
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
from netbox.constants import NESTED_SERIALIZER_PREFIX
from utilities.api import get_serializer_for_model
from utilities.utils import count_related
@@ -362,7 +362,7 @@ class InventoryItemTemplateViewSet(NetBoxModelViewSet):
class DeviceRoleViewSet(NetBoxModelViewSet):
queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate(
device_count=count_related(Device, 'device_role'),
device_count=count_related(Device, 'role'),
virtualmachine_count=count_related(VirtualMachine, 'role')
)
serializer_class = serializers.DeviceRoleSerializer
@@ -386,9 +386,14 @@ class PlatformViewSet(NetBoxModelViewSet):
# Devices/modules
#
class DeviceViewSet(ConfigContextQuerySetMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
class DeviceViewSet(
SequentialBulkCreatesMixin,
ConfigContextQuerySetMixin,
ConfigTemplateRenderMixin,
NetBoxModelViewSet
):
queryset = Device.objects.prefetch_related(
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
'device_type__manufacturer', 'role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
)
filterset_class = filtersets.DeviceFilterSet
@@ -574,9 +579,7 @@ class CableTerminationViewSet(NetBoxModelViewSet):
#
class VirtualChassisViewSet(NetBoxModelViewSet):
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
member_count=count_related(Device, 'virtual_chassis')
)
queryset = VirtualChassis.objects.prefetch_related('tags')
serializer_class = serializers.VirtualChassisSerializer
filterset_class = filtersets.VirtualChassisFilterSet
brief_prefetch_fields = ['master']
@@ -641,7 +644,10 @@ class ConnectedDeviceViewSet(ViewSet):
def get_view_name(self):
return "Connected Device Locator"
@extend_schema(responses={200: OpenApiTypes.OBJECT})
@extend_schema(
parameters=[_device_param, _interface_param],
responses={200: serializers.DeviceSerializer}
)
def list(self, request):
peer_device_name = request.query_params.get(self._device_param.name)

View File

@@ -9,7 +9,8 @@ class DCIMConfig(AppConfig):
def ready(self):
from . import signals, search
from .models import CableTermination
from .models import CableTermination, Device, DeviceType, VirtualChassis
from utilities.counters import connect_counters
# Register denormalized fields
denormalized.register(CableTermination, '_device', {
@@ -24,3 +25,6 @@ class DCIMConfig(AppConfig):
denormalized.register(CableTermination, '_location', {
'_site': 'site',
})
# Register counters
connect_counters(Device, DeviceType, VirtualChassis)

View File

@@ -1,3 +1,5 @@
from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
@@ -15,11 +17,11 @@ class SiteStatusChoices(ChoiceSet):
STATUS_RETIRED = 'retired'
CHOICES = [
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_STAGING, 'Staging', 'blue'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
(STATUS_RETIRED, 'Retired', 'red'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_STAGING, _('Staging'), 'blue'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
(STATUS_RETIRED, _('Retired'), 'red'),
]
@@ -60,13 +62,13 @@ class RackTypeChoices(ChoiceSet):
TYPE_WALLCABINET_VERTICAL = 'wall-cabinet-vertical'
CHOICES = (
(TYPE_2POST, '2-post frame'),
(TYPE_4POST, '4-post frame'),
(TYPE_CABINET, '4-post cabinet'),
(TYPE_WALLFRAME, 'Wall-mounted frame'),
(TYPE_WALLFRAME_VERTICAL, 'Wall-mounted frame (vertical)'),
(TYPE_WALLCABINET, 'Wall-mounted cabinet'),
(TYPE_WALLCABINET_VERTICAL, 'Wall-mounted cabinet (vertical)'),
(TYPE_2POST, _('2-post frame')),
(TYPE_4POST, _('4-post frame')),
(TYPE_CABINET, _('4-post cabinet')),
(TYPE_WALLFRAME, _('Wall-mounted frame')),
(TYPE_WALLFRAME_VERTICAL, _('Wall-mounted frame (vertical)')),
(TYPE_WALLCABINET, _('Wall-mounted cabinet')),
(TYPE_WALLCABINET_VERTICAL, _('Wall-mounted cabinet (vertical)')),
)
@@ -78,10 +80,10 @@ class RackWidthChoices(ChoiceSet):
WIDTH_23IN = 23
CHOICES = (
(WIDTH_10IN, '10 inches'),
(WIDTH_19IN, '19 inches'),
(WIDTH_21IN, '21 inches'),
(WIDTH_23IN, '23 inches'),
(WIDTH_10IN, _('10 inches')),
(WIDTH_19IN, _('19 inches')),
(WIDTH_21IN, _('21 inches')),
(WIDTH_23IN, _('23 inches')),
)
@@ -95,11 +97,11 @@ class RackStatusChoices(ChoiceSet):
STATUS_DEPRECATED = 'deprecated'
CHOICES = [
(STATUS_RESERVED, 'Reserved', 'yellow'),
(STATUS_AVAILABLE, 'Available', 'green'),
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_ACTIVE, 'Active', 'blue'),
(STATUS_DEPRECATED, 'Deprecated', 'red'),
(STATUS_RESERVED, _('Reserved'), 'yellow'),
(STATUS_AVAILABLE, _('Available'), 'green'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_ACTIVE, _('Active'), 'blue'),
(STATUS_DEPRECATED, _('Deprecated'), 'red'),
]
@@ -109,8 +111,8 @@ class RackDimensionUnitChoices(ChoiceSet):
UNIT_INCH = 'in'
CHOICES = (
(UNIT_MILLIMETER, 'Millimeters'),
(UNIT_INCH, 'Inches'),
(UNIT_MILLIMETER, _('Millimeters')),
(UNIT_INCH, _('Inches')),
)
@@ -135,8 +137,8 @@ class SubdeviceRoleChoices(ChoiceSet):
ROLE_CHILD = 'child'
CHOICES = (
(ROLE_PARENT, 'Parent'),
(ROLE_CHILD, 'Child'),
(ROLE_PARENT, _('Parent')),
(ROLE_CHILD, _('Child')),
)
@@ -150,8 +152,8 @@ class DeviceFaceChoices(ChoiceSet):
FACE_REAR = 'rear'
CHOICES = (
(FACE_FRONT, 'Front'),
(FACE_REAR, 'Rear'),
(FACE_FRONT, _('Front')),
(FACE_REAR, _('Rear')),
)
@@ -167,13 +169,13 @@ class DeviceStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = [
(STATUS_OFFLINE, 'Offline', 'gray'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_STAGED, 'Staged', 'blue'),
(STATUS_FAILED, 'Failed', 'red'),
(STATUS_INVENTORY, 'Inventory', 'purple'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
(STATUS_OFFLINE, _('Offline'), 'gray'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_STAGED, _('Staged'), 'blue'),
(STATUS_FAILED, _('Failed'), 'red'),
(STATUS_INVENTORY, _('Inventory'), 'purple'),
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
]
@@ -188,13 +190,13 @@ class DeviceAirflowChoices(ChoiceSet):
AIRFLOW_MIXED = 'mixed'
CHOICES = (
(AIRFLOW_FRONT_TO_REAR, 'Front to rear'),
(AIRFLOW_REAR_TO_FRONT, 'Rear to front'),
(AIRFLOW_LEFT_TO_RIGHT, 'Left to right'),
(AIRFLOW_RIGHT_TO_LEFT, 'Right to left'),
(AIRFLOW_SIDE_TO_REAR, 'Side to rear'),
(AIRFLOW_PASSIVE, 'Passive'),
(AIRFLOW_MIXED, 'Mixed'),
(AIRFLOW_FRONT_TO_REAR, _('Front to rear')),
(AIRFLOW_REAR_TO_FRONT, _('Rear to front')),
(AIRFLOW_LEFT_TO_RIGHT, _('Left to right')),
(AIRFLOW_RIGHT_TO_LEFT, _('Right to left')),
(AIRFLOW_SIDE_TO_REAR, _('Side to rear')),
(AIRFLOW_PASSIVE, _('Passive')),
(AIRFLOW_MIXED, _('Mixed')),
)
@@ -213,12 +215,12 @@ class ModuleStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = [
(STATUS_OFFLINE, 'Offline', 'gray'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_STAGED, 'Staged', 'blue'),
(STATUS_FAILED, 'Failed', 'red'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
(STATUS_OFFLINE, _('Offline'), 'gray'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_STAGED, _('Staged'), 'blue'),
(STATUS_FAILED, _('Failed'), 'red'),
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
]
@@ -318,6 +320,10 @@ class PowerPortTypeChoices(ChoiceSet):
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
# IEC 60906-1
TYPE_IEC_60906_1 = 'iec-60906-1'
TYPE_NBR_14136_10A = 'nbr-14136-10a'
TYPE_NBR_14136_20A = 'nbr-14136-20a'
# NEMA non-locking
TYPE_NEMA_115P = 'nema-1-15p'
TYPE_NEMA_515P = 'nema-5-15p'
@@ -429,7 +435,12 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
)),
('NEMA (Non-locking)', (
('IEC 60906-1', (
(TYPE_IEC_60906_1, 'IEC 60906-1'),
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
)),
(_('NEMA (Non-locking)'), (
(TYPE_NEMA_115P, 'NEMA 1-15P'),
(TYPE_NEMA_515P, 'NEMA 5-15P'),
(TYPE_NEMA_520P, 'NEMA 5-20P'),
@@ -451,7 +462,7 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_NEMA_1550P, 'NEMA 15-50P'),
(TYPE_NEMA_1560P, 'NEMA 15-60P'),
)),
('NEMA (Locking)', (
(_('NEMA (Locking)'), (
(TYPE_NEMA_L115P, 'NEMA L1-15P'),
(TYPE_NEMA_L515P, 'NEMA L5-15P'),
(TYPE_NEMA_L520P, 'NEMA L5-20P'),
@@ -474,7 +485,7 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_NEMA_L2130P, 'NEMA L21-30P'),
(TYPE_NEMA_L2230P, 'NEMA L22-30P'),
)),
('California Style', (
(_('California Style'), (
(TYPE_CS6361C, 'CS6361C'),
(TYPE_CS6365C, 'CS6365C'),
(TYPE_CS8165C, 'CS8165C'),
@@ -482,7 +493,7 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_CS8365C, 'CS8365C'),
(TYPE_CS8465C, 'CS8465C'),
)),
('International/ITA', (
(_('International/ITA'), (
(TYPE_ITA_C, 'ITA Type C (CEE 7/16)'),
(TYPE_ITA_E, 'ITA Type E (CEE 7/6)'),
(TYPE_ITA_F, 'ITA Type F (CEE 7/4)'),
@@ -512,7 +523,7 @@ class PowerPortTypeChoices(ChoiceSet):
('DC', (
(TYPE_DC, 'DC Terminal'),
)),
('Proprietary', (
(_('Proprietary'), (
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
(TYPE_NEUTRIK_POWERCON_32A, 'Neutrik powerCON (32A)'),
@@ -520,7 +531,7 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
)),
('Other', (
(_('Other'), (
(TYPE_HARDWIRED, 'Hardwired'),
(TYPE_OTHER, 'Other'),
)),
@@ -553,6 +564,10 @@ class PowerOutletTypeChoices(ChoiceSet):
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
# IEC 60906-1
TYPE_IEC_60906_1 = 'iec-60906-1'
TYPE_NBR_14136_10A = 'nbr-14136-10a'
TYPE_NBR_14136_20A = 'nbr-14136-20a'
# NEMA non-locking
TYPE_NEMA_115R = 'nema-1-15r'
TYPE_NEMA_515R = 'nema-5-15r'
@@ -657,7 +672,12 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
)),
('NEMA (Non-locking)', (
('IEC 60906-1', (
(TYPE_IEC_60906_1, 'IEC 60906-1'),
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
)),
(_('NEMA (Non-locking)'), (
(TYPE_NEMA_115R, 'NEMA 1-15R'),
(TYPE_NEMA_515R, 'NEMA 5-15R'),
(TYPE_NEMA_520R, 'NEMA 5-20R'),
@@ -679,7 +699,7 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_NEMA_1550R, 'NEMA 15-50R'),
(TYPE_NEMA_1560R, 'NEMA 15-60R'),
)),
('NEMA (Locking)', (
(_('NEMA (Locking)'), (
(TYPE_NEMA_L115R, 'NEMA L1-15R'),
(TYPE_NEMA_L515R, 'NEMA L5-15R'),
(TYPE_NEMA_L520R, 'NEMA L5-20R'),
@@ -702,7 +722,7 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_NEMA_L2130R, 'NEMA L21-30R'),
(TYPE_NEMA_L2230R, 'NEMA L22-30R'),
)),
('California Style', (
(_('California Style'), (
(TYPE_CS6360C, 'CS6360C'),
(TYPE_CS6364C, 'CS6364C'),
(TYPE_CS8164C, 'CS8164C'),
@@ -710,7 +730,7 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_CS8364C, 'CS8364C'),
(TYPE_CS8464C, 'CS8464C'),
)),
('ITA/International', (
(_('ITA/International'), (
(TYPE_ITA_E, 'ITA Type E (CEE 7/5)'),
(TYPE_ITA_F, 'ITA Type F (CEE 7/3)'),
(TYPE_ITA_G, 'ITA Type G (BS 1363)'),
@@ -732,7 +752,7 @@ class PowerOutletTypeChoices(ChoiceSet):
('DC', (
(TYPE_DC, 'DC Terminal'),
)),
('Proprietary', (
(_('Proprietary'), (
(TYPE_HDOT_CX, 'HDOT Cx'),
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
@@ -741,7 +761,7 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
(TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
)),
('Other', (
(_('Other'), (
(TYPE_HARDWIRED, 'Hardwired'),
(TYPE_OTHER, 'Other'),
)),
@@ -771,9 +791,9 @@ class InterfaceKindChoices(ChoiceSet):
KIND_WIRELESS = 'wireless'
CHOICES = (
(KIND_PHYSICAL, 'Physical'),
(KIND_VIRTUAL, 'Virtual'),
(KIND_WIRELESS, 'Wireless'),
(KIND_PHYSICAL, _('Physical')),
(KIND_VIRTUAL, _('Virtual')),
(KIND_WIRELESS, _('Wireless')),
)
@@ -809,12 +829,18 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_100GE_CFP4 = '100gbase-x-cfp4'
TYPE_100GE_CXP = '100gbase-x-cxp'
TYPE_100GE_CPAK = '100gbase-x-cpak'
TYPE_100GE_DSFP = '100gbase-x-dsfp'
TYPE_100GE_SFP_DD = '100gbase-x-sfpdd'
TYPE_100GE_QSFP28 = '100gbase-x-qsfp28'
TYPE_100GE_QSFP_DD = '100gbase-x-qsfpdd'
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
TYPE_200GE_QSFP56 = '200gbase-x-qsfp56'
TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd'
TYPE_400GE_CFP2 = '400gbase-x-cfp2'
TYPE_400GE_QSFP112 = '400gbase-x-qsfp112'
TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd'
TYPE_400GE_OSFP = '400gbase-x-osfp'
TYPE_400GE_OSFP_RHS = '400gbase-x-osfp-rhs'
TYPE_400GE_CDFP = '400gbase-x-cdfp'
TYPE_400GE_CFP8 = '400gbase-x-cfp8'
TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd'
@@ -918,15 +944,15 @@ class InterfaceTypeChoices(ChoiceSet):
CHOICES = (
(
'Virtual interfaces',
_('Virtual interfaces'),
(
(TYPE_VIRTUAL, 'Virtual'),
(TYPE_BRIDGE, 'Bridge'),
(TYPE_LAG, 'Link Aggregation Group (LAG)'),
(TYPE_VIRTUAL, _('Virtual')),
(TYPE_BRIDGE, _('Bridge')),
(TYPE_LAG, _('Link Aggregation Group (LAG)')),
),
),
(
'Ethernet (fixed)',
_('Ethernet (fixed)'),
(
(TYPE_100ME_FX, '100BASE-FX (10/100ME FIBER)'),
(TYPE_100ME_LFX, '100BASE-LFX (10/100ME FIBER)'),
@@ -940,7 +966,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Ethernet (modular)',
_('Ethernet (modular)'),
(
(TYPE_1GE_GBIC, 'GBIC (1GE)'),
(TYPE_1GE_SFP, 'SFP (1GE)'),
@@ -955,14 +981,20 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_100GE_CFP, 'CFP (100GE)'),
(TYPE_100GE_CFP2, 'CFP2 (100GE)'),
(TYPE_200GE_CFP2, 'CFP2 (200GE)'),
(TYPE_400GE_CFP2, 'CFP2 (400GE)'),
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
(TYPE_100GE_CXP, 'CXP (100GE)'),
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
(TYPE_100GE_DSFP, 'DSFP (100GE)'),
(TYPE_100GE_SFP_DD, 'SFP-DD (100GE)'),
(TYPE_100GE_QSFP28, 'QSFP28 (100GE)'),
(TYPE_100GE_QSFP_DD, 'QSFP-DD (100GE)'),
(TYPE_200GE_QSFP56, 'QSFP56 (200GE)'),
(TYPE_200GE_QSFP_DD, 'QSFP-DD (200GE)'),
(TYPE_400GE_QSFP112, 'QSFP112 (400GE)'),
(TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'),
(TYPE_400GE_OSFP, 'OSFP (400GE)'),
(TYPE_400GE_OSFP_RHS, 'OSFP-RHS (400GE)'),
(TYPE_400GE_CDFP, 'CDFP (400GE)'),
(TYPE_400GE_CFP8, 'CPF8 (400GE)'),
(TYPE_800GE_QSFP_DD, 'QSFP-DD (800GE)'),
@@ -970,7 +1002,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Ethernet (backplane)',
_('Ethernet (backplane)'),
(
(TYPE_1GE_KX, '1000BASE-KX (1GE)'),
(TYPE_10GE_KR, '10GBASE-KR (10GE)'),
@@ -984,7 +1016,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Wireless',
_('Wireless'),
(
(TYPE_80211A, 'IEEE 802.11a'),
(TYPE_80211G, 'IEEE 802.11b/g'),
@@ -998,7 +1030,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Cellular',
_('Cellular'),
(
(TYPE_GSM, 'GSM'),
(TYPE_CDMA, 'CDMA'),
@@ -1045,7 +1077,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Serial',
_('Serial'),
(
(TYPE_T1, 'T1 (1.544 Mbps)'),
(TYPE_E1, 'E1 (2.048 Mbps)'),
@@ -1060,7 +1092,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Coaxial',
_('Coaxial'),
(
(TYPE_DOCSIS, 'DOCSIS'),
)
@@ -1077,7 +1109,7 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Stacking',
_('Stacking'),
(
(TYPE_STACKWISE, 'Cisco StackWise'),
(TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'),
@@ -1096,9 +1128,9 @@ class InterfaceTypeChoices(ChoiceSet):
)
),
(
'Other',
_('Other'),
(
(TYPE_OTHER, 'Other'),
(TYPE_OTHER, _('Other')),
)
),
)
@@ -1115,6 +1147,8 @@ class InterfaceSpeedChoices(ChoiceSet):
(25000000, '25 Gbps'),
(40000000, '40 Gbps'),
(100000000, '100 Gbps'),
(200000000, '200 Gbps'),
(400000000, '400 Gbps'),
]
@@ -1125,9 +1159,9 @@ class InterfaceDuplexChoices(ChoiceSet):
DUPLEX_AUTO = 'auto'
CHOICES = (
(DUPLEX_HALF, 'Half'),
(DUPLEX_FULL, 'Full'),
(DUPLEX_AUTO, 'Auto'),
(DUPLEX_HALF, _('Half')),
(DUPLEX_FULL, _('Full')),
(DUPLEX_AUTO, _('Auto')),
)
@@ -1138,9 +1172,9 @@ class InterfaceModeChoices(ChoiceSet):
MODE_TAGGED_ALL = 'tagged-all'
CHOICES = (
(MODE_ACCESS, 'Access'),
(MODE_TAGGED, 'Tagged'),
(MODE_TAGGED_ALL, 'Tagged (All)'),
(MODE_ACCESS, _('Access')),
(MODE_TAGGED, _('Tagged')),
(MODE_TAGGED_ALL, _('Tagged (All)')),
)
@@ -1169,7 +1203,7 @@ class InterfacePoETypeChoices(ChoiceSet):
CHOICES = (
(
'IEEE Standard',
_('IEEE Standard'),
(
(TYPE_1_8023AF, '802.3af (Type 1)'),
(TYPE_2_8023AT, '802.3at (Type 2)'),
@@ -1178,12 +1212,12 @@ class InterfacePoETypeChoices(ChoiceSet):
)
),
(
'Passive',
_('Passive'),
(
(PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'),
(PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'),
(PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'),
(PASSIVE_48V_4PAIR, 'Passive 48V (4-pair)'),
(PASSIVE_24V_2PAIR, _('Passive 24V (2-pair)')),
(PASSIVE_24V_4PAIR, _('Passive 24V (4-pair)')),
(PASSIVE_48V_2PAIR, _('Passive 48V (2-pair)')),
(PASSIVE_48V_4PAIR, _('Passive 48V (4-pair)')),
)
),
)
@@ -1245,7 +1279,7 @@ class PortTypeChoices(ChoiceSet):
CHOICES = (
(
'Copper',
_('Copper'),
(
(TYPE_8P8C, '8P8C'),
(TYPE_8P6C, '8P6C'),
@@ -1268,7 +1302,7 @@ class PortTypeChoices(ChoiceSet):
),
),
(
'Fiber Optic',
_('Fiber Optic'),
(
(TYPE_FC, 'FC'),
(TYPE_LC, 'LC'),
@@ -1301,9 +1335,9 @@ class PortTypeChoices(ChoiceSet):
),
),
(
'Other',
_('Other'),
(
(TYPE_OTHER, 'Other'),
(TYPE_OTHER, _('Other')),
)
)
)
@@ -1341,7 +1375,7 @@ class CableTypeChoices(ChoiceSet):
CHOICES = (
(
'Copper', (
_('Copper'), (
(TYPE_CAT3, 'CAT3'),
(TYPE_CAT5, 'CAT5'),
(TYPE_CAT5E, 'CAT5e'),
@@ -1357,7 +1391,7 @@ class CableTypeChoices(ChoiceSet):
),
),
(
'Fiber', (
_('Fiber'), (
(TYPE_MMF, 'Multimode Fiber'),
(TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
@@ -1370,7 +1404,7 @@ class CableTypeChoices(ChoiceSet):
(TYPE_AOC, 'Active Optical Cabling (AOC)'),
),
),
(TYPE_POWER, 'Power'),
(TYPE_POWER, _('Power')),
)
@@ -1381,9 +1415,9 @@ class LinkStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
(STATUS_CONNECTED, 'Connected', 'green'),
(STATUS_PLANNED, 'Planned', 'blue'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
(STATUS_CONNECTED, _('Connected'), 'green'),
(STATUS_PLANNED, _('Planned'), 'blue'),
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
)
@@ -1400,12 +1434,12 @@ class CableLengthUnitChoices(ChoiceSet):
UNIT_INCH = 'in'
CHOICES = (
(UNIT_KILOMETER, 'Kilometers'),
(UNIT_METER, 'Meters'),
(UNIT_CENTIMETER, 'Centimeters'),
(UNIT_MILE, 'Miles'),
(UNIT_FOOT, 'Feet'),
(UNIT_INCH, 'Inches'),
(UNIT_KILOMETER, _('Kilometers')),
(UNIT_METER, _('Meters')),
(UNIT_CENTIMETER, _('Centimeters')),
(UNIT_MILE, _('Miles')),
(UNIT_FOOT, _('Feet')),
(UNIT_INCH, _('Inches')),
)
@@ -1420,10 +1454,10 @@ class WeightUnitChoices(ChoiceSet):
UNIT_OUNCE = 'oz'
CHOICES = (
(UNIT_KILOGRAM, 'Kilograms'),
(UNIT_GRAM, 'Grams'),
(UNIT_POUND, 'Pounds'),
(UNIT_OUNCE, 'Ounces'),
(UNIT_KILOGRAM, _('Kilograms')),
(UNIT_GRAM, _('Grams')),
(UNIT_POUND, _('Pounds')),
(UNIT_OUNCE, _('Ounces')),
)
@@ -1456,10 +1490,10 @@ class PowerFeedStatusChoices(ChoiceSet):
STATUS_FAILED = 'failed'
CHOICES = [
(STATUS_OFFLINE, 'Offline', 'gray'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_PLANNED, 'Planned', 'blue'),
(STATUS_FAILED, 'Failed', 'red'),
(STATUS_OFFLINE, _('Offline'), 'gray'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_PLANNED, _('Planned'), 'blue'),
(STATUS_FAILED, _('Failed'), 'red'),
]
@@ -1469,8 +1503,8 @@ class PowerFeedTypeChoices(ChoiceSet):
TYPE_REDUNDANT = 'redundant'
CHOICES = (
(TYPE_PRIMARY, 'Primary', 'green'),
(TYPE_REDUNDANT, 'Redundant', 'cyan'),
(TYPE_PRIMARY, _('Primary'), 'green'),
(TYPE_REDUNDANT, _('Redundant'), 'cyan'),
)
@@ -1491,8 +1525,8 @@ class PowerFeedPhaseChoices(ChoiceSet):
PHASE_3PHASE = 'three-phase'
CHOICES = (
(PHASE_SINGLE, 'Single phase'),
(PHASE_3PHASE, 'Three-phase'),
(PHASE_SINGLE, _('Single phase')),
(PHASE_3PHASE, _('Three-phase')),
)
@@ -1507,7 +1541,7 @@ class VirtualDeviceContextStatusChoices(ChoiceSet):
STATUS_OFFLINE = 'offline'
CHOICES = [
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_OFFLINE, 'Offline', 'red'),
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_PLANNED, _('Planned'), 'cyan'),
(STATUS_OFFLINE, _('Offline'), 'red'),
]

View File

@@ -11,11 +11,14 @@ DEVICETYPE_IMAGE_FORMATS = 'image/bmp,image/gif,image/jpeg,image/png,image/tiff,
#
RACK_U_HEIGHT_DEFAULT = 42
RACK_U_HEIGHT_MAX = 100
RACK_ELEVATION_BORDER_WIDTH = 2
RACK_ELEVATION_DEFAULT_LEGEND_WIDTH = 30
RACK_ELEVATION_DEFAULT_MARGIN_WIDTH = 15
RACK_STARTING_UNIT_DEFAULT = 1
#
# RearPorts

View File

@@ -6,7 +6,6 @@ from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
from .lookups import PathContains
__all__ = (
'ASNField',
'MACAddressField',
'PathField',
'WWNField',

View File

@@ -1,5 +1,5 @@
import django_filters
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.utils.translation import gettext as _
from extras.filtersets import LocalConfigContextFilterSet
@@ -323,8 +323,8 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
class Meta:
model = Rack
fields = [
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit'
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit'
]
def search(self, queryset, name, value):
@@ -395,12 +395,12 @@ class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
label=_('Location (slug)'),
)
user_id = django_filters.ModelMultipleChoiceFilter(
queryset=User.objects.all(),
queryset=get_user_model().objects.all(),
label=_('User (ID)'),
)
user = django_filters.ModelMultipleChoiceFilter(
field_name='user__username',
queryset=User.objects.all(),
queryset=get_user_model().objects.all(),
to_field_name='username',
label=_('User (name)'),
)
@@ -696,6 +696,9 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
poe_type = django_filters.MultipleChoiceFilter(
choices=InterfacePoETypeChoices
)
rf_role = django_filters.MultipleChoiceFilter(
choices=WirelessRoleChoices
)
class Meta:
model = InterfaceTemplate
@@ -811,7 +814,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
fields = ['id', 'name', 'slug', 'description']
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
@@ -837,12 +840,12 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
label=_('Device type (ID)'),
)
role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device_role_id',
field_name='role_id',
queryset=DeviceRole.objects.all(),
label=_('Role (ID)'),
)
role = django_filters.ModelMultipleChoiceFilter(
field_name='device_role__slug',
field_name='role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label=_('Role (slug)'),
@@ -941,6 +944,10 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
method='_has_primary_ip',
label=_('Has a primary IP'),
)
has_oob_ip = django_filters.BooleanFilter(
method='_has_oob_ip',
label=_('Has an out-of-band IP'),
)
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_chassis',
queryset=VirtualChassis.objects.all(),
@@ -996,10 +1003,15 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
queryset=IPAddress.objects.all(),
label=_('Primary IPv6 (ID)'),
)
oob_ip_id = django_filters.ModelMultipleChoiceFilter(
field_name='oob_ip',
queryset=IPAddress.objects.all(),
label=_('OOB IP (ID)'),
)
class Meta:
model = Device
fields = ['id', 'asset_tag', 'face', 'position', 'airflow', 'vc_position', 'vc_priority']
fields = ['id', 'asset_tag', 'face', 'position', 'latitude', 'longitude', 'airflow', 'vc_position', 'vc_priority']
def search(self, queryset, name, value):
if not value.strip():
@@ -1020,6 +1032,12 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
return queryset.filter(params)
return queryset.exclude(params)
def _has_oob_ip(self, queryset, name, value):
params = Q(oob_ip__isnull=False)
if value:
return queryset.filter(params)
return queryset.exclude(params)
def _virtual_chassis_member(self, queryset, name, value):
return queryset.exclude(virtual_chassis__isnull=value)
@@ -1077,10 +1095,13 @@ class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(identifier=value.strip())
).distinct()
qs_filter = Q(name__icontains=value)
try:
qs_filter |= Q(identifier=int(value))
except ValueError:
pass
return queryset.filter(qs_filter).distinct()
def _has_primary_ip(self, queryset, name, value):
params = Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False)
@@ -1219,6 +1240,28 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name',
label=_('Device (name)'),
)
device_type_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_type',
queryset=DeviceType.objects.all(),
label=_('Device type (ID)'),
)
device_type = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_type__model',
queryset=DeviceType.objects.all(),
to_field_name='model',
label=_('Device type (model)'),
)
role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__role',
queryset=DeviceRole.objects.all(),
label=_('Device role (ID)'),
)
role = django_filters.ModelMultipleChoiceFilter(
field_name='device__role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label=_('Device role (slug)'),
)
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__virtual_chassis',
queryset=VirtualChassis.objects.all(),
@@ -1230,6 +1273,18 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name',
label=_('Virtual Chassis'),
)
# TODO: Remove in v4.0
device_role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__role',
queryset=DeviceRole.objects.all(),
label=_('Device role (ID)'),
)
device_role = django_filters.ModelMultipleChoiceFilter(
field_name='device__role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label=_('Device role (slug)'),
)
def search(self, queryset, name, value):
if not value.strip():
@@ -1407,17 +1462,15 @@ class InterfaceFilterSet(
PathEndpointFilterSet,
CommonInterfaceFilterSet
):
# Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis
# members
device = MultiValueCharFilter(
method='filter_device',
virtual_chassis_member = MultiValueCharFilter(
method='filter_virtual_chassis_member',
field_name='name',
label=_('Device'),
label=_('Virtual Chassis Interfaces for Device')
)
device_id = MultiValueNumberFilter(
method='filter_device_id',
virtual_chassis_member_id = MultiValueNumberFilter(
method='filter_virtual_chassis_member',
field_name='pk',
label=_('Device (ID)'),
label=_('Virtual Chassis Interfaces for Device (ID)')
)
kind = django_filters.CharFilter(
method='filter_kind',
@@ -1485,23 +1538,11 @@ class InterfaceFilterSet(
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'cable_end',
]
def filter_device(self, queryset, name, value):
def filter_virtual_chassis_member(self, queryset, name, value):
try:
devices = Device.objects.filter(**{'{}__in'.format(name): value})
vc_interface_ids = []
for device in devices:
vc_interface_ids.extend(device.vc_interfaces().values_list('id', flat=True))
return queryset.filter(pk__in=vc_interface_ids)
except Device.DoesNotExist:
return queryset.none()
def filter_device_id(self, queryset, name, id_list):
# Include interfaces belonging to peer virtual chassis members
vc_interface_ids = []
try:
devices = Device.objects.filter(pk__in=id_list)
for device in devices:
vc_interface_ids += device.vc_interfaces(if_master=False).values_list('id', flat=True)
for device in Device.objects.filter(**{f'{name}__in': value}):
vc_interface_ids.extend(device.vc_interfaces(if_master=False).values_list('id', flat=True))
return queryset.filter(pk__in=vc_interface_ids)
except Device.DoesNotExist:
return queryset.none()
@@ -1837,7 +1878,7 @@ class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
return queryset.filter(qs_filter)
class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet, TenancyFilterSet):
region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='power_panel__site__region',

View File

@@ -1,7 +1,7 @@
from django import forms
from dcim.models import *
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from extras.forms import CustomFieldsMixin
from extras.models import Tag
from utilities.forms import BootstrapMixin, form_from_model
@@ -32,10 +32,12 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCre
widget=forms.MultipleHiddenInput()
)
description = forms.CharField(
label=_('Description'),
max_length=100,
required=False
)
tags = DynamicModelMultipleChoiceField(
label=_('Tags'),
queryset=Tag.objects.all(),
required=False
)
@@ -76,14 +78,14 @@ class PowerOutletBulkCreateForm(
class InterfaceBulkCreateForm(
form_from_model(Interface, [
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type',
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type', 'rf_role'
]),
DeviceBulkAddComponentForm
):
model = Interface
field_order = (
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
'poe_type', 'mark_connected', 'description', 'tags',
'poe_type', 'mark_connected', 'rf_role', 'description', 'tags',
)

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.forms.array import SimpleArrayField
from django.core.exceptions import ObjectDoesNotExist
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
@@ -56,6 +56,7 @@ __all__ = (
class RegionImportForm(NetBoxModelImportForm):
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=Region.objects.all(),
required=False,
to_field_name='name',
@@ -69,6 +70,7 @@ class RegionImportForm(NetBoxModelImportForm):
class SiteGroupImportForm(NetBoxModelImportForm):
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=SiteGroup.objects.all(),
required=False,
to_field_name='name',
@@ -82,22 +84,26 @@ class SiteGroupImportForm(NetBoxModelImportForm):
class SiteImportForm(NetBoxModelImportForm):
status = CSVChoiceField(
label=_('Status'),
choices=SiteStatusChoices,
help_text=_('Operational status')
)
region = CSVModelChoiceField(
label=_('Region'),
queryset=Region.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned region')
)
group = CSVModelChoiceField(
label=_('Group'),
queryset=SiteGroup.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned group')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
@@ -112,18 +118,22 @@ class SiteImportForm(NetBoxModelImportForm):
)
help_texts = {
'time_zone': mark_safe(
_('Time zone (<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">available options</a>)')
'{} (<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">{}</a>)'.format(
_('Time zone'), _('available options')
)
)
}
class LocationImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
help_text=_('Assigned site')
)
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=Location.objects.all(),
required=False,
to_field_name='name',
@@ -133,10 +143,12 @@ class LocationImportForm(NetBoxModelImportForm):
}
)
status = CSVChoiceField(
label=_('Status'),
choices=LocationStatusChoices,
help_text=_('Operational status')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
@@ -155,51 +167,60 @@ class RackRoleImportForm(NetBoxModelImportForm):
model = RackRole
fields = ('name', 'slug', 'color', 'description', 'tags')
help_texts = {
'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
}
class RackImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name'
)
location = CSVModelChoiceField(
label=_('Location'),
queryset=Location.objects.all(),
required=False,
to_field_name='name'
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Name of assigned tenant')
)
status = CSVChoiceField(
label=_('Status'),
choices=RackStatusChoices,
help_text=_('Operational status')
)
role = CSVModelChoiceField(
label=_('Role'),
queryset=RackRole.objects.all(),
required=False,
to_field_name='name',
help_text=_('Name of assigned role')
)
type = CSVChoiceField(
label=_('Type'),
choices=RackTypeChoices,
required=False,
help_text=_('Rack type')
)
width = forms.ChoiceField(
label=_('Width'),
choices=RackWidthChoices,
help_text=_('Rail-to-rail width (in inches)')
)
outer_unit = CSVChoiceField(
label=_('Outer unit'),
choices=RackDimensionUnitChoices,
required=False,
help_text=_('Unit for outer dimensions')
)
weight_unit = CSVChoiceField(
label=_('Weight unit'),
choices=WeightUnitChoices,
required=False,
help_text=_('Unit for rack weights')
@@ -225,27 +246,32 @@ class RackImportForm(NetBoxModelImportForm):
class RackReservationImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
help_text=_('Parent site')
)
location = CSVModelChoiceField(
label=_('Location'),
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text=_("Rack's location (if any)")
)
rack = CSVModelChoiceField(
label=_('Rack'),
queryset=Rack.objects.all(),
to_field_name='name',
help_text=_('Rack')
)
units = SimpleArrayField(
label=_('Units'),
base_field=forms.IntegerField(),
required=True,
help_text=_('Comma-separated list of individual unit numbers')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
@@ -282,21 +308,25 @@ class ManufacturerImportForm(NetBoxModelImportForm):
class DeviceTypeImportForm(NetBoxModelImportForm):
manufacturer = forms.ModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
to_field_name='name',
help_text=_('The manufacturer which produces this device type')
)
default_platform = forms.ModelChoiceField(
label=_('Default platform'),
queryset=Platform.objects.all(),
to_field_name='name',
required=False,
help_text=_('The default platform for devices of this type (optional)')
)
weight = forms.DecimalField(
label=_('Weight'),
required=False,
help_text=_('Device weight'),
)
weight_unit = CSVChoiceField(
label=_('Weight unit'),
choices=WeightUnitChoices,
required=False,
help_text=_('Unit for device weight')
@@ -306,20 +336,23 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
model = DeviceType
fields = [
'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments',
'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', 'tags',
]
class ModuleTypeImportForm(NetBoxModelImportForm):
manufacturer = forms.ModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
to_field_name='name'
)
weight = forms.DecimalField(
label=_('Weight'),
required=False,
help_text=_('Module weight'),
)
weight_unit = CSVChoiceField(
label=_('Weight unit'),
choices=WeightUnitChoices,
required=False,
help_text=_('Unit for module weight')
@@ -327,11 +360,12 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
class Meta:
model = ModuleType
fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments']
fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments', 'tags']
class DeviceRoleImportForm(NetBoxModelImportForm):
config_template = CSVModelChoiceField(
label=_('Config template'),
queryset=ConfigTemplate.objects.all(),
to_field_name='name',
required=False,
@@ -343,19 +377,21 @@ class DeviceRoleImportForm(NetBoxModelImportForm):
model = DeviceRole
fields = ('name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags')
help_texts = {
'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
}
class PlatformImportForm(NetBoxModelImportForm):
slug = SlugField()
manufacturer = CSVModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
required=False,
to_field_name='name',
help_text=_('Limit platform assignments to this manufacturer')
)
config_template = CSVModelChoiceField(
label=_('Config template'),
queryset=ConfigTemplate.objects.all(),
to_field_name='name',
required=False,
@@ -365,49 +401,57 @@ class PlatformImportForm(NetBoxModelImportForm):
class Meta:
model = Platform
fields = (
'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags',
'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags',
)
class BaseDeviceImportForm(NetBoxModelImportForm):
device_role = CSVModelChoiceField(
role = CSVModelChoiceField(
label=_('Device role'),
queryset=DeviceRole.objects.all(),
to_field_name='name',
help_text=_('Assigned role')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned tenant')
)
manufacturer = CSVModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
to_field_name='name',
help_text=_('Device type manufacturer')
)
device_type = CSVModelChoiceField(
label=_('Device type'),
queryset=DeviceType.objects.all(),
to_field_name='model',
help_text=_('Device type model')
)
platform = CSVModelChoiceField(
label=_('Platform'),
queryset=Platform.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned platform')
)
status = CSVChoiceField(
label=_('Status'),
choices=DeviceStatusChoices,
help_text=_('Operational status')
)
virtual_chassis = CSVModelChoiceField(
label=_('Virtual chassis'),
queryset=VirtualChassis.objects.all(),
to_field_name='name',
required=False,
help_text=_('Virtual chassis')
)
cluster = CSVModelChoiceField(
label=_('Cluster'),
queryset=Cluster.objects.all(),
to_field_name='name',
required=False,
@@ -430,45 +474,53 @@ class BaseDeviceImportForm(NetBoxModelImportForm):
class DeviceImportForm(BaseDeviceImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
help_text=_('Assigned site')
)
location = CSVModelChoiceField(
label=_('Location'),
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text=_("Assigned location (if any)")
)
rack = CSVModelChoiceField(
label=_('Rack'),
queryset=Rack.objects.all(),
to_field_name='name',
required=False,
help_text=_("Assigned rack (if any)")
)
face = CSVChoiceField(
label=_('Face'),
choices=DeviceFaceChoices,
required=False,
help_text=_('Mounted rack face')
)
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=Device.objects.all(),
to_field_name='name',
required=False,
help_text=_('Parent device (for child devices)')
)
device_bay = CSVModelChoiceField(
label=_('Device bay'),
queryset=DeviceBay.objects.all(),
to_field_name='name',
required=False,
help_text=_('Device bay in which this device is installed (for child devices)')
)
airflow = CSVChoiceField(
label=_('Airflow'),
choices=DeviceAirflowChoices,
required=False,
help_text=_('Airflow direction')
)
config_template = CSVModelChoiceField(
label=_('Config template'),
queryset=ConfigTemplate.objects.all(),
to_field_name='name',
required=False,
@@ -477,9 +529,10 @@ class DeviceImportForm(BaseDeviceImportForm):
class Meta(BaseDeviceImportForm.Meta):
fields = [
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'site', 'location', 'rack', 'position', 'face', 'parent', 'device_bay', 'airflow', 'virtual_chassis',
'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments', 'tags',
'name', 'role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent', 'device_bay', 'airflow',
'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments',
'tags',
]
def __init__(self, data=None, *args, **kwargs):
@@ -522,29 +575,35 @@ class DeviceImportForm(BaseDeviceImportForm):
class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name',
help_text=_('The device in which this module is installed')
)
module_bay = CSVModelChoiceField(
label=_('Module bay'),
queryset=ModuleBay.objects.all(),
to_field_name='name',
help_text=_('The module bay in which this module is installed')
)
module_type = CSVModelChoiceField(
label=_('Module type'),
queryset=ModuleType.objects.all(),
to_field_name='model',
help_text=_('The type of module')
)
status = CSVChoiceField(
label=_('Status'),
choices=ModuleStatusChoices,
help_text=_('Operational status')
)
replicate_components = forms.BooleanField(
label=_('Replicate components'),
required=False,
help_text=_('Automatically populate components associated with this module type (enabled by default)')
)
adopt_components = forms.BooleanField(
label=_('Adopt components'),
required=False,
help_text=_('Adopt already existing components')
)
@@ -578,15 +637,18 @@ class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
class ConsolePortImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
type = CSVChoiceField(
label=_('Type'),
choices=ConsolePortTypeChoices,
required=False,
help_text=_('Port type')
)
speed = CSVTypedChoiceField(
label=_('Speed'),
choices=ConsolePortSpeedChoices,
coerce=int,
empty_value=None,
@@ -601,15 +663,18 @@ class ConsolePortImportForm(NetBoxModelImportForm):
class ConsoleServerPortImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
type = CSVChoiceField(
label=_('Type'),
choices=ConsolePortTypeChoices,
required=False,
help_text=_('Port type')
)
speed = CSVTypedChoiceField(
label=_('Speed'),
choices=ConsolePortSpeedChoices,
coerce=int,
empty_value=None,
@@ -624,10 +689,12 @@ class ConsoleServerPortImportForm(NetBoxModelImportForm):
class PowerPortImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
type = CSVChoiceField(
label=_('Type'),
choices=PowerPortTypeChoices,
required=False,
help_text=_('Port type')
@@ -642,21 +709,25 @@ class PowerPortImportForm(NetBoxModelImportForm):
class PowerOutletImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
type = CSVChoiceField(
label=_('Type'),
choices=PowerOutletTypeChoices,
required=False,
help_text=_('Outlet type')
)
power_port = CSVModelChoiceField(
label=_('Power port'),
queryset=PowerPort.objects.all(),
required=False,
to_field_name='name',
help_text=_('Local power port which feeds this outlet')
)
feed_leg = CSVChoiceField(
label=_('Feed lag'),
choices=PowerOutletFeedLegChoices,
required=False,
help_text=_('Electrical phase (for three-phase circuits)')
@@ -691,63 +762,77 @@ class PowerOutletImportForm(NetBoxModelImportForm):
class InterfaceImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=Interface.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent interface')
)
bridge = CSVModelChoiceField(
label=_('Bridge'),
queryset=Interface.objects.all(),
required=False,
to_field_name='name',
help_text=_('Bridged interface')
)
lag = CSVModelChoiceField(
label=_('Lag'),
queryset=Interface.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent LAG interface')
)
vdcs = CSVModelMultipleChoiceField(
label=_('Vdcs'),
queryset=VirtualDeviceContext.objects.all(),
required=False,
to_field_name='name',
help_text='VDC names separated by commas, encased with double quotes (e.g. "vdc1, vdc2, vdc3")'
help_text=mark_safe(
_('VDC names separated by commas, encased with double quotes. Example:') + ' <code>vdc1,vdc2,vdc3</code>'
)
)
type = CSVChoiceField(
label=_('Type'),
choices=InterfaceTypeChoices,
help_text=_('Physical medium')
)
duplex = CSVChoiceField(
label=_('Duplex'),
choices=InterfaceDuplexChoices,
required=False
)
poe_mode = CSVChoiceField(
label=_('Poe mode'),
choices=InterfacePoEModeChoices,
required=False,
help_text=_('PoE mode')
)
poe_type = CSVChoiceField(
label=_('Poe type'),
choices=InterfacePoETypeChoices,
required=False,
help_text=_('PoE type')
)
mode = CSVChoiceField(
label=_('Mode'),
choices=InterfaceModeChoices,
required=False,
help_text=_('IEEE 802.1Q operational mode (for L2 interfaces)')
)
vrf = CSVModelChoiceField(
label=_('VRF'),
queryset=VRF.objects.all(),
required=False,
to_field_name='rd',
help_text=_('Assigned VRF')
)
rf_role = CSVChoiceField(
label=_('Rf role'),
choices=WirelessRoleChoices,
required=False,
help_text=_('Wireless role (AP/station)')
@@ -791,15 +876,18 @@ class InterfaceImportForm(NetBoxModelImportForm):
class FrontPortImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
rear_port = CSVModelChoiceField(
label=_('Rear port'),
queryset=RearPort.objects.all(),
to_field_name='name',
help_text=_('Corresponding rear port')
)
type = CSVChoiceField(
label=_('Type'),
choices=PortTypeChoices,
help_text=_('Physical medium classification')
)
@@ -836,10 +924,12 @@ class FrontPortImportForm(NetBoxModelImportForm):
class RearPortImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
type = CSVChoiceField(
label=_('Type'),
help_text=_('Physical medium classification'),
choices=PortTypeChoices,
)
@@ -851,6 +941,7 @@ class RearPortImportForm(NetBoxModelImportForm):
class ModuleBayImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
@@ -862,10 +953,12 @@ class ModuleBayImportForm(NetBoxModelImportForm):
class DeviceBayImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
installed_device = CSVModelChoiceField(
label=_('Installed device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
@@ -908,32 +1001,38 @@ class DeviceBayImportForm(NetBoxModelImportForm):
class InventoryItemImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name'
)
role = CSVModelChoiceField(
label=_('Role'),
queryset=InventoryItemRole.objects.all(),
to_field_name='name',
required=False
)
manufacturer = CSVModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
to_field_name='name',
required=False
)
parent = CSVModelChoiceField(
label=_('Parent'),
queryset=Device.objects.all(),
to_field_name='name',
required=False,
help_text=_('Parent inventory item')
)
component_type = CSVContentTypeField(
label=_('Component type'),
queryset=ContentType.objects.all(),
limit_choices_to=MODULAR_COMPONENT_MODELS,
required=False,
help_text=_('Component Type')
)
component_name = forms.CharField(
label=_('Compnent name'),
required=False,
help_text=_('Component Name')
)
@@ -990,7 +1089,7 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
model = InventoryItemRole
fields = ('name', 'slug', 'color', 'description')
help_texts = {
'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
}
@@ -1001,52 +1100,62 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
class CableImportForm(NetBoxModelImportForm):
# Termination A
side_a_device = CSVModelChoiceField(
label=_('Side A device'),
queryset=Device.objects.all(),
to_field_name='name',
help_text=_('Side A device')
help_text=_('Device name')
)
side_a_type = CSVContentTypeField(
label=_('Side A type'),
queryset=ContentType.objects.all(),
limit_choices_to=CABLE_TERMINATION_MODELS,
help_text=_('Side A type')
help_text=_('Termination type')
)
side_a_name = forms.CharField(
help_text=_('Side A component name')
label=_('Side A name'),
help_text=_('Termination name')
)
# Termination B
side_b_device = CSVModelChoiceField(
label=_('Side B device'),
queryset=Device.objects.all(),
to_field_name='name',
help_text=_('Side B device')
help_text=_('Device name')
)
side_b_type = CSVContentTypeField(
label=_('Side B type'),
queryset=ContentType.objects.all(),
limit_choices_to=CABLE_TERMINATION_MODELS,
help_text=_('Side B type')
help_text=_('Termination type')
)
side_b_name = forms.CharField(
help_text=_('Side B component name')
label=_('Side B name'),
help_text=_('Termination name')
)
# Cable attributes
status = CSVChoiceField(
label=_('Status'),
choices=LinkStatusChoices,
required=False,
help_text=_('Connection status')
)
type = CSVChoiceField(
label=_('Type'),
choices=CableTypeChoices,
required=False,
help_text=_('Physical medium classification')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned tenant')
)
length_unit = CSVChoiceField(
label=_('Length unit'),
choices=CableLengthUnitChoices,
required=False,
help_text=_('Length unit')
@@ -1059,7 +1168,7 @@ class CableImportForm(NetBoxModelImportForm):
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
]
help_texts = {
'color': mark_safe(_('RGB color in hexadecimal (e.g. <code>00ff00</code>)')),
'color': mark_safe(_('RGB color in hexadecimal. Example:') + ' <code>00ff00</code>'),
}
def _clean_side(self, side):
@@ -1109,6 +1218,7 @@ class CableImportForm(NetBoxModelImportForm):
class VirtualChassisImportForm(NetBoxModelImportForm):
master = CSVModelChoiceField(
label=_('Master'),
queryset=Device.objects.all(),
to_field_name='name',
required=False,
@@ -1126,11 +1236,13 @@ class VirtualChassisImportForm(NetBoxModelImportForm):
class PowerPanelImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
help_text=_('Name of parent site')
)
location = CSVModelChoiceField(
label=_('Location'),
queryset=Location.objects.all(),
required=False,
to_field_name='name'
@@ -1152,40 +1264,54 @@ class PowerPanelImportForm(NetBoxModelImportForm):
class PowerFeedImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
to_field_name='name',
help_text=_('Assigned site')
)
power_panel = CSVModelChoiceField(
label=_('Power panel'),
queryset=PowerPanel.objects.all(),
to_field_name='name',
help_text=_('Upstream power panel')
)
location = CSVModelChoiceField(
label=_('Location'),
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text=_("Rack's location (if any)")
)
rack = CSVModelChoiceField(
label=_('Rack'),
queryset=Rack.objects.all(),
to_field_name='name',
required=False,
help_text=_('Rack')
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
to_field_name='name',
required=False,
help_text=_('Assigned tenant')
)
status = CSVChoiceField(
label=_('Status'),
choices=PowerFeedStatusChoices,
help_text=_('Operational status')
)
type = CSVChoiceField(
label=_('Type'),
choices=PowerFeedTypeChoices,
help_text=_('Primary or redundant')
)
supply = CSVChoiceField(
label=_('Supply'),
choices=PowerFeedSupplyChoices,
help_text=_('Supply type (AC/DC)')
)
phase = CSVChoiceField(
label=_('Phase'),
choices=PowerFeedPhaseChoices,
help_text=_('Single or three-phase')
)
@@ -1194,7 +1320,7 @@ class PowerFeedImportForm(NetBoxModelImportForm):
model = PowerFeed
fields = (
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
'voltage', 'amperage', 'max_utilization', 'description', 'comments', 'tags',
'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'comments', 'tags',
)
def __init__(self, data=None, *args, **kwargs):
@@ -1221,11 +1347,13 @@ class PowerFeedImportForm(NetBoxModelImportForm):
class VirtualDeviceContextImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
to_field_name='name',
help_text='Assigned role'
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
@@ -47,7 +47,7 @@ class InterfaceCommonForm(forms.Form):
# Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned."
'mode': _("An access interface cannot have tagged VLANs assigned.")
})
# Remove all tagged VLAN assignments from "tagged all" interfaces
@@ -61,8 +61,10 @@ class InterfaceCommonForm(forms.Form):
if invalid_vlans:
raise forms.ValidationError({
'tagged_vlans': f"The tagged VLANs ({', '.join(invalid_vlans)}) must belong to the same site as "
f"the interface's parent device/VM, or they must be global"
'tagged_vlans': _(
"The tagged VLANs ({vlans}) must belong to the same site as the interface's parent device/VM, "
"or they must be global"
).format(vlans=', '.join(invalid_vlans))
})
@@ -105,7 +107,7 @@ class ModuleCommonForm(forms.Form):
# Installing modules with placeholders require that the bay has a position value
if MODULE_TOKEN in template.name and not module_bay.position:
raise forms.ValidationError(
"Cannot install module with placeholder values in a module bay with no position defined"
_("Cannot install module with placeholder values in a module bay with no position defined.")
)
resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
@@ -114,12 +116,17 @@ class ModuleCommonForm(forms.Form):
# It is not possible to adopt components already belonging to a module
if adopt_components and existing_item and existing_item.module:
raise forms.ValidationError(
f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs "
f"to a module"
_("Cannot adopt {name} '{resolved_name}' as it already belongs to a module").format(
name=template.component_model.__name__,
resolved_name=resolved_name
)
)
# If we are not adopting components we error if the component exists
if not adopt_components and resolved_name in installed_components:
raise forms.ValidationError(
f"{template.component_model.__name__} - {resolved_name} already exists"
_("{name} - {resolved_name} already exists").format(
name=template.component_model.__name__,
resolved_name=resolved_name
)
)

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from circuits.models import Circuit, CircuitTermination
from dcim.models import *

View File

@@ -1,6 +1,6 @@
from django import forms
from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
@@ -56,9 +56,11 @@ __all__ = (
class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
name = forms.CharField(
label=_('Name'),
required=False
)
label = forms.CharField(
label=_('Label'),
required=False
)
region_id = DynamicModelMultipleChoiceField(
@@ -102,13 +104,25 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Virtual Chassis')
)
device_type_id = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
required=False,
label=_('Device type')
)
role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Device role')
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
query_params={
'site_id': '$site_id',
'location_id': '$location_id',
'virtual_chassis_id': '$virtual_chassis_id'
'virtual_chassis_id': '$virtual_chassis_id',
'device_type_id': '$device_type_id',
'role_id': '$role_id'
},
label=_('Device')
)
@@ -118,7 +132,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
model = Region
fieldsets = (
(None, ('q', 'filter_id', 'tag', 'parent_id')),
('Contacts', ('contact', 'contact_role', 'contact_group'))
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
)
parent_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -132,7 +146,7 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
model = SiteGroup
fieldsets = (
(None, ('q', 'filter_id', 'tag', 'parent_id')),
('Contacts', ('contact', 'contact_role', 'contact_group'))
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
)
parent_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
@@ -146,11 +160,12 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
model = Site
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
(_('Attributes'), ('status', 'region_id', 'group_id', 'asn_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=SiteStatusChoices,
required=False
)
@@ -176,9 +191,9 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
model = Location
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
(_('Attributes'), ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -209,6 +224,7 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelF
label=_('Parent')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=LocationStatusChoices,
required=False
)
@@ -224,12 +240,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
model = Rack
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
('Function', ('status', 'role_id')),
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
('Weight', ('weight', 'max_weight', 'weight_unit')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
(_('Function'), ('status', 'role_id')),
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -259,14 +275,17 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
label=_('Location')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=RackStatusChoices,
required=False
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=RackTypeChoices,
required=False
)
width = forms.MultipleChoiceField(
label=_('Width'),
choices=RackWidthChoices,
required=False
)
@@ -277,21 +296,26 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
label=_('Role')
)
serial = forms.CharField(
label=_('Serial'),
required=False
)
asset_tag = forms.CharField(
label=_('Asset tag'),
required=False
)
tag = TagFilterField(model)
weight = forms.DecimalField(
label=_('Weight'),
required=False,
min_value=1
)
max_weight = forms.IntegerField(
label=_('Max weight'),
required=False,
min_value=1
)
weight_unit = forms.ChoiceField(
label=_('Weight unit'),
choices=add_blank_choice(WeightUnitChoices),
required=False
)
@@ -300,12 +324,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
class RackElevationFilterForm(RackFilterForm):
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
('Function', ('status', 'role_id')),
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
('Weight', ('weight', 'max_weight', 'weight_unit')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
(_('Function'), ('status', 'role_id')),
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
)
id = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
@@ -322,9 +346,9 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = RackReservation
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('User', ('user_id',)),
('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
(_('User'), ('user_id',)),
(_('Rack'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -364,7 +388,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
label=_('Rack')
)
user_id = DynamicModelMultipleChoiceField(
queryset=User.objects.all(),
queryset=get_user_model().objects.all(),
required=False,
label=_('User'),
widget=APISelectMultiple(
@@ -378,7 +402,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
model = Manufacturer
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Contacts', ('contact', 'contact_role', 'contact_group'))
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
)
tag = TagFilterField(model)
@@ -387,13 +411,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
model = DeviceType
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Hardware', ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
('Images', ('has_front_image', 'has_rear_image')),
('Components', (
(_('Hardware'), ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
(_('Images'), ('has_front_image', 'has_rear_image')),
(_('Components'), (
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
)),
('Weight', ('weight', 'weight_unit')),
(_('Weight'), ('weight', 'weight_unit')),
)
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
@@ -406,98 +430,103 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
label=_('Default platform')
)
part_number = forms.CharField(
label=_('Part number'),
required=False
)
subdevice_role = forms.MultipleChoiceField(
label=_('Subdevice role'),
choices=add_blank_choice(SubdeviceRoleChoices),
required=False
)
airflow = forms.MultipleChoiceField(
label=_('Airflow'),
choices=add_blank_choice(DeviceAirflowChoices),
required=False
)
has_front_image = forms.NullBooleanField(
required=False,
label='Has a front image',
label=_('Has a front image'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
has_rear_image = forms.NullBooleanField(
required=False,
label='Has a rear image',
label=_('Has a rear image'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
console_ports = forms.NullBooleanField(
required=False,
label='Has console ports',
label=_('Has console ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
console_server_ports = forms.NullBooleanField(
required=False,
label='Has console server ports',
label=_('Has console server ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_ports = forms.NullBooleanField(
required=False,
label='Has power ports',
label=_('Has power ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_outlets = forms.NullBooleanField(
required=False,
label='Has power outlets',
label=_('Has power outlets'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
interfaces = forms.NullBooleanField(
required=False,
label='Has interfaces',
label=_('Has interfaces'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
pass_through_ports = forms.NullBooleanField(
required=False,
label='Has pass-through ports',
label=_('Has pass-through ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
device_bays = forms.NullBooleanField(
required=False,
label='Has device bays',
label=_('Has device bays'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
module_bays = forms.NullBooleanField(
required=False,
label='Has module bays',
label=_('Has module bays'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
inventory_items = forms.NullBooleanField(
required=False,
label='Has inventory items',
label=_('Has inventory items'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
tag = TagFilterField(model)
weight = forms.DecimalField(
label=_('Weight'),
required=False
)
weight_unit = forms.ChoiceField(
label=_('Weight unit'),
choices=add_blank_choice(WeightUnitChoices),
required=False
)
@@ -507,12 +536,12 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
model = ModuleType
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Hardware', ('manufacturer_id', 'part_number')),
('Components', (
(_('Hardware'), ('manufacturer_id', 'part_number')),
(_('Components'), (
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
'pass_through_ports',
)),
('Weight', ('weight', 'weight_unit')),
(_('Weight'), ('weight', 'weight_unit')),
)
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
@@ -521,55 +550,58 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
fetch_trigger='open'
)
part_number = forms.CharField(
label=_('Part number'),
required=False
)
console_ports = forms.NullBooleanField(
required=False,
label='Has console ports',
label=_('Has console ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
console_server_ports = forms.NullBooleanField(
required=False,
label='Has console server ports',
label=_('Has console server ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_ports = forms.NullBooleanField(
required=False,
label='Has power ports',
label=_('Has power ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_outlets = forms.NullBooleanField(
required=False,
label='Has power outlets',
label=_('Has power outlets'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
interfaces = forms.NullBooleanField(
required=False,
label='Has interfaces',
label=_('Has interfaces'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
pass_through_ports = forms.NullBooleanField(
required=False,
label='Has pass-through ports',
label=_('Has pass-through ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
tag = TagFilterField(model)
weight = forms.DecimalField(
label=_('Weight'),
required=False
)
weight_unit = forms.ChoiceField(
label=_('Weight unit'),
choices=add_blank_choice(WeightUnitChoices),
required=False
)
@@ -609,15 +641,17 @@ class DeviceFilterForm(
model = Device
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
('Components', (
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Operation'), ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
(_('Hardware'), ('manufacturer_id', 'device_type_id', 'platform_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
(_('Components'), (
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
)),
('Miscellaneous', ('has_primary_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data'))
(_('Miscellaneous'), (
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
))
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -682,22 +716,26 @@ class DeviceFilterForm(
label=_('Platform')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=DeviceStatusChoices,
required=False
)
airflow = forms.MultipleChoiceField(
label=_('Airflow'),
choices=add_blank_choice(DeviceAirflowChoices),
required=False
)
serial = forms.CharField(
label=_('Serial'),
required=False
)
asset_tag = forms.CharField(
label=_('Asset tag'),
required=False
)
mac_address = forms.CharField(
required=False,
label='MAC address'
label=_('MAC address')
)
config_template_id = DynamicModelMultipleChoiceField(
queryset=ConfigTemplate.objects.all(),
@@ -706,56 +744,63 @@ class DeviceFilterForm(
)
has_primary_ip = forms.NullBooleanField(
required=False,
label='Has a primary IP',
label=_('Has a primary IP'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
has_oob_ip = forms.NullBooleanField(
required=False,
label='Has an OOB IP',
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
virtual_chassis_member = forms.NullBooleanField(
required=False,
label='Virtual chassis member',
label=_('Virtual chassis member'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
console_ports = forms.NullBooleanField(
required=False,
label='Has console ports',
label=_('Has console ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
console_server_ports = forms.NullBooleanField(
required=False,
label='Has console server ports',
label=_('Has console server ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_ports = forms.NullBooleanField(
required=False,
label='Has power ports',
label=_('Has power ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
power_outlets = forms.NullBooleanField(
required=False,
label='Has power outlets',
label=_('Has power outlets'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
interfaces = forms.NullBooleanField(
required=False,
label='Has interfaces',
label=_('Has interfaces'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
pass_through_ports = forms.NullBooleanField(
required=False,
label='Has pass-through ports',
label=_('Has pass-through ports'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
@@ -770,8 +815,8 @@ class VirtualDeviceContextFilterForm(
model = VirtualDeviceContext
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('device', 'status', 'has_primary_ip')),
('Tenant', ('tenant_group_id', 'tenant_id')),
(_('Attributes'), ('device', 'status', 'has_primary_ip')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
)
device = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
@@ -780,12 +825,13 @@ class VirtualDeviceContextFilterForm(
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
label=_('Status'),
required=False,
choices=add_blank_choice(VirtualDeviceContextStatusChoices)
)
has_primary_ip = forms.NullBooleanField(
required=False,
label='Has a primary IP',
label=_('Has a primary IP'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
@@ -797,7 +843,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
model = Module
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Hardware', ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
(_('Hardware'), ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
)
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
@@ -815,13 +861,16 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
fetch_trigger='open'
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=ModuleStatusChoices,
required=False
)
serial = forms.CharField(
label=_('Serial'),
required=False
)
asset_tag = forms.CharField(
label=_('Asset tag'),
required=False
)
tag = TagFilterField(model)
@@ -831,8 +880,8 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = VirtualChassis
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -860,9 +909,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = Cable
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('site_id', 'location_id', 'rack_id', 'device_id')),
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
('Tenant', ('tenant_group_id', 'tenant_id')),
(_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')),
(_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -908,20 +957,25 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
label=_('Device')
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=add_blank_choice(CableTypeChoices),
required=False
)
status = forms.MultipleChoiceField(
label=_('Status'),
required=False,
choices=add_blank_choice(LinkStatusChoices)
)
color = ColorField(
label=_('Color'),
required=False
)
length = forms.IntegerField(
label=_('Length'),
required=False
)
length_unit = forms.ChoiceField(
label=_('Length unit'),
choices=add_blank_choice(CableLengthUnitChoices),
required=False
)
@@ -932,8 +986,8 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
model = PowerPanel
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
('Contacts', ('contact', 'contact_role', 'contact_group')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -966,12 +1020,13 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
tag = TagFilterField(model)
class PowerFeedFilterForm(NetBoxModelFilterSetForm):
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = PowerFeed
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
(_('Attributes'), ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
@@ -1010,28 +1065,35 @@ class PowerFeedFilterForm(NetBoxModelFilterSetForm):
label=_('Rack')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=PowerFeedStatusChoices,
required=False
)
type = forms.ChoiceField(
label=_('Type'),
choices=add_blank_choice(PowerFeedTypeChoices),
required=False
)
supply = forms.ChoiceField(
label=_('Supply'),
choices=add_blank_choice(PowerFeedSupplyChoices),
required=False
)
phase = forms.ChoiceField(
label=_('Phase'),
choices=add_blank_choice(PowerFeedPhaseChoices),
required=False
)
voltage = forms.IntegerField(
label=_('Voltage'),
required=False
)
amperage = forms.IntegerField(
label=_('Amperage'),
required=False
)
max_utilization = forms.IntegerField(
label=_('Max utilization'),
required=False
)
tag = TagFilterField(model)
@@ -1043,12 +1105,14 @@ class PowerFeedFilterForm(NetBoxModelFilterSetForm):
class CabledFilterForm(forms.Form):
cabled = forms.NullBooleanField(
label=_('Cabled'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
occupied = forms.NullBooleanField(
label=_('Occupied'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1058,6 +1122,7 @@ class CabledFilterForm(forms.Form):
class PathEndpointFilterForm(CabledFilterForm):
connected = forms.NullBooleanField(
label=_('Connected'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1069,15 +1134,18 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = ConsolePort
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'speed')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Connection', ('cabled', 'connected', 'occupied')),
(_('Attributes'), ('name', 'label', 'type', 'speed')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Connection'), ('cabled', 'connected', 'occupied')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=ConsolePortTypeChoices,
required=False
)
speed = forms.MultipleChoiceField(
label=_('Speed'),
choices=ConsolePortSpeedChoices,
required=False
)
@@ -1088,15 +1156,18 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
model = ConsoleServerPort
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'speed')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Connection', ('cabled', 'connected', 'occupied')),
(_('Attributes'), ('name', 'label', 'type', 'speed')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Connection'), ('cabled', 'connected', 'occupied')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=ConsolePortTypeChoices,
required=False
)
speed = forms.MultipleChoiceField(
label=_('Speed'),
choices=ConsolePortSpeedChoices,
required=False
)
@@ -1107,11 +1178,13 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = PowerPort
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Connection', ('cabled', 'connected', 'occupied')),
(_('Attributes'), ('name', 'label', 'type')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Connection'), ('cabled', 'connected', 'occupied')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=PowerPortTypeChoices,
required=False
)
@@ -1122,11 +1195,13 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = PowerOutlet
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Connection', ('cabled', 'connected', 'occupied')),
(_('Attributes'), ('name', 'label', 'type')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Connection'), ('cabled', 'connected', 'occupied')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=PowerOutletTypeChoices,
required=False
)
@@ -1137,13 +1212,13 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
model = Interface
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
('PoE', ('poe_mode', 'poe_type')),
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id',
'device_id', 'vdc_id')),
('Connection', ('cabled', 'connected', 'occupied')),
(_('Attributes'), ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
(_('Addressing'), ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
(_('PoE'), ('poe_mode', 'poe_type')),
(_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
(_('Connection'), ('cabled', 'connected', 'occupied')),
)
vdc_id = DynamicModelMultipleChoiceField(
queryset=VirtualDeviceContext.objects.all(),
@@ -1154,30 +1229,36 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
label=_('Virtual Device Context')
)
kind = forms.MultipleChoiceField(
label=_('Kind'),
choices=InterfaceKindChoices,
required=False
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=InterfaceTypeChoices,
required=False
)
speed = forms.IntegerField(
label=_('Speed'),
required=False,
widget=NumberWithOptions(
options=InterfaceSpeedChoices
)
)
duplex = forms.MultipleChoiceField(
label=_('Duplex'),
choices=InterfaceDuplexChoices,
required=False
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
mgmt_only = forms.NullBooleanField(
label=_('Mgmt only'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
@@ -1185,50 +1266,50 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
)
mac_address = forms.CharField(
required=False,
label='MAC address'
label=_('MAC address')
)
wwn = forms.CharField(
required=False,
label='WWN'
label=_('WWN')
)
poe_mode = forms.MultipleChoiceField(
choices=InterfacePoEModeChoices,
required=False,
label='PoE mode'
label=_('PoE mode')
)
poe_type = forms.MultipleChoiceField(
choices=InterfacePoETypeChoices,
required=False,
label='PoE type'
label=_('PoE type')
)
rf_role = forms.MultipleChoiceField(
choices=WirelessRoleChoices,
required=False,
label='Wireless role'
label=_('Wireless role')
)
rf_channel = forms.MultipleChoiceField(
choices=WirelessChannelChoices,
required=False,
label='Wireless channel'
label=_('Wireless channel')
)
rf_channel_frequency = forms.IntegerField(
required=False,
label='Channel frequency (MHz)'
label=_('Channel frequency (MHz)')
)
rf_channel_width = forms.IntegerField(
required=False,
label='Channel width (MHz)'
label=_('Channel width (MHz)')
)
tx_power = forms.IntegerField(
required=False,
label='Transmit power (dBm)',
label=_('Transmit power (dBm)'),
min_value=0,
max_value=127
)
vrf_id = DynamicModelMultipleChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
label=_('VRF')
)
l2vpn_id = DynamicModelMultipleChoiceField(
queryset=L2VPN.objects.all(),
@@ -1241,16 +1322,19 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'color')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Cable', ('cabled', 'occupied')),
(_('Attributes'), ('name', 'label', 'type', 'color')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Cable'), ('cabled', 'occupied')),
)
model = FrontPort
type = forms.MultipleChoiceField(
label=_('Type'),
choices=PortTypeChoices,
required=False
)
color = ColorField(
label=_('Color'),
required=False
)
tag = TagFilterField(model)
@@ -1260,15 +1344,18 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
model = RearPort
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'color')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Cable', ('cabled', 'occupied')),
(_('Attributes'), ('name', 'label', 'type', 'color')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
(_('Cable'), ('cabled', 'occupied')),
)
type = forms.MultipleChoiceField(
label=_('Type'),
choices=PortTypeChoices,
required=False
)
color = ColorField(
label=_('Color'),
required=False
)
tag = TagFilterField(model)
@@ -1278,11 +1365,13 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
model = ModuleBay
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'position')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
(_('Attributes'), ('name', 'label', 'position')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
)
tag = TagFilterField(model)
position = forms.CharField(
label=_('Position'),
required=False
)
@@ -1291,8 +1380,9 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
model = DeviceBay
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
(_('Attributes'), ('name', 'label')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
)
tag = TagFilterField(model)
@@ -1301,8 +1391,9 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
model = InventoryItem
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
(_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
(_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')),
)
role_id = DynamicModelMultipleChoiceField(
queryset=InventoryItemRole.objects.all(),
@@ -1316,12 +1407,15 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
label=_('Manufacturer')
)
serial = forms.CharField(
label=_('Serial'),
required=False
)
asset_tag = forms.CharField(
label=_('Asset tag'),
required=False
)
discovered = forms.NullBooleanField(
label=_('Discovered'),
required=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES

Some files were not shown because too many files have changed in this diff Show More