Compare commits

...

505 Commits

Author SHA1 Message Date
jeremystretch
1aa295dc84 Release v3.2-beta1 2022-02-15 14:56:01 -05:00
Jeremy Stretch
85b534a0b0 Merge pull request #8650 from netbox-community/8649-config-module
Closes #8649: Enable customization of configuration module
2022-02-15 13:11:50 -05:00
jeremystretch
e728738e34 Closes #8649: Enable customization of configuration module using NETBOX_CONFIGURATION environment variable 2022-02-15 12:36:03 -05:00
jeremystretch
aa85ae89c1 Merge v3.1.8 2022-02-15 10:05:07 -05:00
jeremystretch
b5e4fdc3d8 PRVB 2022-02-15 09:32:52 -05:00
Jeremy Stretch
90f91eeea4 Merge pull request #8640 from netbox-community/develop
Release v3.1.8
2022-02-15 09:31:00 -05:00
jeremystretch
ae0ae5fd4e Remove outdated installation video 2022-02-15 09:20:19 -05:00
jeremystretch
5b7486cff8 Release v3.1.8 2022-02-15 09:01:58 -05:00
jeremystretch
14240318f1 Fixes #8609: Display validation error when attempting to assign VLANs to interface with no mode during bulk edit 2022-02-15 08:39:45 -05:00
jeremystretch
18eb9ffae6 Changelog for #8391 2022-02-14 10:34:30 -05:00
Jeremy Stretch
c0a62793c4 Merge pull request #8441 from seulsale/8391-install-date-null
Fixes #8391: Install date should appear empty when exported
2022-02-14 10:32:56 -05:00
jeremystretch
dd848d754f Changelog & cleanup for #8556 2022-02-14 10:06:56 -05:00
Jeremy Stretch
f058850598 Merge pull request #8581 from mathieu-mp/8556-add-full-name-to-change-log-tables
Closes #8556: Add 'Full Name' column to Change Log table
2022-02-14 09:49:36 -05:00
Jeremy Stretch
0c7220016b Merge pull request #8621 from JonathonReinhart/nbshell-tab-complete
Enable tab completion in nbshell
2022-02-14 09:27:35 -05:00
jeremystretch
8c19124717 Fixes #8622: Correct help text of status field on VM import form 2022-02-14 08:54:36 -05:00
Mathieu PAYROL
46f4359e1f Closes #8556: Add 'Full Name' column to Change Log table 2022-02-14 09:07:57 +01:00
Sergio Saucedo
f80452c7d9 Update import order 2022-02-14 00:47:48 -06:00
Sergio Saucedo
611f1b57dd Implement custom DateTimeColumn improving null values handling 2022-02-14 00:44:50 -06:00
Jonathon Reinhart
d1b1a45725 Enable tab completion in nbshell 2022-02-13 03:00:57 -05:00
jeremystretch
6e38f7e532 Changelog for #8577 2022-02-11 16:00:01 -05:00
Jeremy Stretch
2c1e681984 Merge pull request #8584 from 991jo/assigned_contacts_fix
Fixes #8577: Contact assignment amounts not shown during contact glob…
2022-02-11 15:49:02 -05:00
jeremystretch
f11ad99983 Fixes #8611: Fix bulk editing for certain custom link, webhook, and journal entry fields 2022-02-11 15:34:41 -05:00
jeremystretch
e1ef911d40 #8564: Fix deepmerge logic to allow nullifying dicts 2022-02-11 15:22:50 -05:00
jeremystretch
b40afd1006 Create custom RTD configuration 2022-02-11 14:49:45 -05:00
jeremystretch
af01122f33 Fix mkdocs requirements specification 2022-02-11 14:43:43 -05:00
jeremystretch
189e835499 Use project requirements to build docs 2022-02-11 14:36:03 -05:00
jeremystretch
1319b62acb Standardize on get_FOO_color() method for returning ChoiceField colors 2022-02-11 14:25:13 -05:00
jeremystretch
9cf9f1bdba Update documentation for v3.2 2022-02-11 12:57:08 -05:00
jeremystretch
0bf1789464 Fix template context for API token views 2022-02-11 12:28:49 -05:00
jeremystretch
fe6acf07a5 Fix component column on InventoryItemTemplateTable 2022-02-11 12:26:26 -05:00
jeremystretch
8e888b4435 Use ColoredLabelColumn for InventoryItem role 2022-02-11 12:08:16 -05:00
jeremystretch
47e99ecb54 Use ContentTypeChoiceField for object_type 2022-02-11 11:33:16 -05:00
jeremystretch
3b80f67e4d #7844: Show module when viewing/editing device components 2022-02-11 11:09:07 -05:00
jeremystretch
71d3dc6e44 Improve ChoiceFieldColumn to not rely on model method to derive label color 2022-02-10 16:29:19 -05:00
jeremystretch
f111380674 Disable I10N to restore pre-4.0 datetime formatting 2022-02-10 15:30:29 -05:00
jeremystretch
d52105b3b8 Merge branch 'develop' into feature 2022-02-10 15:05:54 -05:00
jeremystretch
a4ca585ef2 Remove references to the old mailing list 2022-02-10 14:56:21 -05:00
jeremystretch
076461a1b6 Change notes for #7150, #8398 2022-02-10 14:22:40 -05:00
Jeremy Stretch
0c7407ebb6 Merge pull request #8592 from 991jo/fix_rack_svg_url
Fixes #7150: Devices on the elevations opposite side should be clickable
2022-02-10 14:21:01 -05:00
Jeremy Stretch
f13a3fa549 Merge pull request #8589 from ITJamie/patch-1
small documentation upgrade regarding group syncs
2022-02-10 14:11:12 -05:00
Markku Leiniö
c0a65eb593 Fixes #8398: Add ConfigParam.size to enlarge specific config fields (#8565)
* Fixes #8398: Add ConfigParam.size to enlarge specific config fields

* Revert "Fixes #8398: Add ConfigParam.size to enlarge specific config fields"

This reverts commit 05e8fff458.

* Use forms.Textarea for the banner config fields
2022-02-10 12:15:02 -05:00
jeremystretch
450a7730d3 Fixes #8578: Object change log tables should honor user's configured preferences 2022-02-10 12:07:09 -05:00
jeremystretch
41ee4b642f Fixes #8604: Fix tag filter on config context list filter form 2022-02-10 11:56:41 -05:00
jeremystretch
d42c59792f #8334: Move object changelog & journaling to generic views 2022-02-09 16:24:10 -05:00
jeremystretch
7c105019d8 Closes #8600: Document built-in template tags & filters 2022-02-09 16:01:58 -05:00
jeremystretch
ee566723d7 Document supported table columns 2022-02-09 14:31:40 -05:00
jeremystretch
23a80770e1 Move configure_table() logic to NetBoxTable.configure() 2022-02-09 14:10:54 -05:00
jeremystretch
10e6ae2094 Introduce get_viewname() as a standard utility 2022-02-09 13:47:12 -05:00
jeremystretch
e2286a4c48 Fix plugin name resolution 2022-02-09 12:55:10 -05:00
thatmattlove
3ee3c52e14 Improve CI performance 2022-02-09 10:26:09 -07:00
Johannes Erwerle
e76a5bfd85 Fixes #7150: Devices on the elevations opposite side should be clickable 2022-02-09 15:07:36 +01:00
Jamie (Bear) Murphy
59c89a3b9d small documentation upgrade regarding group syncs
small documentation upgrade regarding group syncs
2022-02-09 13:05:51 +00:00
jeremystretch
272d6e7437 Closes #8463: Change the created field on all change-logged models from date to datetime 2022-02-08 14:41:44 -05:00
Jeremy Stretch
45e5c4eb46 Merge pull request #8586 from netbox-community/template-cleanup
Closes #8585: Support generic templates for plugins
2022-02-08 13:22:44 -05:00
jeremystretch
d9b7c012a6 Document templates supported for plugin use 2022-02-08 12:14:37 -05:00
jeremystretch
270288f730 Rename bulk operation templates 2022-02-08 11:49:18 -05:00
jeremystretch
1e55d064ab Document the base layout template 2022-02-08 11:45:23 -05:00
jeremystretch
e796fd1e11 Clean up and document the bulk import/edit/delete templates 2022-02-08 11:23:52 -05:00
jeremystretch
b0039e938e Clean up and document object edit & delete templates 2022-02-08 11:19:33 -05:00
Sergio Saucedo
8fc605037a Implement custom DateColumn improving null values handling 2022-02-08 01:26:26 -06:00
Johannes Erwerle
311ddf82c5 Fixes #8577: Contact assignment amounts not shown during contact global search 2022-02-08 08:03:48 +01:00
thatmattlove
9d65486c64 Fixes #8564: reset the table config to an empty object when reset is clicked 2022-02-07 16:03:09 -07:00
jeremystretch
624eda297f Clean up and document object, object list templates 2022-02-07 16:50:17 -05:00
thatmattlove
ccce7751a0 Fixes #8564: only use columns form field in user table config form submit 2022-02-07 14:36:28 -07:00
thatmattlove
7252f0b490 Add optional selector to getSelectedOptions for more specific field selection 2022-02-07 14:34:35 -07:00
thatmattlove
094d2e586a Fix code formatting 2022-02-07 14:14:43 -07:00
thatmattlove
6c1507c88c Implement replaceAll utility function
add #8331 release notes
2022-02-07 14:04:58 -07:00
mathieu-mp
60f48326e1 #8331 Maximize browser compatibility 2022-02-07 14:04:49 -07:00
jeremystretch
26db326483 Fix field group header 2022-02-07 15:09:09 -05:00
jeremystretch
d816f797a4 Clean up release notes 2022-02-07 14:31:49 -05:00
Jeremy Stretch
0e827b6ae6 Merge pull request #8562 from netbox-community/8405-plugins-graphql
Closes #8405: GraphQL support for plugins
2022-02-07 13:08:32 -05:00
jeremystretch
049acde5b0 Closes #8572: Add a pre_run() method for reports 2022-02-07 12:57:02 -05:00
Jeremy Stretch
733a9bb2e1 Merge pull request #8510 from netbox-community/8032-django-40
Closes #8032: Upgrade to Django 4.0
2022-02-07 11:47:38 -05:00
jeremystretch
9ac769e4f8 Pull graphene-django from v2 branch 2022-02-07 11:32:02 -05:00
jeremystretch
2157f93f36 Clean up merge conflict remnants 2022-02-07 10:47:07 -05:00
jeremystretch
5b985a924b Changelog for #8548 & misc cleanup 2022-02-07 10:37:11 -05:00
Jeremy Stretch
ee74989f74 Merge pull request #8566 from tijshuisman/develop
Fixes #8548: Virtual Chassis position zero not shown in device page
2022-02-07 10:32:44 -05:00
jeremystretch
3651ef53e3 #7852: Extend VRF assignment to VM interfaces 2022-02-07 09:54:00 -05:00
Tijs Huisman
e2fc7e8cd7 Fixes #8548: Virtual Chassis position zero not shown in device page 2022-02-05 15:10:03 +01:00
jeremystretch
aff55881df Changelog for #8561 2022-02-04 16:22:30 -05:00
Jeremy Stretch
4d066a075d Merge pull request #8563 from jasonyates/8561-rear-console
Fixes #8561 - Unable to connect a cable from rear ports of a patch panel to a device console port
2022-02-04 16:17:07 -05:00
Jason Yates
201077b6f6 Fixes #8561 - Unable to connect a cable from rear ports of a patch panel to a device console port 2022-02-04 20:44:43 +00:00
jeremystretch
dae5c94be0 Expose BaseObjectType and NetBoxObjectType for plugins 2022-02-04 15:07:35 -05:00
jeremystretch
03ea257711 Initial work on GraphQL 2022-02-04 15:06:58 -05:00
jeremystretch
df95115e2e Refactor plugins registry 2022-02-04 14:37:29 -05:00
jeremystretch
5fea012eab Fix GitHub templates 2022-02-04 13:57:30 -05:00
jeremystretch
a2981870ce #7844: Allow installing modules via UI without replicating components 2022-02-04 11:51:30 -05:00
jeremystretch
60e87cd496 Enable the use of fieldsets on bulk edit forms 2022-02-04 09:59:53 -05:00
jeremystretch
ac1c0b0715 #8054: Allow replacing default static choices 2022-02-03 13:52:42 -05:00
jeremystretch
6575af6b93 Fix saving interfaces 2022-02-03 13:25:27 -05:00
jeremystretch
0e95ca7b69 Fix ProgrammingError exception when annotating config context data 2022-02-03 12:58:54 -05:00
jeremystretch
630ff2abb4 Remove dependency on is_safe_url() 2022-02-03 12:58:54 -05:00
jeremystretch
7611cfddae Tweak related names in old migrations to avoid creating new ones 2022-02-03 12:58:54 -05:00
jeremystretch
ef75f7e650 Upgrade to Django 4.0 2022-02-03 12:58:54 -05:00
jeremystretch
478eefb74c Merge v3.1.7 2022-02-03 12:55:34 -05:00
jeremystretch
795134c084 PRVB 2022-02-03 11:34:36 -05:00
Jeremy Stretch
4f689223b4 Merge pull request #8540 from netbox-community/develop
Release v3.1.7
2022-02-03 11:31:48 -05:00
jeremystretch
70ce7293ac Release v3.1.7 2022-02-03 10:51:41 -05:00
jeremystretch
94a0a3b568 Closes #8502: Omit [all] from social-auth-core in base requirements 2022-02-03 10:39:39 -05:00
jeremystretch
69305f0509 Fixes #8315: Fix display of NAT link for primary IPv4 address under device view 2022-02-03 10:30:26 -05:00
jeremystretch
24f48b11e6 Closes #8530: Indicate CSV or YAML as format for "all data" export 2022-02-03 10:22:38 -05:00
jeremystretch
ff3b48fa59 Fixes #8527: Fix display of changelog retention period 2022-02-03 09:48:21 -05:00
jeremystretch
db3f478598 Closes #8517: Render boolean custom fields as icons in object tables 2022-02-02 16:24:51 -05:00
jeremystretch
e20ac803f3 Fixes #8498: Fix display of selected content type filters in object list views 2022-02-02 16:08:12 -05:00
Daniel Sheppard
ea283365e7 Fixes #8425 - Fix exception when viewing change list/records with removed plugins 2022-02-02 11:18:41 -06:00
jeremystretch
8211830bd8 Fixes #8514: Correct several links to config parameters 2022-02-02 09:27:29 -05:00
jeremystretch
2a8e0f9404 Update table accessors to use dunders in path 2022-02-02 09:18:50 -05:00
jeremystretch
c15cfc26f1 Fixes #8512: Correct file permissions to allow execution of housekeeping script 2022-02-01 16:58:09 -05:00
jeremystretch
4f4e6938eb Closes #7504: Include IP range data under IPAM role views 2022-02-01 16:47:29 -05:00
jeremystretch
8545a547b9 Closes #8494: Include locations count under tenant view 2022-02-01 16:31:34 -05:00
jeremystretch
3bb7184f28 Fixes #8499: Content types REST API endpoint should not require model permission 2022-02-01 15:14:13 -05:00
jeremystretch
74c4f12b27 Closes #8509: CSRF_TRUSTED_ORIGINS is now a discrete configuration parameter 2022-02-01 14:29:52 -05:00
jeremystretch
5af18c2d8a Move pk field declaration under NetBoxModelBulkEditForm 2022-02-01 11:40:23 -05:00
jeremystretch
d38620bad2 Fix bulk nullification of custom fields 2022-02-01 11:31:11 -05:00
jeremystretch
3621b1a0d0 Set model as attribute on bulk edit forms 2022-02-01 11:00:18 -05:00
Jeremy Stretch
4347f624d8 Merge pull request #8504 from netbox-community/8488-plugins-forms
Closes #8488: Support form components for plugins
2022-01-31 16:46:33 -05:00
jeremystretch
bfb1a82754 Update docstrings for base form classes 2022-01-31 16:23:23 -05:00
jeremystretch
d1672f8818 Move nullable_fields out of Meta for bulk edit forms 2022-01-31 16:15:40 -05:00
jeremystretch
353e132cf9 Replace filter_groups with fieldsets on filter forms 2022-01-31 16:03:26 -05:00
jeremystretch
ccb3a75281 Move fieldsets out of Meta for model forms 2022-01-31 15:52:36 -05:00
jeremystretch
cf3ca5a661 Refactor & document supported form fields 2022-01-31 14:10:13 -05:00
jeremystretch
e4eee1cdfc Clean up nullable fields declaration for bulk edit forms 2022-01-28 16:47:54 -05:00
jeremystretch
f4776731ec Establish 4 core forms in netbox.forms.base 2022-01-28 15:48:15 -05:00
Jeremy Stretch
dd71942a5e Merge pull request #8489 from 991jo/fix-unittest-docs
Fixes #8477: The commands for running the tests in the development se…
2022-01-28 14:19:57 -05:00
jeremystretch
19fdd5e151 Fixes #8465: Accept empty string values for Interface rf_channel in REST API 2022-01-28 14:03:36 -05:00
jeremystretch
f537dc632e Fixes #8456: Fix redundant display of VRF RD in prefix view 2022-01-28 13:19:23 -05:00
jeremystretch
2221006970 Closes #8462: Linkify manufacturer column in device type table 2022-01-28 13:09:57 -05:00
Johannes Erwerle
5d29c5958b Fixes #8477: The commands for running the tests in the development section are not working 2022-01-28 17:54:37 +01:00
jeremystretch
0fe72376b1 Remove unused form attribute from BulkDeleteView 2022-01-28 10:00:36 -05:00
Jeremy Stretch
64dd46c7e4 Merge pull request #8482 from 991jo/feature-asn-ui-improvement
Fixes #8476: Bring the ASN Web UI up to the standard set by other obj…
2022-01-28 09:37:54 -05:00
Johannes Erwerle
8df382d976 Fixes #8476: Bring the ASN Web UI up to the standard set by other objects 2022-01-28 11:58:29 +01:00
Jeremy Stretch
3a447d5515 Merge pull request #8473 from netbox-community/6221-pluginviews
Closes #8472: Make view name resolution plugin-safe
2022-01-27 16:56:20 -05:00
jeremystretch
75aa1c7b80 Merge feature 2022-01-27 16:38:36 -05:00
jeremystretch
f1697c6856 Add change log for plugins framework additions 2022-01-27 16:21:19 -05:00
jeremystretch
3c1ea5d0fb Closes #8470: Expose NetBoxTable in the plugins framework 2022-01-27 16:14:02 -05:00
jeremystretch
59d3f5c4ea Split out NetBoxTable from BaseTable 2022-01-27 16:00:38 -05:00
jeremystretch
4a1b4e0485 Closes #8469: Move BaseTable, columns to netbox core app 2022-01-27 15:00:10 -05:00
jeremystretch
083d1acb81 Closes #8453: Rename PrimaryModelFilterSet to NetBoxModelFilterSet & expose for plugins 2022-01-27 09:27:33 -05:00
jeremystretch
c5650bb278 Rename PrimaryModel to NetBoxModel 2022-01-26 20:57:14 -05:00
jeremystretch
a795b95f7e Closes #8451: Include ChangeLoggingMixin in BaseModel 2022-01-26 20:41:41 -05:00
jeremystretch
b67859832a Refactor to_objectchange() 2022-01-26 20:25:23 -05:00
jeremystretch
eb00e20269 Revert "Refactor ChangeLoggedModelFilterSet"
This reverts commit 28de9b8913.
2022-01-26 09:03:30 -05:00
jeremystretch
b797b08bcf Remove BigIDModel 2022-01-26 09:02:04 -05:00
jeremystretch
e4abbfb2c6 Closes #8454: Set DEFAULT_AUTO_FIELD to BigAutoField 2022-01-25 17:37:06 -05:00
jeremystretch
28de9b8913 Refactor ChangeLoggedModelFilterSet 2022-01-25 16:18:07 -05:00
jeremystretch
acc9ca7d7d Move TagFilter to PrimaryFilterSet 2022-01-25 16:11:49 -05:00
jeremystretch
497afcc1e4 Rearrange plugins documentation 2022-01-25 13:53:31 -05:00
jeremystretch
571e9801f3 Closes #8195: Ensure all GenericForeignKey ID fields employ PositiveBigIntegerField 2022-01-24 16:02:54 -05:00
Sergio Saucedo
31c58409e1 Set install_date default value as empty string 2022-01-24 02:36:27 -06:00
jeremystretch
5abfe821bc Changelog cnad cleanup for #7853 2022-01-21 15:43:53 -05:00
Jeremy Stretch
05d4c127ee Merge pull request #8428 from netbox-community/8334-plugins-views
Closes #8334: Formally support use of generic views by plugins
2022-01-21 15:37:20 -05:00
jeremystretch
1c94625042 Remove widget_attrs from BulkImportView 2022-01-21 14:48:27 -05:00
jeremystretch
a74ed33b0e Move get_object() to BaseObjectView 2022-01-21 14:41:37 -05:00
jeremystretch
e03593d86f Move get_extra_context() to base views 2022-01-21 14:01:14 -05:00
Jeremy Stretch
3d6c2c5fef Merge pull request #8420 from netbox-community/7853-speed_duplex
Fixes #7853 - Add speed and duplex
2022-01-21 13:07:23 -05:00
jeremystretch
54834c47f8 Refactor generic views; add plugins dev documentation 2022-01-20 16:31:55 -05:00
Daniel Sheppard
d0bfd7e19a #7853 - Add tests 2022-01-20 14:13:13 -06:00
Daniel Sheppard
1a807416b8 #7853 - Add columns to tables 2022-01-20 13:58:37 -06:00
Daniel Sheppard
5f8870d448 #7853 - Change Duplex Filterset to allow multivalues 2022-01-20 13:58:11 -06:00
Daniel Sheppard
375a140343 Merge branch 'feature' of https://github.com/netbox-community/netbox into 7853-speed_duplex 2022-01-20 13:12:04 -06:00
Jeremy Stretch
7002319cc8 Merge pull request #8414 from netbox-community/8392-plugins-features
Closes #8392: Enable NetBox features for plugin models
2022-01-20 12:23:52 -05:00
jeremystretch
e6acae5f94 Omit job results as a supported feature 2022-01-20 11:41:00 -05:00
jeremystretch
1a8f144f5c Include custom validation in BaseModel 2022-01-20 10:53:00 -05:00
jeremystretch
b7682ca9e8 Fix documentation build 2022-01-20 09:27:37 -05:00
jeremystretch
196784474d Update documentation requirements 2022-01-19 16:58:06 -05:00
jeremystretch
d104544d6f Add mkdocstrings 2022-01-19 16:52:00 -05:00
jeremystretch
dd55226455 Draft documentation for model features 2022-01-19 16:47:41 -05:00
jeremystretch
047bed2a86 Tweak registry initialization 2022-01-19 15:22:28 -05:00
jeremystretch
cdae0c2bef Remove extras_features() decorator 2022-01-19 15:16:10 -05:00
jeremystretch
c7825e391c Designate feature mixin classes & employ class_prepared signal to register features 2022-01-19 14:46:50 -05:00
jeremystretch
bf6345aa90 Closes #5429: Enable toggling the placement of table paginators 2022-01-19 09:14:38 -05:00
jeremystretch
3fcae36cf1 Closes #8307: Add data_type indicator to REST API serializer for custom fields 2022-01-18 16:57:54 -05:00
jeremystretch
69eb6b11d0 Closes #8368: Enable controlling the order of custom script form fields with field_order 2022-01-18 16:01:40 -05:00
jeremystretch
1f2d4fd2b3 Closes #8381: Add contacts to global search function 2022-01-18 15:40:19 -05:00
jeremystretch
21468fff25 Closes #8367: Add ASNs to global search function 2022-01-18 15:36:21 -05:00
jeremystretch
4711b4d529 Correct FeatureQuery invocations 2022-01-18 15:17:05 -05:00
Daniel Sheppard
29d4859e02 Fixes #8375 - Change ASN display column from ASDOT to ASPLAIN. Add ASDOT display column. 2022-01-18 11:23:52 -06:00
jeremystretch
4b81d86311 Closes #8376: Correct example condition defitinions; call out value vs label ealuation for choice fields 2022-01-18 11:31:39 -05:00
jeremystretch
38963e7960 Fixes #8377: Fix calculation of absolute cable lengths when specified in fractional units 2022-01-18 11:09:12 -05:00
jeremystretch
3e3880823b Merge v3.1.6 2022-01-17 11:12:54 -05:00
jeremystretch
1584d51433 PRVB 2022-01-17 10:16:37 -05:00
Jeremy Stretch
98571c62a6 Merge pull request #8372 from netbox-community/develop
Release v3.1.6
2022-01-17 10:15:24 -05:00
jeremystretch
69f525bfd3 Release v3.1.6 2022-01-17 09:49:16 -05:00
jeremystretch
2b31154834 Fixes #8358: Fix inconsistent styling of custom fields on filter & bulk edit forms 2022-01-14 14:23:58 -05:00
jeremystretch
b0948ea018 Changelog for #8342, #8357 2022-01-14 11:51:02 -05:00
Jeremy Stretch
a50e4e3380 Merge pull request #8352 from jasonyates/8342-created-updated
Fixes #8342
2022-01-14 11:48:52 -05:00
Jeremy Stretch
5564664b13 Merge pull request #8360 from jasonyates/8357-location-filter
Fixes #8357 - Filter view for Locations is missing tags field
2022-01-14 11:48:36 -05:00
Jason Yates
1ae5a2c808 Fixes #8357 - Filter view for Locations is missing tags field
Adding tag field to Locations filter view
2022-01-14 06:19:25 -08:00
Jason Yates
0181a25d70 Fixes #8342
created & last_updated fields are missing from some REST API calls. Added missing fields to the following API calls

/api/dcim/virtual-chassis/
/api/dcim/cables/
/api/dcim/power-panels/
/api/dcim/rack-reservations/
/api/circuits/circuit-terminations/
/api/extras/webhooks/
/api/extras/custom-fields/
/api/extras/custom-links/
/api/extras/export-templates/
/api/extras/tags/
2022-01-13 19:13:28 -08:00
jeremystretch
60ba4a9830 Changelog for #8337 2022-01-13 15:24:15 -05:00
Jeremy Stretch
3802a78c9d Merge pull request #8341 from jasonyates/8337-created-updated
Add created & last updated as available fields to all tables
2022-01-13 15:23:12 -05:00
jeremystretch
0ca6d73614 #8293: Tweak table column output & add changelog 2022-01-13 15:10:06 -05:00
Jeremy Stretch
aa77f8f0d2 Merge pull request #8329 from jasonyates/8293-asdot
Adding asdot notation to ASN views
2022-01-13 15:02:21 -05:00
jeremystretch
7767692394 Changelog for #8295 2022-01-13 12:10:25 -05:00
Jeremy Stretch
5077ff169e Merge pull request #8343 from netbox-community/1591-service-templates
Closes #1591: Service templates
2022-01-13 12:09:36 -05:00
jeremystretch
b21b6238cf Fix test permissions 2022-01-13 11:52:06 -05:00
Jeremy Stretch
cf89984c7a Merge pull request #8339 from rodvand/feature
Fixes: #8295 Render the payload_url of a Webhook with Jinja2
2022-01-13 11:36:56 -05:00
jeremystretch
707aad234e Add view test for creating service from template 2022-01-13 11:27:29 -05:00
jeremystretch
5b851a2d09 Changelog for #1591 2022-01-13 10:48:08 -05:00
jeremystretch
bb5ded2039 Enable creating services from templates in the UI 2022-01-13 10:32:42 -05:00
Jason Yates
381796e708 Add created & last updated as available fields to all tables
Adds two fields to all relevant tables to allow the addition of Created & Last Updated columns.

All tables with a Configure Table option were updated.

Some sections reformatted to comply with E501 line length as a result of changes
2022-01-13 09:22:32 +00:00
Jason Yates
62fc7717c8 Suggested changes
* Updating asdot computation to use an fstring
* Cleaning code. Custom property now returns either the ASN with ASDOT notation or just the ASN. asn_with_asdot can now be referenced in ASNTable & objet template.
2022-01-13 04:58:51 +00:00
jeremystretch
b07a7ba9bc Fix display of custom object fields within tables 2022-01-12 17:07:54 -05:00
jeremystretch
97e7ef9a3f Introduce ServiceTemplate 2022-01-12 16:42:28 -05:00
Martin Rødvand
5cbc978cad Render the payload_url of the Webhook with Jinja2
- Update markdown documentation
- Expand on the help text for the Webhook model
2022-01-12 21:58:19 +01:00
jeremystretch
e19451bb4f Plug WG8333 in the plugins development docs 2022-01-12 14:40:33 -05:00
Jason Yates
85f588e8c9 Updating page title to include asdot notation 2022-01-12 16:44:22 +00:00
Jason Yates
ea644868a6 Adding asdot notation to ASN views
Adds custom property to asn model to compute asdot notation if required.
Updates asn view to show asdot notation if one exists in the format xxxxx (yyy.yyy)
Adds a custom column renderer to asn table to display asdot notation if one exists
2022-01-12 14:06:22 +00:00
jeremystretch
d08accaaf1 Changelog for #8279 2022-01-11 16:27:30 -05:00
Jeremy Stretch
f49272cacb Merge pull request #8321 from jasonyates/8279-vc-rack-view
Fixes #8279 - No virtual chassis name in rack view
2022-01-11 16:25:50 -05:00
jeremystretch
c8713d94d8 Merge branch 'develop' into feature 2022-01-11 16:16:13 -05:00
Jason Yates
be8fef0228 Fixes #8279
A device that is part of a VC that has no name should display [virtual-chassis name]:[virtual-chassis position] as opposed to [device_type] in the rack rendering.
2022-01-11 21:03:18 +00:00
jeremystretch
b584f09223 Fixes #8319: Custom URL fields should honor ALLOWED_URL_SCHEMES config parameter 2022-01-11 15:32:04 -05:00
jeremystretch
d2968c95df Fixes #8314: Prevent custom fields with default values from appearing as applied filters erroneously 2022-01-11 15:02:10 -05:00
jeremystretch
7421e5f7d7 Fixes #8317: Fix CSV import of multi-select custom field values 2022-01-11 14:52:47 -05:00
jeremystretch
0b2a43cfcc Document formal release cycle 2022-01-11 12:54:07 -05:00
jeremystretch
50309d3ab3 Reference netbox-demo-data repo in development guide 2022-01-10 15:34:27 -05:00
jeremystretch
dd0b16bff5 Fixes #8305: Fix assignment of custom field data to FHRP groups via UI 2022-01-10 15:26:01 -05:00
jeremystretch
d5443adc74 Tweak sidebar colors & remove hover delay 2022-01-10 15:13:12 -05:00
jeremystretch
9152ba72f1 Fixes #8306: Redirect user to previous page after login 2022-01-10 14:44:25 -05:00
jeremystretch
ff396b5953 Fix CSV import test & form cleanup 2022-01-10 14:27:52 -05:00
jeremystretch
21e0e6e495 Closes #6954: Remember users' table ordering preferences 2022-01-10 14:03:07 -05:00
jeremystretch
72e17914e2 Closes #8296: Allow disabling custom links 2022-01-10 12:11:37 -05:00
Jeremy Stretch
17aa37ae21 Merge pull request #8303 from netbox-community/7679-table-actions
Closes #7679: Object table actions menus
2022-01-10 11:38:07 -05:00
jeremystretch
94c116617a Changelog for #7679 2022-01-10 11:20:06 -05:00
jeremystretch
aed23d61fc Replace ButtonsColumn with ActionsColumn 2022-01-10 11:17:40 -05:00
jeremystretch
076ca46ab4 Closes #8302: Linkify role column in device & VM tables 2022-01-10 09:48:14 -05:00
jeremystretch
02519b270e Fixes #8301: Fix delete button for various object children views 2022-01-10 09:30:50 -05:00
jeremystretch
5aa7dedccb Changelog for #8246, #8285 2022-01-10 08:38:08 -05:00
Jeremy Stretch
6383dfa854 Merge pull request #8292 from jasonyates/8246-commit-rate
Fixes #8246 - Circuits list view to display formatted commit rate
2022-01-10 08:36:47 -05:00
Jeremy Stretch
5a4fb0323b Merge pull request #8286 from jasonyates/8285-cluster-count-tenant
Fixes #8285 tenant cluster count
2022-01-10 08:34:02 -05:00
jeremystretch
e84a282aa6 Revert REST API changes from #8284 2022-01-10 08:24:45 -05:00
Jason Yates
f732493473 Fixing code style E302 2022-01-08 22:24:25 +00:00
Jason Yates
f66a265fcf Fixes #8246 - Circuits list view to display formatted commit rate
Adds a custom column class to format the commit rate in the circuits table view using humanize_speed template helper. Export still exports the raw number.
2022-01-08 21:55:07 +00:00
Daniel Sheppard
0f58faaddb #7853 - Initial work on Speed/Duplex.
TODO: Documentation, Tests, Form order
2022-01-08 12:25:30 -06:00
Daniel Sheppard
f1472d218e Update changelog for #8262 and #8265 2022-01-08 00:21:38 -06:00
Daniel Sheppard
d65c05aacd Merge pull request #8269 from bluikko/cisco-stackwise-n
Merge PR from bluikko for #8265
2022-01-08 00:20:43 -06:00
Daniel Sheppard
2b28ffa2f4 Merge pull request #8284 from jasonyates/8262-tenant-cable-stat
Fixes #8262 - Add Cable stat for Tenant
2022-01-08 00:15:35 -06:00
Daniel Sheppard
10ec31df3e Fix #8287 - Correct label in export template form 2022-01-08 00:13:58 -06:00
Jason Yates
184b1055dc Fixes #8285 - Cluster count missing from tenant api output 2022-01-07 20:17:43 +00:00
Jeremy Stretch
447a5f01a9 Merge pull request #8282 from netbox-community/7852-interface-vrf
Closes #7852: Interface VRF assignment
2022-01-07 15:16:11 -05:00
Jason Yates
eaec25e6c2 Fixes #8262 - Add Cable stat for Tenant 2022-01-07 20:02:45 +00:00
jeremystretch
3e277de82d Closes #7852: Enable assigning interfaces to VRFs 2022-01-07 14:57:43 -05:00
jeremystretch
8b07fbc554 Allow passing additional columns & specifying a sequence 2022-01-07 11:56:18 -05:00
jeremystretch
bff7400de4 Convert ActionsMenuItem to dataclass 2022-01-07 11:23:04 -05:00
jeremystretch
1024adca72 Exclude actions column from export 2022-01-07 11:00:35 -05:00
jeremystretch
ededa69e4a Only show relevant links for user permissions 2022-01-07 10:53:00 -05:00
jeremystretch
6d48ce4a25 Always include actions as a default column 2022-01-07 10:36:58 -05:00
jeremystretch
00a8fd654e Refactor table utilities 2022-01-07 09:12:48 -05:00
bluikko
b63e29610e Add Cisco StackWise-n choices 2022-01-07 11:56:54 +07:00
jeremystretch
58f7eb319f Initial work on #7679 2022-01-06 16:53:24 -05:00
Jeremy Stretch
453f2ab02d Merge pull request #8261 from netbox-community/7006-custom-object-fields
Closes #7006: Custom object fields
2022-01-06 14:09:40 -05:00
jeremystretch
3002382edc Documentation and changelog for #7006 2022-01-06 13:44:21 -05:00
jeremystretch
bfc695434c Add object_type validation 2022-01-06 13:43:40 -05:00
jeremystretch
1e80cc6db5 Clean up & extend custom field tests 2022-01-06 13:24:37 -05:00
jeremystretch
b0db5a8b0a PRVB 2022-01-06 09:58:50 -05:00
Jeremy Stretch
d3e2241ff7 Merge pull request #8257 from netbox-community/develop
Release v3.1.5
2022-01-06 09:52:54 -05:00
jeremystretch
e90b9f6c19 Release v3.1.5 2022-01-06 09:24:28 -05:00
jeremystretch
4c1199e009 Fixes #8255: Fix bulk editing of authentication parameters for wireless LANs and links 2022-01-06 08:54:05 -05:00
jeremystretch
65471068b6 Closes #8252: Linkify type and group columns in clusters table 2022-01-05 21:36:20 -05:00
jeremystretch
7aa1fabbd7 Fix tests 2022-01-05 21:21:23 -05:00
jeremystretch
85c06372ff Fix bulk editing for custom object fields 2022-01-05 21:04:44 -05:00
jeremystretch
c6467a824b #8228: Always add a blank choice 2022-01-05 17:10:59 -05:00
jeremystretch
271b7adeb8 Extend to support the assignment of multiple objects per field 2022-01-05 17:05:54 -05:00
jeremystretch
b1d1f3c6b2 Fixes #8228: Optional ChoiceVar fields should not force a selection 2022-01-05 15:46:04 -05:00
jeremystretch
574c2e2770 Closes #8244: Add length & length unit fields to cable filter form 2022-01-05 15:32:34 -05:00
jeremystretch
aec2d233c9 Changelog for #8231 2022-01-05 15:18:49 -05:00
Jeremy Stretch
39418f2bbe Merge pull request #8247 from netbox-community/8231-htmx-confirmation-dialogs
Closes #8231: Use HTMX for object deletion confirmations
2022-01-05 15:14:51 -05:00
jeremystretch
ccda73494f Center modal dialog vertically 2022-01-05 14:57:56 -05:00
jeremystretch
443b4ccc57 Initial work on #8231 2022-01-05 14:06:56 -05:00
Daniel Sheppard
88ac0f5d34 Work on #6221 - Make templatetags safe for consumption when using plugins and update ButtonColumn to use viewname helper. 2022-01-05 11:31:00 -06:00
jeremystretch
511aedd5db Omit table configuration form from rack elevations view 2022-01-05 11:39:58 -05:00
jeremystretch
2524290099 Introduce modals template block 2022-01-05 09:21:48 -05:00
jeremystretch
01e8017265 Clean up template blocks 2022-01-05 09:09:39 -05:00
jeremystretch
8338fc405f Simplify theme color palette 2022-01-04 20:51:10 -05:00
jeremystretch
0a22b3990f #7450: Clean up footer and navbar styles 2022-01-04 20:42:44 -05:00
jeremystretch
954d81147e Reindex migrations 2022-01-04 17:07:37 -05:00
jeremystretch
fa1e28e860 Initial work on #7006 2022-01-04 16:59:52 -05:00
jeremystretch
662cafe416 Form widgets & style cleanup 2022-01-04 15:01:16 -05:00
jeremystretch
ea961ba8f2 Fixes #8224: Fix KeyError exception when creating FHRP group with IP address and protocol "other" 2022-01-04 13:49:07 -05:00
jeremystretch
8c8774cd2f Fixes #8226: Honor return URL after populating a device bay 2022-01-04 13:24:15 -05:00
jeremystretch
2fe02ddb1f Add tests for IPAM object children views 2022-01-04 09:32:41 -05:00
jeremystretch
e11e8a5d64 Fixes #8213: Fix ValueError exception under prefix IP addresses view 2022-01-04 09:15:25 -05:00
jeremystretch
0978777eec Merge v3.1.4 2022-01-03 11:20:58 -05:00
jeremystretch
79bebf7c9b PRVB 2022-01-03 11:18:46 -05:00
Jeremy Stretch
8d3b660ce0 Merge pull request #8212 from netbox-community/develop
Release v3.1.4
2022-01-03 11:16:27 -05:00
jeremystretch
9de53fe070 Release v3.1.4 2022-01-03 11:00:23 -05:00
jeremystretch
ecb9fc65b7 Closes #8197: Allow filtering sites by group when connecting a cable 2022-01-03 10:41:43 -05:00
Jeremy Stretch
7b25d0379f Merge pull request #8202 from netbja/patch-1
Small syntax error
2022-01-03 10:39:56 -05:00
jeremystretch
05d4176d34 Fixes #8201: Custom integer fields should allow negative integers as minimum/maximum values 2022-01-03 10:07:19 -05:00
jeremystretch
7b0dff88ae Closes #8210: Establish netbox/local/ as a path for local resources 2022-01-03 09:45:30 -05:00
jeremystretch
1c7604e0fe Fixes #8200: Correct typo in navigation menu 2022-01-03 09:20:26 -05:00
jeremystretch
e18dc43aae Fixes #8196: Fix IndexError exception when viewing large IPv6 prefixes in UI 2022-01-03 09:17:15 -05:00
netbja
caaad684a4 Small syntax error
No double quotes after password.
2021-12-31 11:25:12 +01:00
jeremystretch
cdd51aee75 Closes #8194: Enable bulk user assignment to groups under admin UI 2021-12-30 13:19:18 -05:00
jeremystretch
51851f6c99 Refactor users.admin 2021-12-30 13:08:09 -05:00
jeremystretch
ab98aa489c Related objects should be prefetched for Prefix/IPRange child object views 2021-12-30 12:43:37 -05:00
jeremystretch
5829985ca8 Remove power utilization as default column from racks table 2021-12-30 12:02:20 -05:00
jeremystretch
2fa8e27f05 Fixes #8192: Add "add prefix" button to aggregate child prefixes view 2021-12-30 12:00:37 -05:00
jeremystretch
68f92dfd5d Fix redirection URL for prefix IP ranges view 2021-12-30 11:47:21 -05:00
jeremystretch
67aeb380e7 Fix DNS name label in IP address bulk edit form 2021-12-30 11:46:09 -05:00
jeremystretch
f7d91b7139 Extend "Adding models" documentation 2021-12-30 10:12:28 -05:00
jeremystretch
b6e157f393 Add features summary to README 2021-12-30 10:08:31 -05:00
jeremystretch
2319fce092 Add tab to cable connect view 2021-12-30 09:51:30 -05:00
jeremystretch
a5f1707662 Fixes #8191: Fix return URL when adding IP addresses to VM interfaces 2021-12-30 09:46:02 -05:00
jeremystretch
6cda55da06 Fixes #8187: Fix rendering of tags column in object tables 2021-12-30 09:41:35 -05:00
jeremystretch
8e69961744 Fix CustomLinkButtonClassChoices references in tests 2021-12-30 08:15:43 -05:00
jeremystretch
9f53497e39 Clean up & expand button color choices 2021-12-29 20:28:12 -05:00
Jeremy Stretch
ae3c871438 Merge pull request #8186 from netbox-community/8118-inventoryitem-template
Closes #8118: Inventory item templates
2021-12-29 16:58:57 -05:00
jeremystretch
1edf80db8e Changelog & documentation for #8118 2021-12-29 16:40:03 -05:00
jeremystretch
791cc093f4 Enable the association of inventory item templates with component templates 2021-12-29 16:30:44 -05:00
jeremystretch
4c15f4a84f Initial work on #8118 2021-12-29 15:37:01 -05:00
jeremystretch
3bb485d0b8 Merge v3.1.3 2021-12-29 12:41:56 -05:00
jeremystretch
c3f2fee633 PRVB 2021-12-29 12:40:04 -05:00
Jeremy Stretch
1f575a2a47 Merge pull request #8185 from netbox-community/develop
Release v3.1.3
2021-12-29 12:31:07 -05:00
jeremystretch
13c4d13157 Release NetBox v3.1.3 2021-12-29 12:10:46 -05:00
jeremystretch
43fadab3bb Closes #8034: Enable specifying custom field validators during CSV import 2021-12-29 11:57:27 -05:00
jeremystretch
82a0240d2e Closes #8182: Introduce checkmark template tag 2021-12-29 10:26:42 -05:00
jeremystretch
f2aa35d3d2 Closes #7600: Include count of available IPs on prefix view 2021-12-29 09:59:25 -05:00
jeremystretch
9c9fcaf42f Fixes #7290: Defer loading API-backed form fields 2021-12-29 09:30:43 -05:00
jeremystretch
146a51ceba Clean up API tokens view 2021-12-29 09:10:56 -05:00
jeremystretch
b0350e9e96 Remove navbar background color 2021-12-29 08:56:59 -05:00
Jeremy Stretch
e0126d971c Merge pull request #8179 from netbox-community/8089-choice-colors
Closes #8089: Color names
2021-12-28 21:23:01 -05:00
jeremystretch
b60ace80be Update documentation for FIELD_CHOICES 2021-12-28 20:21:35 -05:00
jeremystretch
b3ea007e0a Update ChoiceSets to use base colors 2021-12-28 20:18:07 -05:00
jeremystretch
a0d6cb1fd3 Simplify theme color palette 2021-12-28 17:14:04 -05:00
jeremystretch
7b66dae2f0 Merge branch 'develop' into feature 2021-12-28 16:14:24 -05:00
jeremystretch
35e346c4b9 Fix circuit termination button style 2021-12-28 16:13:58 -05:00
jeremystretch
3982f13569 Show parent device/VM when creating new components 2021-12-28 15:19:41 -05:00
jeremystretch
21356b487a #7846: Show assigned component (if any) when creating inventory item 2021-12-28 14:15:06 -05:00
jeremystretch
1987647cc3 Closes #8175: Display parent object when attaching an image 2021-12-28 13:06:27 -05:00
Jeremy Stretch
e9910d1fe2 Merge pull request #8176 from netbox-community/7846-inventoryitem-component
Closes #7846: Associate inventory items with device components
2021-12-28 11:48:36 -05:00
jeremystretch
4c5a5c70b0 Changelog for #7846 2021-12-28 11:19:46 -05:00
jeremystretch
a0836b6876 Add inventory items panel to device component views 2021-12-28 11:06:34 -05:00
jeremystretch
e0319cc894 Clean up form rendering 2021-12-28 10:22:00 -05:00
jeremystretch
4075cc8518 Restore front port component creation 2021-12-28 09:53:56 -05:00
jeremystretch
8ca09ec07f Clean up form display 2021-12-28 08:54:03 -05:00
jeremystretch
ba85101d30 Update component model forms to use DynamicModelChoiceField query_params for related objects 2021-12-27 21:25:47 -05:00
jeremystretch
a237c01b4b Refactor ComponentCreateView to use separate forms for names/labels and model creation 2021-12-27 21:04:29 -05:00
jeremystretch
99d5013de3 Initial work on #7846 2021-12-27 14:01:25 -05:00
Jeremy Stretch
a58f1c6a7d Merge pull request #8172 from netbox-community/3087-inventory-item-roles
Closes #3087: Add inventory item roles
2021-12-27 11:06:50 -05:00
jeremystretch
a748083f26 Changelog for #3087 2021-12-27 10:52:04 -05:00
jeremystretch
6e9afccfd7 #8037: Add role field to InventoryItem 2021-12-27 10:45:33 -05:00
jeremystretch
04fb5e544d #3087: Add InvetoryItemRole 2021-12-27 10:18:39 -05:00
jeremystretch
542534aeba Add direct link to preferences in user menu 2021-12-23 14:41:39 -05:00
jeremystretch
908a2824ba Reduce saturation of 'info' theme color 2021-12-23 14:34:09 -05:00
jeremystretch
77dd684916 Closes #7784: Support cluster type assignment for config contexts 2021-12-23 14:20:03 -05:00
jeremystretch
bffd22038b Closes #7681: Add service_id field for provider networks 2021-12-23 13:50:01 -05:00
jeremystretch
a03ae295f6 Update release notes 2021-12-23 11:39:56 -05:00
jeremystretch
544d991e1e Closes #8168: Add min/max VID fields to VLANGroup 2021-12-23 11:22:10 -05:00
jeremystretch
083fda3172 #2658: Fix test permissions 2021-12-23 10:46:57 -05:00
jeremystretch
e0cfd5e49b Closes #2658: Avalable VLANs API endpoint for VLAN groups 2021-12-23 10:14:28 -05:00
jeremystretch
2dd165bbef Merge branch 'develop' into feature 2021-12-23 08:32:40 -05:00
Jeremy Stretch
cab9733b60 Merge pull request #8159 from netbox-community/6782-custom-link-columns
Closes #6782: Custom link columns
2021-12-22 21:13:13 -05:00
jeremystretch
99e0dcec76 Changelog & docs for #6782 2021-12-22 20:57:59 -05:00
jeremystretch
9dafb36c88 Introduce CustomLinkColumn 2021-12-22 20:56:11 -05:00
jeremystretch
3d7d19b608 Move rendering logic under CustomLink class 2021-12-22 20:25:57 -05:00
jeremystretch
d650d10cb2 #7449: Apply distinctive styling to top navbar 2021-12-22 15:32:35 -05:00
jeremystretch
7fe45018e9 #7449: Remove red color from logout link 2021-12-22 15:22:06 -05:00
jeremystretch
4c4cab87fb #7449: Don't color valid form fields 2021-12-22 15:18:24 -05:00
jeremystretch
94c7f64baf Relocate confirmation_form.html 2021-12-22 15:08:04 -05:00
jeremystretch
f369b5f588 Reorganize & clean up templatetag templates 2021-12-22 15:05:24 -05:00
jeremystretch
37065b7c50 Remove obsolete template 2021-12-22 14:47:42 -05:00
jeremystretch
0a7372460f Changelog for #7887 2021-12-22 12:48:24 -05:00
Jeremy Stretch
063abc8ef7 Merge pull request #8153 from davama/develop
Add missing HTTP_X_FORWARDED_FOR
2021-12-22 12:46:22 -05:00
Jeremy Stretch
d64c88786e Merge pull request #8143 from netbox-community/7759-user-preferences
Closes #7759: User preferences framework
2021-12-22 11:00:18 -05:00
jeremystretch
fb4511d099 Fixes #8140: Restore missing fields on wireless LAN & link REST API serializers 2021-12-22 10:55:06 -05:00
jeremystretch
7343ae7339 Fix invalid key retrieval 2021-12-22 10:45:21 -05:00
jeremystretch
cb6342c874 Reference DEFAULT_USER_PREFERENCES for undefined preferences 2021-12-22 10:13:08 -05:00
jeremystretch
01997efcbe Add tests & cleanup 2021-12-22 09:51:31 -05:00
jeremystretch
7926225e9b Improve preferences form rendering 2021-12-22 09:35:29 -05:00
jeremystretch
1aafcf241f Enable plugins to define user preferences 2021-12-22 09:10:50 -05:00
jeremystretch
1eeac7f4f4 Introduce DEFAULT_USER_PREFERENCES dynamic config setting 2021-12-21 20:30:59 -05:00
jeremystretch
2c01e178c7 Update config context display to reference data_format preference 2021-12-21 19:59:33 -05:00
jeremystretch
36d2422eef Introduce UserPreference to define user preferences 2021-12-21 17:05:06 -05:00
jeremystretch
70f257b1ea Introduce UserConfigForm for managing user preferences 2021-12-21 16:29:01 -05:00
jeremystretch
275560698f Fixes #8139: Fix rendering of table configuration form under VM interfaces view 2021-12-21 14:10:12 -05:00
jeremystretch
d4b6fe14c3 Fixes #8138: Fix alignment of tags panel within IP address view 2021-12-21 14:04:15 -05:00
jeremystretch
f1350a1022 FIxes #7972: Standardize name of RemoteUserBackend logger 2021-12-21 13:57:12 -05:00
jeremystretch
344fb638fd Fixes #8127: Fix disassociation of interface under IP address edit view 2021-12-21 13:17:54 -05:00
thatmattlove
373cc74a33 Fixes #8134: reinitialize event listeners when HTMX swaps elements 2021-12-21 11:11:33 -07:00
jeremystretch
8e95ac42c2 Closes #8100: Add "other" choice for FHRP group protocol 2021-12-21 13:05:38 -05:00
jeremystretch
ceb941df81 Closes #8135: Append version when fetching static assets 2021-12-21 13:00:52 -05:00
jeremystretch
d275538116 Changelog & cleanup for #7246, #8097 2021-12-21 11:53:31 -05:00
Jeremy Stretch
fa38cdbc0d Merge pull request #8121 from kkthxbye-code/fix-8097
Fix #8097: Re-fix markdown table rendering
2021-12-21 11:50:24 -05:00
Jeremy Stretch
7569544b7b Merge pull request #8063 from rizlas/develop
Get_Environment from napalm should not need any decoding
2021-12-21 11:43:23 -05:00
Jeremy Stretch
853a52f3ca Merge branch 'develop' into fix-8097 2021-12-21 11:37:58 -05:00
jeremystretch
5e32c69e0e Merge branch 'develop' into feature 2021-12-21 11:28:16 -05:00
rizlas
39a0b15df4 Update netbox/dcim/api/views.py
Test without decode_dict function

Co-authored-by: Jeremy Stretch <jstretch@ns1.com>
2021-12-21 17:15:54 +01:00
jeremystretch
a0db10838b Fixes #8131: Restore annotation of available IPs under prefix IPs view 2021-12-21 11:09:30 -05:00
jeremystretch
f2f10dff92 Fix RearPortTemplateTable buttons 2021-12-21 10:57:46 -05:00
jeremystretch
7ba45b2887 Clean up imports 2021-12-21 10:48:10 -05:00
jeremystretch
c91eb8f406 Remove extraneous output from service edit template 2021-12-21 10:30:30 -05:00
jeremystretch
57a78b3cad Clean up device/devicetype tab views 2021-12-21 10:28:28 -05:00
jeremystretch
b755c7dab3 Add changelog for #7962 (via #8114) 2021-12-21 09:03:36 -05:00
Jeremy Stretch
9ffd791ae4 Merge pull request #8130 from netbox-community/8114-htmx-jobs
Closes #8114: Use HTMX to update report/script results
2021-12-21 09:01:15 -05:00
jeremystretch
8af12b22bb Clean up report & script templates 2021-12-21 08:43:01 -05:00
jeremystretch
17ba0a97d5 Remove jobs Javascript 2021-12-20 20:59:14 -05:00
jeremystretch
4ae2b4e0b9 Convert reports to use HTMX 2021-12-20 20:52:29 -05:00
jeremystretch
872691a138 Convert scripts to use HTMX 2021-12-20 20:45:32 -05:00
kkthxbye-code
3a54ecb522 Fix #8097: Re-fix markdown table rendering 2021-12-20 23:31:24 +01:00
jeremystretch
71b4641e18 Merge v3.1.2 2021-12-20 16:28:11 -05:00
jeremystretch
42b590af77 PRVB 2021-12-20 16:06:42 -05:00
Jeremy Stretch
b15ecf7649 Merge pull request #8123 from netbox-community/develop
Release v3.1.2
2021-12-20 16:04:41 -05:00
jeremystretch
df4f80e773 Release v3.1.2 2021-12-20 15:48:28 -05:00
jeremystretch
b8b485af4d Changelog & PEP8 cleanup for #7999 2021-12-20 14:17:52 -05:00
Jeremy Stretch
892d6b55ec Merge pull request #8000 from joni1993/more-channels
feat: add 6GHz & 60Ghz channels
2021-12-20 14:16:12 -05:00
Jeremy Stretch
4a3bc8d365 Merge pull request #8111 from bonktree/opaque-icon
templates: add an opaque icon for mobile home screens
2021-12-20 13:58:25 -05:00
jeremystretch
e12da72615 Fixes #8101: Preserve return URL when using "create and add another" button 2021-12-20 13:41:22 -05:00
jeremystretch
f95e510060 Fixes #8102: Raise validation error when attempting to assign an IP address to multiple objects 2021-12-20 13:09:28 -05:00
Daniel Sheppard
82932ae7a5 Fixes #8102 - Add validation around assigned objects 2021-12-20 11:07:44 -06:00
jeremystretch
ae55ca7fd7 Changelog for #7844 2021-12-20 11:02:56 -05:00
Jeremy Stretch
d69a314bbf Merge pull request #8105 from netbox-community/7844-modules
Closes #7844: Add support for device modules
2021-12-20 10:55:50 -05:00
jeremystretch
e35aa4bd1e Add documentation for modules 2021-12-20 10:31:18 -05:00
jeremystretch
eaa1165611 Add position field for module bays 2021-12-20 09:51:55 -05:00
jeremystretch
14fc37a8b8 Closes #7661: Remove forced styling of custom banners 2021-12-19 15:33:48 -05:00
Arseny Maslennikov
7b23856cc8 templates: add an opaque icon for mobile home screens
The netbox_touch-icon-180.png icon was produced by rendering
netbox_icon.svg into a 160x160 square, centered in a 180x180 PNG filled
by the background colour of #212529.

In other words, it is a screenshot of the following HTML element:
```html
  <div style="width: 180px;height: 180px;background-color: #212529;">
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" style="padding: 10px;">
    <g fill="#9cc8f8" stroke="#9cc8f8">
      <circle cx="37" cy="284" r="23"></circle>
      <circle cx="101" cy="37" r="23"></circle>
      <circle cx="101" cy="220" r="23"></circle>
      <circle cx="284" cy="220" r="23"></circle>
      <rect x="93" y="37" width="16" height="180"></rect>
      <rect x="101" y="212" width="180" height="16"></rect>
      <rect x="93" y="212" width="16" height="90" transform="rotate(45 101 220)"></rect>
    </g>
    <g fill="#1685fc" stroke="#1685fc">
      <circle cx="284" cy="37" r="23"></circle>
      <circle cx="37" cy="101" r="23"></circle>
      <circle cx="220" cy="101" r="23"></circle>
      <circle cx="220" cy="284" r="23"></circle>
      <rect x="37" y="93" width="180" height="16"></rect>
      <rect x="212" y="101" width="16" height="180"></rect>
      <rect x="212" y="93" width="16" height="90" transform="rotate(225 220 101)"></rect>
    </g>
  </svg>
  </div>
```
2021-12-19 01:32:15 +03:00
jeremystretch
85f9690377 Closes #8083: Removed "related devices" panel from device view 2021-12-18 14:30:28 -05:00
jeremystretch
4723500c5f Fixes #8092: Rack elevations should not include device asset tags 2021-12-18 14:26:32 -05:00
jeremystretch
2db82a73a5 #8096: Include only first assigned IP in FHRPGroup string representation 2021-12-18 14:19:57 -05:00
jeremystretch
b00eeb86ea Fixes #8096: Fix DataError during change logging of objects with very long string representations 2021-12-18 14:16:37 -05:00
jeremystretch
628e186846 Closes #8108: Improve breadcrumb links for device/VM components 2021-12-18 14:02:01 -05:00
jeremystretch
cf4a55bc2f Closes #8107: Correct template name 2021-12-18 13:52:39 -05:00
jeremystretch
ed6a160372 Add modules to device component serializers 2021-12-17 20:31:15 -05:00
jeremystretch
7dc4e00b4d Add module, module_bay columns to device component tables 2021-12-17 20:15:49 -05:00
jeremystretch
e0d7511eaa Misc cleanup 2021-12-17 16:34:51 -05:00
jeremystretch
7777922bef Add Module model 2021-12-17 16:12:03 -05:00
jeremystretch
5bd223a468 Fix YAML import for ModuleTypes 2021-12-17 13:28:17 -05:00
jeremystretch
7c60e3c0ff Add Module model 2021-12-17 12:18:37 -05:00
jeremystretch
e529d7fd3b Add ModuleBay and ModuleBayTemplate models 2021-12-17 09:35:57 -05:00
jeremystretch
5f9f0e3ed3 Split generic views into separate modules 2021-12-16 16:41:43 -05:00
jeremystretch
e91a76c936 Refactor bulk generic views 2021-12-16 16:28:23 -05:00
Christian Jonak
cab07c7c4b fix: non 20Mhz-wide channel centers 2021-12-16 19:28:39 +01:00
jeremystretch
7735a539e9 Fixes #8088: Improve legibility of text in labels with light-colored backgrounds 2021-12-16 12:44:18 -05:00
Christian Jonak
68eb6fc3c1 fix: use center freq instead of beginning of freq range for 6Ghz 2021-12-16 18:14:56 +01:00
jeremystretch
1dd3d2ec48 Changelog for #8054 2021-12-16 11:32:31 -05:00
jeremystretch
ea6cdc9673 Closes #7650: Add support for local account password validation 2021-12-16 11:28:57 -05:00
Jeremy Stretch
134742a8b7 Merge pull request #8090 from netbox-community/8054-configurable-choice-fields
Closes #8054: Configurable choice fields
2021-12-16 11:17:32 -05:00
jeremystretch
d8be8e25a5 ChoiceSet cleanup 2021-12-16 10:31:32 -05:00
jeremystretch
1902ecb8ca Drop as_dict() method from ChoiceSet 2021-12-16 10:22:05 -05:00
jeremystretch
124302908a Support nested choice groups 2021-12-16 10:19:16 -05:00
jeremystretch
0d3b50a5e5 Support CSS class definition directly in CHOICES iterable 2021-12-16 10:03:23 -05:00
jeremystretch
419f86a4a5 #8054: Support configurable status choices 2021-12-16 09:36:15 -05:00
jeremystretch
fd785fc9a5 Move speed select dropdown menu to widget template 2021-12-16 08:41:43 -05:00
jeremystretch
8d06908353 Bulk component add view should use tabs 2021-12-15 16:57:30 -05:00
jeremystretch
806706ca1d Refresh development documentation 2021-12-15 16:31:06 -05:00
jeremystretch
28f577738a Merge branch 'develop' into feature 2021-12-15 13:19:17 -05:00
jeremystretch
044e203eab Standardize button colors 2021-12-15 12:16:50 -05:00
jeremystretch
fcc7207b67 Restore actions column under VM interfaces table 2021-12-15 12:11:20 -05:00
jeremystretch
8dbd3f332b Closes #8081: Allow creating services directly from navigation menu 2021-12-15 11:55:27 -05:00
jeremystretch
f43ec7c05d Add "add IP range" button to prefix IP ranges view 2021-12-15 11:03:38 -05:00
jeremystretch
997e88af00 Merge branch 'develop' into feature 2021-12-15 10:53:21 -05:00
jeremystretch
ff9dde54e3 Ensure consistent placement of table paginator 2021-12-15 10:34:20 -05:00
jeremystretch
3699f16848 Show per-page selector only when results are present 2021-12-15 09:46:59 -05:00
jeremystretch
fee2ac2ebd Changelog for #8057 2021-12-15 09:36:52 -05:00
Jeremy Stretch
57d3bfcfc9 Merge pull request #8073 from netbox-community/8057-htmx-tables
Closes #8057: Dynamic object tables using HTMX
2021-12-15 09:16:41 -05:00
jeremystretch
b92e34556f Fixes #8077: Fix exception when attaching image to location, circuit, or power panel 2021-12-15 08:45:17 -05:00
jeremystretch
b6ff55309e Fixes #8078: Add missing wireless models to lsmodels() in nbshell 2021-12-15 08:38:19 -05:00
jeremystretch
305d88ebda Fixes #8079: Fix validation of LLDP neighbors when connected device has an asset tag 2021-12-15 08:36:03 -05:00
jeremystretch
cdc73d4f56 Closes #8080: Link to NAT IPs for device/VM primary IPs 2021-12-15 08:35:01 -05:00
jeremystretch
0e50c964d5 Remove obsolete pagination TS/CSS 2021-12-14 21:00:48 -05:00
jeremystretch
863fb9aa47 Sync HTMX and non-HTMX paginator styles 2021-12-14 20:53:24 -05:00
jeremystretch
298fb00a3e Remove obsolete "quick find" TS 2021-12-14 20:04:49 -05:00
jeremystretch
d1e8c06d36 Fixes #8074: Ordering VMs by name should reference naturalized value 2021-12-14 17:03:03 -05:00
jeremystretch
8ed79d5973 Remove obsolete templates 2021-12-14 16:44:03 -05:00
jeremystretch
85b10b59e4 Introduce child prefixes view for aggregates 2021-12-14 16:38:25 -05:00
jeremystretch
9a53c22833 Serve HTMX JS locally 2021-12-14 15:55:40 -05:00
jeremystretch
c981b5cba0 Add prep_table_data() method to ObjectChildrenView 2021-12-14 15:42:28 -05:00
jeremystretch
4ffa823ab8 Enable HTMX for all ObjectChildrenViews 2021-12-14 15:31:42 -05:00
Jeremy Stretch
001c7e4b18 Merge pull request #8070 from netbox-community/8069-generic-children-view
Closes #8069: Generic children view
2021-12-14 14:30:41 -05:00
jeremystretch
402136dc8f Merge branch '8069-generic-children-view' into 8057-htmx-tables 2021-12-14 14:21:08 -05:00
jeremystretch
59ee30f056 Update cluster VM/device views to use ObjectChildrenView 2021-12-14 14:08:44 -05:00
jeremystretch
c795068a78 Update VLAN member interface views to use ObjectChildrenView 2021-12-14 14:03:44 -05:00
jeremystretch
5ce080779b Update IPRange IP addresses view to use ObjectChildrenView 2021-12-14 13:55:09 -05:00
jeremystretch
8d3b296eed Update device/VM component views to use ObjectChildrenView 2021-12-14 13:47:40 -05:00
jeremystretch
cfdb985d00 Update prefix children views to use ObjectChildrenView 2021-12-14 13:33:53 -05:00
jeremystretch
af6f0db284 Introduce ObjectChildrenView 2021-12-14 13:33:36 -05:00
jeremystretch
491eac184e Enable HTMX for connections lists 2021-12-14 11:53:16 -05:00
jeremystretch
414d33eb26 Refactor HTMX table template 2021-12-14 11:41:39 -05:00
jeremystretch
2dad35186a Generic view cleanup 2021-12-14 11:28:13 -05:00
jeremystretch
6dd6094088 Push HTMX URL to browser location 2021-12-14 08:25:17 -05:00
rizlas
2ec64a2ea2 Get_Environment from napalm should not need any decoding 2021-12-14 10:17:00 +01:00
jeremystretch
5c34a75032 Enable HTMX for quick table search 2021-12-13 20:15:03 -05:00
jeremystretch
91f33d3289 #8057: Enable dynamic tables for object list views 2021-12-13 16:51:59 -05:00
jeremystretch
c50dc1eb35 Standardize usage of table template 2021-12-13 15:36:51 -05:00
jeremystretch
dc1331e736 Fixes #7674: Fix inadvertent application of device type context to virtual machines 2021-12-13 13:42:59 -05:00
jeremystretch
afc866eee4 #7665: Refactored add_requested_prefixes(); removed button icons 2021-12-13 12:15:43 -05:00
jeremystretch
b6d93b7c5b Changelog for #7665 2021-12-13 12:10:03 -05:00
Jeremy Stretch
5d6158dd64 Merge pull request #7826 from WillIrvine/develop
Add filter for optionally including assigned prefixes
2021-12-13 12:04:38 -05:00
jeremystretch
f2f6edabf9 Merge branch 'develop' into feature 2021-12-13 11:29:54 -05:00
jeremystretch
e9549ab0bd PRVB 2021-12-13 09:16:55 -05:00
jeremystretch
7d99e15dc3 Closes #7743: Remove legacy ASN field from site model 2021-12-09 17:01:27 -05:00
jeremystretch
d2d2978288 Closes #7748: Remove legacy contact fields from site model 2021-12-09 16:23:39 -05:00
jeremystretch
8680981990 Closes #8031: Remove automatic redirection of legacy slug-based URLs 2021-12-09 15:43:41 -05:00
jeremystretch
78ca6f1a87 Use strings for build matrix 2021-12-09 15:39:50 -05:00
jeremystretch
62e5680eaf Closes #7731: Require Python 3.8 or later 2021-12-09 15:35:40 -05:00
Christian Jonak-Möchel
cc50e22928 feat: add 6GHz & 60Ghz channels 2021-12-07 15:14:17 +01:00
William Irvine
13414dcd25 pep8 compliance... 2021-12-07 10:13:54 +13:00
William Irvine
aebfccfd4b Merge branch 'develop' into develop 2021-12-07 10:06:35 +13:00
Will Irvine
ca07a88674 fix spelling... 2021-12-02 10:47:19 +13:00
Will Irvine
dcfd332cbf Moved filtering logic to utils, adjusted show buttons 2021-12-01 19:24:44 +13:00
Dave
038d7e0fa6 Add missing HTTP_X_FORWARDED_FOR
See discussion [here](https://github.com/netbox-community/netbox/discussions/7876) for background.

From the [doc](https://netbox.readthedocs.io/en/stable/customization/custom-scripts/) i should be able to access `META.HTTP_X_FORWARDED_FOR` but i was not able to since they were not being sent downstream
2021-11-19 15:20:00 -05:00
Will Irvine
80048bfa2b Make the same changes for aggregate views as these use the same adjusted functions 2021-11-13 16:42:38 +13:00
Will Irvine
641a9bc6c5 pep8 compliance 2021-11-13 15:26:07 +13:00
Will Irvine
0edf9b17f6 Closes #7665 add new boolen for filtering assigned prefixes, adjust current filter for avaliabile prefixes to only return avaliable 2021-11-13 13:27:49 +13:00
579 changed files with 21453 additions and 11079 deletions

View File

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

View File

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

View File

@@ -3,10 +3,12 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
env:
NETBOX_CONFIGURATION: netbox.configuration_testing
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
node-version: [14.x]
python-version: ['3.8', '3.9', '3.10']
node-version: ['14.x']
services:
redis:
image: redis
@@ -38,14 +40,25 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install Yarn Package Manager
run: npm install -g yarn
- name: Setup Node.js with Yarn Caching
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: yarn
cache-dependency-path: netbox/project-static/yarn.lock
- name: Install Frontend Dependencies
run: yarn --cwd netbox/project-static
- name: Install dependencies & set up configuration
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pycodestyle coverage
ln -s configuration.testing.py netbox/netbox/configuration.py
yarn --cwd netbox/project-static
- name: Build documentation
run: mkdocs build
@@ -63,7 +76,7 @@ jobs:
run: scripts/verify-bundles.sh
- name: Run tests
run: coverage run --source="netbox/" netbox/manage.py test netbox/
run: coverage run --source="netbox/" netbox/manage.py test netbox/ --parallel
- name: Show coverage report
run: coverage report --skip-covered --omit *migrations*

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ yarn-error.log*
!/netbox/project-static/docs/.info
/netbox/netbox/configuration.py
/netbox/netbox/ldap_config.py
/netbox/local/*
/netbox/reports/*
!/netbox/reports/__init__.py
/netbox/scripts/*

10
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,10 @@
version: 2
build:
os: ubuntu-20.04
tools:
python: "3.9"
mkdocs:
configuration: mkdocs.yml
python:
install:
- requirements: requirements.txt

View File

@@ -16,13 +16,6 @@ categories for discussions:
feature request
* **Q&A** - Request help with installing or using NetBox
### Mailing List
We also have a Google Groups [mailing list](https://groups.google.com/g/netbox-discuss)
for general discussion, however we're encouraging people to use GitHub
discussions where possible, as it's much easier for newcomers to review past
discussions.
### Slack
For real-time chat, you can join the **#netbox** Slack channel on [NetDev Community](https://netdev.chat/).

View File

@@ -5,11 +5,46 @@
![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master)
NetBox is an infrastructure resource modeling (IRM) tool designed to empower
network automation. Initially conceived by the network engineering team at
network automation, used by thousands of organizations around the world.
Initially conceived by the network engineering team at
[DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically
to address the needs of network and infrastructure engineers. It is intended to
function as a domain-specific source of truth for network operations.
Myriad infrastructure components can be modeled in NetBox, including:
* Hierarchical regions, site groups, sites, and locations
* Racks, devices, and device components
* Cables and wireless connections
* Power distribution
* Data circuits and providers
* Virtual machines and clusters
* IP prefixes, ranges, and addresses
* VRFs and route targets
* FHRP groups (VRRP, HSRP, etc.)
* AS numbers
* VLANs and scoped VLAN groups
* Organizational tenants and contacts
In addition to its extensive built-in models and functionality, NetBox can be
customized and extended through the use of:
* Custom fields
* Custom links
* Configuration contexts
* Custom model validation rules
* Reports
* Custom scripts
* Export templates
* Conditional webhooks
* Plugins
* Single sign-on (SSO) authentication
* NAPALM integration
* Detailed change logging
NetBox also features a complete REST API as well as a GraphQL API for easily
integrating with other tools and systems.
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/)
Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a
complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox).
@@ -33,7 +68,6 @@ The complete documentation for NetBox can be found at [Read the Docs](https://ne
* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - Discussion forum hosted by GitHub; ideal for Q&A and other structured discussions
* [Slack](https://netdev.chat/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being replaced by GitHub discussions
### Installation

View File

@@ -1,6 +1,6 @@
# The Python web framework on which NetBox is built
# https://github.com/django/django
Django<4.0
Django
# Django middleware which permits cross-domain API requests
# https://github.com/OttoYiu/django-cors-headers
@@ -82,6 +82,10 @@ markdown-include
# https://github.com/squidfunk/mkdocs-material
mkdocs-material
# Introspection for embedded code
# https://github.com/mkdocstrings/mkdocstrings
mkdocstrings
# Library for manipulating IP prefixes and addresses
# https://github.com/drkjam/netaddr
netaddr
@@ -98,13 +102,9 @@ psycopg2-binary
# https://github.com/yaml/pyyaml
PyYAML
# In-memory key/value store used for caching and queuing
# https://github.com/andymccurdy/redis-py
redis
# Social authentication framework
# https://github.com/python-social-auth/social-core
social-auth-core[all]
social-auth-core
# Django app for social-auth-core
# https://github.com/python-social-auth/social-app-django

0
contrib/netbox-housekeeping.sh Normal file → Executable file
View File

View File

@@ -1,5 +1,22 @@
{!models/extras/webhook.md!}
## Conditional Webhooks
A webhook may include a set of conditional logic expressed in JSON used to control whether a webhook triggers for a specific object. For example, you may wish to trigger a webhook for devices only when the `status` field of an object is "active":
```json
{
"and": [
{
"attr": "status.value",
"value": "active"
}
]
}
```
For more detail, see the reference documentation for NetBox's [conditional logic](../reference/conditions.md).
## Webhook Processing
When a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected in the admin UI under System > Background Tasks.

View File

@@ -3,7 +3,7 @@
NetBox includes a `housekeeping` management command that should be run nightly. This command handles:
* Clearing expired authentication sessions from the database
* Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention)
* Deleting changelog records older than the configured [retention time](../configuration/dynamic-settings.md#changelog_retention)
This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be linked from your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file.

View File

@@ -66,6 +66,22 @@ CUSTOM_VALIDATORS = {
---
## DEFAULT_USER_PREFERENCES
This is a dictionary defining the default preferences to be set for newly-created user accounts. For example, to set the default page size for all users to 100, define the following:
```python
DEFAULT_USER_PREFERENCES = {
"pagination": {
"per_page": 100
}
}
```
For a complete list of available preferences, log into NetBox and navigate to `/user/preferences/`. A period in a preference name indicates a level of nesting in the JSON data. The example above maps to `pagination.per_page`.
---
## ENFORCE_GLOBAL_UNIQUE
Default: False

View File

@@ -1,6 +1,11 @@
# NetBox Configuration
NetBox's local configuration is stored in `$INSTALL_ROOT/netbox/netbox/configuration.py`. An example configuration is provided as `configuration.example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file. While NetBox has many configuration settings, only a few of them must be defined at the time of installation: these are defined under "required settings" below.
NetBox's local configuration is stored in `$INSTALL_ROOT/netbox/netbox/configuration.py` by default. An example configuration is provided as `configuration_example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file. While NetBox has many configuration settings, only a few of them must be defined at the time of installation: these are defined under "required settings" below.
!!! info "Customizing the Configuration Module"
A custom configuration module may be specified by setting the `NETBOX_CONFIGURATION` environment variable. This must be a dotted path to the desired Python module. For example, a file named `my_config.py` in the same directory as `settings.py` would be referenced as `netbox.my_config`.
For the sake of brevity, the NetBox documentation refers to the configuration file simply as `configuration.py`.
Some configuration parameters may alternatively be defined either in `configuration.py` or within the administrative section of the user interface. Settings which are "hard-coded" in the configuration file take precedence over those defined via the UI.

View File

@@ -13,6 +13,23 @@ ADMINS = [
---
## AUTH_PASSWORD_VALIDATORS
This parameter acts as a pass-through for configuring Django's built-in password validators for local user accounts. If configured, these will be applied whenever a user's password is updated to ensure that it meets minimum criteria such as length or complexity. An example is provided below. For more detail on the available options, please see [the Django documentation](https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation).
```python
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 10,
}
},
]
```
---
## BASE_PATH
Default: None
@@ -49,6 +66,21 @@ CORS_ORIGIN_WHITELIST = [
---
## CSRF_TRUSTED_ORIGINS
Default: `[]`
Defines a list of trusted origins for unsafe (e.g. `POST`) requests. This is a pass-through to Django's [`CSRF_TRUSTED_ORIGINS`](https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS) setting. Note that each host listed must specify a scheme (e.g. `http://` or `https://).
```python
CSRF_TRUSTED_ORIGINS = (
'http://netbox.local',
'https://netbox.local',
)
```
---
## DEBUG
Default: False
@@ -140,6 +172,65 @@ EXEMPT_VIEW_PERMISSIONS = ['*']
---
## FIELD_CHOICES
Some static choice fields on models can be configured with custom values. This is done by defining `FIELD_CHOICES` as a dictionary mapping model fields to their choices. Each choice in the list must have a database value and a human-friendly label, and may optionally specify a color. (A list of available colors is provided below.)
The choices provided can either replace the stock choices provided by NetBox, or append to them. To _replace_ the available choices, specify the app, model, and field name separated by dots. For example, the site model would be referenced as `dcim.Site.status`. To _extend_ the available choices, append a plus sign to the end of this string (e.g. `dcim.Site.status+`).
For example, the following configuration would replace the default site status choices with the options Foo, Bar, and Baz:
```python
FIELD_CHOICES = {
'dcim.Site.status': (
('foo', 'Foo', 'red'),
('bar', 'Bar', 'green'),
('baz', 'Baz', 'blue'),
)
}
```
Appending a plus sign to the field identifier would instead _add_ these choices to the ones already offered:
```python
FIELD_CHOICES = {
'dcim.Site.status+': (
...
)
}
```
The following model fields support configurable choices:
* `circuits.Circuit.status`
* `dcim.Device.status`
* `dcim.PowerFeed.status`
* `dcim.Rack.status`
* `dcim.Site.status`
* `ipam.IPAddress.status`
* `ipam.IPRange.status`
* `ipam.Prefix.status`
* `ipam.VLAN.status`
* `virtualization.VirtualMachine.status`
The following colors are supported:
* `blue`
* `indigo`
* `purple`
* `pink`
* `red`
* `orange`
* `yellow`
* `green`
* `teal`
* `cyan`
* `gray`
* `black`
* `white`
---
## HTTP_PROXIES
Default: None

View File

@@ -35,7 +35,7 @@ The list of groups to assign a new user account when created using remote authen
Default: `{}` (Empty dictionary)
A mapping of permissions to assign a new user account when created using remote authentication. Each key in the dictionary should be set to a dictionary of the attributes to be applied to the permission, or `None` to allow all objects. (Requires `REMOTE_AUTH_ENABLED`.)
A mapping of permissions to assign a new user account when created using remote authentication. Each key in the dictionary should be set to a dictionary of the attributes to be applied to the permission, or `None` to allow all objects. (Requires `REMOTE_AUTH_ENABLED` as True and `REMOTE_AUTH_GROUP_SYNC_ENABLED` as False.)
---
@@ -43,7 +43,7 @@ A mapping of permissions to assign a new user account when created using remote
Default: `False`
NetBox can be configured to support remote user authentication by inferring user authentication from an HTTP header set by the HTTP reverse proxy (e.g. nginx or Apache). Set this to `True` to enable this functionality. (Local authentication will still take effect as a fallback.)
NetBox can be configured to support remote user authentication by inferring user authentication from an HTTP header set by the HTTP reverse proxy (e.g. nginx or Apache). Set this to `True` to enable this functionality. (Local authentication will still take effect as a fallback.) (`REMOTE_AUTH_DEFAULT_GROUPS` will not function if `REMOTE_AUTH_ENABLED` is enabled)
---

View File

@@ -37,4 +37,5 @@ Once component templates have been created, every new device that you create as
{!models/dcim/interfacetemplate.md!}
{!models/dcim/frontporttemplate.md!}
{!models/dcim/rearporttemplate.md!}
{!models/dcim/modulebaytemplate.md!}
{!models/dcim/devicebaytemplate.md!}

View File

@@ -17,6 +17,7 @@ Device components represent discrete objects within a device which are used to t
{!models/dcim/interface.md!}
{!models/dcim/frontport.md!}
{!models/dcim/rearport.md!}
{!models/dcim/modulebay.md!}
{!models/dcim/devicebay.md!}
{!models/dcim/inventoryitem.md!}

View File

@@ -0,0 +1,4 @@
# Modules
{!models/dcim/moduletype.md!}
{!models/dcim/module.md!}

View File

@@ -1,3 +1,4 @@
# Service Mapping
{!models/ipam/servicetemplate.md!}
{!models/ipam/service.md!}

View File

@@ -77,6 +77,10 @@ This is the human-friendly names of your script. If omitted, the class name will
A human-friendly description of what your script does.
### `field_order`
By default, script variables will be ordered in the form as they are defined in the script. `field_order` may be defined as an iterable of field names to determine the order in which variables are rendered. Any fields not included in this iterable be listed last.
### `commit_default`
The checkbox to commit database changes when executing a script is checked by default. Set `commit_default` to False under the script's Meta class to leave this option unchecked by default.

View File

@@ -50,7 +50,7 @@ The `fail()` method may optionally specify a field with which to associate the s
## Assigning Custom Validators
Custom validators are associated with specific NetBox models under the [CUSTOM_VALIDATORS](../configuration/optional-settings.md#custom_validators) configuration parameter. There are three manners by which custom validation rules can be defined:
Custom validators are associated with specific NetBox models under the [CUSTOM_VALIDATORS](../configuration/dynamic-settings.md#custom_validators) configuration parameter. There are three manners by which custom validation rules can be defined:
1. Plain JSON mapping (no custom logic)
2. Dotted path to a custom validator class

View File

@@ -95,7 +95,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, after a report has been run, extend the `post_run()` method. The status of the 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. The status of a completed report is available as `self.failed` and the results object is `self.result`.
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

@@ -2,13 +2,13 @@
## 1. Define the model class
Models within each app are stored in either `models.py` or within a submodule under the `models/` directory. When creating a model, be sure to subclass the [appropriate base model](models.md) from `netbox.models`. This will typically be PrimaryModel or OrganizationalModel. Remember to add the model class to the `__all__` listing for the module.
Models within each app are stored in either `models.py` or within a submodule under the `models/` directory. When creating a model, be sure to subclass the [appropriate base model](models.md) from `netbox.models`. This will typically be NetBoxModel or OrganizationalModel. Remember to add the model class to the `__all__` listing for the module.
Each model should define, at a minimum:
* A `Meta` class specifying a deterministic ordering (if ordered by fields other than the primary ID)
* A `__str__()` method returning a user-friendly string representation of the instance
* A `get_absolute_url()` method returning an instance's direct URL (using `reverse()`)
* A `Meta` class specifying a deterministic ordering (if ordered by fields other than the primary ID)
## 2. Define field choices
@@ -16,9 +16,9 @@ If the model has one or more fields with static choices, define those choices in
## 3. Generate database migrations
Once your model definition is complete, generate database migrations by running `manage.py -n $NAME --no-header`. Always specify a short unique name when generating migrations.
Once your model definition is complete, generate database migrations by running `manage.py makemigrations -n $NAME --no-header`. Always specify a short unique name when generating migrations.
!!! info
!!! info "Configuration Required"
Set `DEVELOPER = True` in your NetBox configuration to enable the creation of new migrations.
## 4. Add all standard views
@@ -37,25 +37,32 @@ Most models will need view classes created in `views.py` to serve the following
Add the relevant URL path for each view created in the previous step to `urls.py`.
## 6. Create the FilterSet
## 6. Add relevant forms
Depending on the type of model being added, you may need to define several types of form classes. These include:
* A base model form (for creating/editing individual objects)
* A bulk edit form
* A bulk import form (for CSV-based import)
* A filterset form (for filtering the object list view)
## 7. Create the FilterSet
Each model should have a corresponding FilterSet class defined. This is used to filter UI and API queries. Subclass the appropriate class from `netbox.filtersets` that matches the model's parent class.
Every model FilterSet should define a `q` filter to support general search queries.
## 7. Create the table
## 8. Create the table class
Create a table class for the model in `tables.py` by subclassing `utilities.tables.BaseTable`. Under the table's `Meta` class, be sure to list both the fields and default columns.
## 8. Create the object template
## 9. Create the object template
Create the HTML template for the object view. (The other views each typically employ a generic template.) This template should extend `generic/object.html`.
## 9. Add the model to the navigation menu
## 10. Add the model to the navigation menu
For NetBox releases prior to v3.0, add the relevant link(s) to the navigation menu template. For later releases, add the relevant items in `netbox/netbox/navigation_menu.py`.
Add the relevant navigation menu items in `netbox/netbox/navigation_menu.py`.
## 10. REST API components
## 11. REST API components
Create the following for each model:
@@ -64,13 +71,13 @@ Create the following for each model:
* API view in `api/views.py`
* Endpoint route in `api/urls.py`
## 11. GraphQL API components (v3.0+)
## 12. GraphQL API components
Create a Graphene object type for the model in `graphql/types.py` by subclassing the appropriate class from `netbox.graphql.types`.
Also extend the schema class defined in `graphql/schema.py` with the individual object and object list fields per the established convention.
## 12. Add tests
## 13. Add tests
Add tests for the following:
@@ -78,7 +85,7 @@ Add tests for the following:
* API views
* Filter sets
## 13. Documentation
## 14. Documentation
Create a new documentation page for the model in `docs/models/<app_label>/<model_name>.md`. Include this file under the "features" documentation where appropriate.

View File

@@ -4,16 +4,16 @@ Below is a list of tasks to consider when adding a new field to a core model.
## 1. Generate and run database migrations
Django migrations are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
[Django migrations](https://docs.djangoproject.com/en/stable/topics/migrations/) are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
```
./manage.py makemigrations <app> -n <name>
./manage.py migrate
```
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in the same migration. You can merge a new migration with an existing one by combining their `operations` lists.
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in a single migration. You can merge a newly generated migration with an existing one by combining their `operations` lists.
!!! note
!!! warning "Do not alter existing migrations"
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered (other than for the purpose of correcting a bug).
## 2. Add validation logic to `clean()`
@@ -24,7 +24,6 @@ If the new field introduces additional validation requirements (beyond what's in
class Foo(models.Model):
def clean(self):
super().clean()
# Custom validation goes here
@@ -40,9 +39,9 @@ If you're adding a relational field (e.g. `ForeignKey`) and intend to include th
Extend the model's API serializer in `<app>.api.serializers` to include the new field. In most cases, it will not be necessary to also extend the nested serializer, which produces a minimal representation of the model.
## 5. Add field to forms
## 5. Add fields to forms
Extend any forms to include the new field as appropriate. Common forms include:
Extend any forms to include the new field(s) as appropriate. These are found under the `forms/` directory within each app. Common forms include:
* **Credit/edit** - Manipulating a single object
* **Bulk edit** - Performing a change on many objects at once
@@ -51,11 +50,11 @@ Extend any forms to include the new field as appropriate. Common forms include:
## 6. Extend object filter set
If the new field should be filterable, add it to the `FilterSet` for the model. If the field should be searchable, remember to reference it in the FilterSet's `search()` method.
If the new field should be filterable, add it to the `FilterSet` for the model. If the field should be searchable, remember to query it in the FilterSet's `search()` method.
## 7. Add column to object table
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require declaring a custom column.
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require declaring a custom column. Also add the field name to `default_columns` if the column should be present in the table by default.
## 8. Update the UI templates

View File

@@ -35,6 +35,8 @@ The NetBox project utilizes three persistent git branches to track work:
Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. **Never** merge pull requests into the `master` branch, which receives merged only from the `develop` branch.
For example, assume that the current NetBox release is v3.1.1. Work applied to the `develop` branch will appear in v3.1.2, and work done under the `feature` branch will be included in the next minor release (v3.2.0).
### Enable Pre-Commit Hooks
NetBox ships with a [git pre-commit hook](https://githooks.com/) script that automatically checks for style compliance and missing database migrations prior to committing changes. This helps avoid erroneous commits that result in CI test failures. You are encouraged to enable it by creating a link to `scripts/git-hooks/pre-commit`:
@@ -46,7 +48,7 @@ $ ln -s ../../scripts/git-hooks/pre-commit
### Create a Python Virtual Environment
A [virtual environment](https://docs.python.org/3/tutorial/venv.html) is like a container for a set of Python packages. They allow you to build environments suited to specific projects without interfering with system packages or other projects. When installed per the documentation, NetBox uses a virtual environment in production.
A [virtual environment](https://docs.python.org/3/tutorial/venv.html) (or "venv" for short) is like a container for a set of Python packages. These allow you to build environments suited to specific projects without interfering with system packages or other projects. When installed per the documentation, NetBox uses a virtual environment in production.
Create a virtual environment using the `venv` Python module:
@@ -57,8 +59,8 @@ $ python3 -m venv ~/.venv/netbox
This will create a directory named `.venv/netbox/` in your home directory, which houses a virtual copy of the Python executable and its related libraries and tooling. When running NetBox for development, it will be run using the Python binary at `~/.venv/netbox/bin/python`.
!!! info
Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created wherever you please.
!!! info "Where to Create Your Virtual Environments"
Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created almost wherever you please.
Once created, activate the virtual environment:
@@ -83,7 +85,7 @@ Collecting Django==3.1 (from -r requirements.txt (line 1))
### Configure NetBox
Within the `netbox/netbox/` directory, copy `configuration.example.py` to `configuration.py` and update the following parameters:
Within the `netbox/netbox/` directory, copy `configuration_example.py` to `configuration.py` and update the following parameters:
* `ALLOWED_HOSTS`: This can be set to `['*']` for development purposes
* `DATABASE`: PostgreSQL database connection parameters
@@ -94,7 +96,7 @@ Within the `netbox/netbox/` directory, copy `configuration.example.py` to `confi
### Start the Development Server
Django provides a lightweight, auto-updating HTTP/WSGI server for development use. NetBox extends this slightly to automatically import models and other utilities. Run the NetBox development server with the `nbshell` management command:
Django provides a lightweight, auto-updating HTTP/WSGI server for development use. It is started with the `runserver` management command:
```no-highlight
$ python netbox/manage.py runserver
@@ -109,23 +111,38 @@ Quit the server with CONTROL-C.
This ensures that your development environment is now complete and operational. Any changes you make to the code base will be automatically adapted by the development server.
!!! info "IDE Integration"
Some IDEs, such as PyCharm, will integrate with Django's development server and allow you to run it directly within the IDE. This is strongly encouraged as it makes for a much more convenient development environment.
## Populating Demo Data
Once you have your development environment up and running, it might be helpful to populate some "dummy" data to make interacting with the UI and APIs more convenient. Check out the [netbox-demo-data](https://github.com/netbox-community/netbox-demo-data) repo on GitHub, which houses a collection of sample data that can be easily imported to any new NetBox deployment. (This sample data is used to populate the public demo instance at <https://demo.netbox.dev>.)
The demo data is provided in JSON format and loaded into an empty database using Django's `loaddata` management command. Consult the demo data repo's `README` file for complete instructions on populating the data.
## Running Tests
Throughout the course of development, it's a good idea to occasionally run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command:
Prior to committing any substantial changes to the code base, be sure to run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command. Remember to ensure the Python virtual environment is active before running this command. Also keep in mind that these commands are executed in the `/netbox/` directory, not the root directory of the repository.
```no-highlight
$ python netbox/manage.py test
$ python manage.py test
```
In cases where you haven't made any changes to the database (which is most of the time), you can append the `--keepdb` argument to this command to reuse the test database between runs. This cuts down on the time it takes to run the test suite since the database doesn't have to be rebuilt each time. (Note that this argument will cause errors if you've modified any model fields since the previous test run.)
```no-highlight
$ python netbox/manage.py test --keepdb
$ python manage.py test --keepdb
```
You can also limit the command to running only a specific subset of tests. For example, to run only IPAM and DCIM view tests:
```no-highlight
$ python manage.py test dcim.tests.test_views ipam.tests.test_views
```
## Submitting Pull Requests
Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. When working on a specific issue, be sure to reference it.
Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. When working on a specific issue, be sure to prefix your commit message with the word "Fixes" or "Closes" and the issue number (with a hash mark). This tells GitHub to automatically close the referenced issue once the commit has been merged.
```no-highlight
$ git commit -m "Closes #1234: Add IPv5 support"
@@ -136,5 +153,5 @@ Once your fork has the new commit, submit a [pull request](https://github.com/ne
Once submitted, a maintainer will review your pull request and either merge it or request changes. If changes are needed, you can make them via new commits to your fork: The pull request will update automatically.
!!! note
Remember, pull requests are entertained only for **accepted** issues. If an issue you want to work on hasn't been approved by a maintainer yet, it's best to avoid risking your time and effort on a change that might not be accepted.
!!! note "Remember to Open an Issue First"
Remember, pull requests are permitted only for **accepted** issues. If an issue you want to work on hasn't been approved by a maintainer yet, it's best to avoid risking your time and effort on a change that might not be accepted. (The one exception to this is trivial changes to the documentation or other non-critical resources.)

View File

@@ -1,25 +1,24 @@
# NetBox Development
NetBox is maintained as a [GitHub project](https://github.com/netbox-community/netbox) under the Apache 2 license. Users are encouraged to submit GitHub issues for feature requests and bug reports, however we are very selective about pull requests. Please see the `CONTRIBUTING` guide for more direction on contributing to NetBox.
NetBox is maintained as a [GitHub project](https://github.com/netbox-community/netbox) under the Apache 2 license. Users are encouraged to submit GitHub issues for feature requests and bug reports, however we are very selective about pull requests. Each pull request must be preceded by an **approved** issue. Please see the `CONTRIBUTING` guide for more direction on contributing to NetBox.
## Communication
There are several official forums for communication among the developers and community members:
* [GitHub issues](https://github.com/netbox-community/netbox/issues) - All feature requests, bug reports, and other substantial changes to the code base **must** be documented in an issue.
* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
* [GitHub issues](https://github.com/netbox-community/netbox/issues) - All feature requests, bug reports, and other substantial changes to the code base **must** be documented in a GitHub issue.
* [GitHub discussions](https://github.com/netbox-community/netbox/discussions) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
* [#netbox on NetDev Community Slack](https://netdev.chat/) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained long.
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being phased out in favor of GitHub discussions.
## Governance
NetBox follows the [benevolent dictator](http://oss-watch.ac.uk/resources/benevolentdictatorgovernancemodel) model of governance, with [Jeremy Stretch](https://github.com/jeremystretch) ultimately responsible for all changes to the code base. While community contributions are welcomed and encouraged, the lead maintainer's primary role is to ensure the project's long-term maintainability and continued focus on its primary functions (in other words, avoid scope creep).
NetBox follows the [benevolent dictator](http://oss-watch.ac.uk/resources/benevolentdictatorgovernancemodel) model of governance, with [Jeremy Stretch](https://github.com/jeremystretch) ultimately responsible for all changes to the code base. While community contributions are welcomed and encouraged, the lead maintainer's primary role is to ensure the project's long-term maintainability and continued focus on its primary functions.
## Project Structure
All development of the current NetBox release occurs in the `develop` branch; releases are packaged from the `master` branch. The `master` branch should _always_ represent the current stable release in its entirety, such that installing NetBox by either downloading a packaged release or cloning the `master` branch provides the same code base.
All development of the current NetBox release occurs in the `develop` branch; releases are packaged from the `master` branch. The `master` branch should _always_ represent the current stable release in its entirety, such that installing NetBox by either downloading a packaged release or cloning the `master` branch provides the same code base. Only pull requests representing new releases should be merged into `master`.
NetBox components are arranged into functional subsections called _apps_ (a carryover from Django vernacular). Each app holds the models, views, and templates relevant to a particular function:
NetBox components are arranged into Django apps. Each app holds the models, views, and other resources relevant to a particular function:
* `circuits`: Communications circuits and providers (not to be confused with power circuits)
* `dcim`: Datacenter infrastructure management (sites, racks, and devices)
@@ -29,3 +28,6 @@ NetBox components are arranged into functional subsections called _apps_ (a carr
* `users`: Authentication and user preferences
* `utilities`: Resources which are not user-facing (extendable classes, etc.)
* `virtualization`: Virtual machines and clusters
* `wireless`: Wireless links and LANs
All core functionality is stored within the `netbox/` subdirectory. HTML templates are stored in a common `templates/` directory, with model- and view-specific templates arranged by app. Documentation is kept in the `docs/` root directory.

View File

@@ -17,12 +17,12 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/
* Nesting - These models can be nested recursively to create a hierarchy
| Type | Change Logging | Webhooks | Custom Fields | Export Templates | Tags | Journaling | Nesting |
| ------------------ | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- | ---------------- |
| ------------------ | ---------------- | ---------------- |------------------| ---------------- | ---------------- | ---------------- | ---------------- |
| Primary | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | |
| Organizational | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | | |
| Nested Group | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | | :material-check: |
| Component | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | | |
| Component Template | :material-check: | :material-check: | :material-check: | | | | |
| Component Template | :material-check: | :material-check: | | | | | |
## Models Index
@@ -44,6 +44,7 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/
* [ipam.ASN](../models/ipam/asn.md)
* [ipam.FHRPGroup](../models/ipam/fhrpgroup.md)
* [ipam.IPAddress](../models/ipam/ipaddress.md)
* [ipam.IPRange](../models/ipam/iprange.md)
* [ipam.Prefix](../models/ipam/prefix.md)
* [ipam.RouteTarget](../models/ipam/routetarget.md)
* [ipam.Service](../models/ipam/service.md)

View File

@@ -1,6 +1,6 @@
# Style Guide
NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). [Pycodestyle](https://github.com/pycqa/pycodestyle) is used to validate code formatting, ignoring certain violations. See `scripts/cibuild.sh`.
NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). [Pycodestyle](https://github.com/pycqa/pycodestyle) is used to validate code formatting, ignoring certain violations. See `scripts/cibuild.sh` for details.
## PEP 8 Exceptions
@@ -30,7 +30,7 @@ pycodestyle --ignore=W504,E501 netbox/
## Introducing New Dependencies
The introduction of a new dependency is best avoided unless it is absolutely necessary. For small features, it's generally preferable to replicate functionality within the NetBox code base rather than to introduce reliance on an external project. This reduces both the burden of tracking new releases and our exposure to outside bugs and attacks.
The introduction of a new dependency is best avoided unless it is absolutely necessary. For small features, it's generally preferable to replicate functionality within the NetBox code base rather than to introduce reliance on an external project. This reduces both the burden of tracking new releases and our exposure to outside bugs and supply chain attacks.
If there's a strong case for introducing a new dependency, it must meet the following criteria:
@@ -43,7 +43,7 @@ When adding a new dependency, a short description of the package and the URL of
## General Guidance
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and open a bug so that the entire code base can be evaluated at a later point.
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and submit a separate bug report so that the entire code base can be evaluated at a later point.
* Prioritize readability over concision. Python is a very flexible language that typically offers several options for expressing a given piece of logic, but some may be more friendly to the reader than others. (List comprehensions are particularly vulnerable to over-optimization.) Always remain considerate of the future reader who may need to interpret your code without the benefit of the context within which you are writing it.

View File

@@ -4,8 +4,11 @@ The `users.UserConfig` model holds individual preferences for each user in the f
## Available Preferences
| Name | Description |
| ---- | ----------- |
| extras.configcontext.format | Preferred format when rendering config context data (JSON or YAML) |
| pagination.per_page | The number of items to display per page of a paginated table |
| tables.TABLE_NAME.columns | The ordered list of columns to display when viewing the table |
| Name | Description |
|--------------------------|---------------------------------------------------------------|
| data_format | Preferred format when rendering raw data (JSON or YAML) |
| pagination.per_page | The number of items to display per page of a paginated table |
| pagination.placement | Where to display the paginator controls relative to the table |
| tables.${table}.columns | The ordered list of columns to display when viewing the table |
| tables.${table}.ordering | A list of column names by which the table should be ordered |
| ui.colormode | Light or dark mode in the user interface |

View File

@@ -67,4 +67,4 @@ Authorization: Token $TOKEN
## Disabling the GraphQL API
If not needed, the GraphQL API can be disabled by setting the [`GRAPHQL_ENABLED`](../configuration/optional-settings.md#graphql_enabled) configuration parameter to False and restarting NetBox.
If not needed, the GraphQL API can be disabled by setting the [`GRAPHQL_ENABLED`](../configuration/dynamic-settings.md#graphql_enabled) configuration parameter to False and restarting NetBox.

View File

@@ -50,12 +50,14 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
| Application | Django/Python |
| Database | PostgreSQL 10+ |
| Task queuing | Redis/django-rq |
| Live device access | NAPALM |
| Live device access | NAPALM (optional) |
## Supported Python Versions
NetBox supports Python 3.7, 3.8, and 3.9 environments currently. (Support for Python 3.6 was removed in NetBox v3.0.)
NetBox supports Python 3.8, 3.9, and 3.10 environments.
## Getting Started
See the [installation guide](installation/index.md) for help getting NetBox up and running quickly.
Minor NetBox releases (e.g. v3.1) are published three times a year; in April, August, and December. These typically introduce major new features and may contain breaking API changes. Patch releases are published roughly every one to two weeks to resolve bugs and fulfill minor feature requests. These are backward-compatible with previous releases unless otherwise noted. The NetBox maintainers strongly recommend running the latest stable release whenever possible.
Please see the [official installation guide](installation/index.md) for detailed instructions on obtaining and installing NetBox.

View File

@@ -6,8 +6,8 @@ This section of the documentation discusses installing and configuring the NetBo
Begin by installing all system packages required by NetBox and its dependencies.
!!! warning "Python 3.7 or later required"
NetBox v3.0 and v3.1 require Python 3.7, 3.8, or 3.9. It is recommended to install at least Python v3.8, as this will become the minimum supported Python version in NetBox v3.2.
!!! warning "Python 3.8 or later required"
NetBox v3.2 requires Python 3.8, 3.9, or 3.10.
=== "Ubuntu"
@@ -17,16 +17,11 @@ Begin by installing all system packages required by NetBox and its dependencies.
=== "CentOS"
!!! warning
CentOS 8 does not provide Python 3.7 or later via its native package manager. You will need to install it via some other means. [Here is an example](https://tecadmin.net/install-python-3-7-on-centos-8/) of installing Python 3.7 from source.
Once you have Python 3.7 or later installed, install the remaining system packages:
```no-highlight
sudo yum install -y gcc libxml2-devel libxslt-devel libffi-devel libpq-devel openssl-devel redhat-rpm-config
```
Before continuing, check that your installed Python version is at least 3.7:
Before continuing, check that your installed Python version is at least 3.8:
```no-highlight
python3 -V
@@ -117,11 +112,11 @@ Create a system user account named `netbox`. We'll configure the WSGI and HTTP s
## Configuration
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`. This file will hold all of your local configuration parameters.
Move into the NetBox configuration directory and make a copy of `configuration_example.py` named `configuration.py`. This file will hold all of your local configuration parameters.
```no-highlight
cd /opt/netbox/netbox/netbox/
sudo cp configuration.example.py configuration.py
sudo cp configuration_example.py configuration.py
```
Open `configuration.py` with your preferred editor to begin configuring NetBox. NetBox offers [many configuration parameters](../configuration/index.md), but only the following four are required for new installations:
@@ -234,10 +229,10 @@ Once NetBox has been configured, we're ready to proceed with the actual installa
sudo /opt/netbox/upgrade.sh
```
Note that **Python 3.7 or later is required** for NetBox v3.0 and later releases. If the default Python installation on your server is set to a lesser version, pass the path to the supported installation as an environment variable named `PYTHON`. (Note that the environment variable must be passed _after_ the `sudo` command.)
Note that **Python 3.8 or later is required** for NetBox v3.2 and later releases. If the default Python installation on your server is set to a lesser version, pass the path to the supported installation as an environment variable named `PYTHON`. (Note that the environment variable must be passed _after_ the `sudo` command.)
```no-highlight
sudo PYTHON=/usr/bin/python3.7 /opt/netbox/upgrade.sh
sudo PYTHON=/usr/bin/python3.8 /opt/netbox/upgrade.sh
```
!!! note

View File

@@ -152,7 +152,7 @@ LOGGING = {
'netbox_auth_log': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/opt/netbox/logs/django-ldap-debug.log',
'filename': '/opt/netbox/local/logs/django-ldap-debug.log',
'maxBytes': 1024 * 500,
'backupCount': 5,
},

View File

@@ -11,15 +11,11 @@ The following sections detail how to set up a new instance of NetBox:
5. [HTTP server](5-http-server.md)
6. [LDAP authentication](6-ldap.md) (optional)
The video below demonstrates the installation of NetBox v3.0 on Ubuntu 20.04 for your reference.
<iframe width="560" height="315" src="https://www.youtube.com/embed/7Fpd2-q9_28" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Requirements
| Dependency | Minimum Version |
|------------|-----------------|
| Python | 3.7 |
| Python | 3.8 |
| PostgreSQL | 10 |
| Redis | 4.0 |

View File

@@ -10,7 +10,7 @@ NetBox v3.0 and later requires the following:
| Dependency | Minimum Version |
|------------|-----------------|
| Python | 3.7 |
| Python | 3.8 |
| PostgreSQL | 10 |
| Redis | 4.0 |
@@ -76,10 +76,10 @@ sudo ./upgrade.sh
```
!!! warning
If the default version of Python is not at least 3.7, you'll need to pass the path to a supported Python version as an environment variable when calling the upgrade script. For example:
If the default version of Python is not at least 3.8, you'll need to pass the path to a supported Python version as an environment variable when calling the upgrade script. For example:
```no-highlight
sudo PYTHON=/usr/bin/python3.7 ./upgrade.sh
sudo PYTHON=/usr/bin/python3.8 ./upgrade.sh
```
This script performs the following actions:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -2,4 +2,4 @@
This model can be used to represent the boundary of a provider network, the details of which are unknown or unimportant to the NetBox user. For example, it might represent a provider's regional MPLS network to which multiple circuits provide connectivity.
Each provider network must be assigned to a provider. A circuit may terminate to either a provider network or to a site.
Each provider network must be assigned to a provider, and may optionally be assigned an arbitrary service ID. A circuit may terminate to either a provider network or to a site.

View File

@@ -5,4 +5,4 @@ Device bays represent a space or slot within a parent device in which a child de
Child devices are first-class Devices in their own right: That is, they are fully independent managed entities which don't share any control plane with the parent. Just like normal devices, child devices have their own platform (OS), role, tags, and components. LAG interfaces may not group interfaces belonging to different child devices.
!!! note
Device bays are **not** suitable for modeling line cards (such as those commonly found in chassis-based routers and switches), as these components depend on the control plane of the parent device to operate. Instead, line cards and similarly non-autonomous hardware should be modeled as inventory items within a device, with any associated interfaces or other components assigned directly to the device.
Device bays are **not** suitable for modeling line cards (such as those commonly found in chassis-based routers and switches), as these components depend on the control plane of the parent device to operate. Instead, these should be modeled as modules installed within module bays.

View File

@@ -1,3 +1,3 @@
## Device Bay Templates
A template for a device bay that will be created on all instantiations of the parent device type.
A template for a device bay that will be created on all instantiations of the parent device type. Device bays hold child devices, such as blade servers.

View File

@@ -4,13 +4,13 @@ A device type represents a particular make and model of hardware that exists in
Device types are instantiated as devices installed within sites and/or equipment racks. For example, you might define a device type to represent a Juniper EX4300-48T network switch with 48 Ethernet interfaces. You can then create multiple _instances_ of this type named "switch1," "switch2," and so on. Each device will automatically inherit the components (such as interfaces) of its device type at the time of creation. However, changes made to a device type will **not** apply to instances of that device type retroactively.
Some devices house child devices which share physical resources, like space and power, but which functional independently from one another. A common example of this is blade server chassis. Each device type is designated as one of the following:
Some devices house child devices which share physical resources, like space and power, but which function independently. A common example of this is blade server chassis. Each device type is designated as one of the following:
* A parent device (which has device bays)
* A child device (which must be installed within a device bay)
* Neither
!!! note
This parent/child relationship is **not** suitable for modeling chassis-based devices, wherein child members share a common control plane. Instead, line cards and similarly non-autonomous hardware should be modeled as inventory items within a device, with any associated interfaces or other components assigned directly to the device.
This parent/child relationship is **not** suitable for modeling chassis-based devices, wherein child members share a common control plane. Instead, line cards and similarly non-autonomous hardware should be modeled as modules or inventory items within a device.
A device type may optionally specify an airflow direction, such as front-to-rear, rear-to-front, or passive. Airflow direction may also be set separately per device. If it is not defined for a device at the time of its creation, it will inherit the airflow setting of its device type.

View File

@@ -1,6 +1,6 @@
## Interfaces
Interfaces in NetBox represent network interfaces used to exchange data with connected devices. On modern networks, these are most commonly Ethernet, but other types are supported as well. Each interface must be assigned a type, and may optionally be assigned a MAC address, MTU, and IEEE 802.1Q mode (tagged or access). Each interface can also be enabled or disabled, and optionally designated as management-only (for out-of-band management).
Interfaces in NetBox represent network interfaces used to exchange data with connected devices. On modern networks, these are most commonly Ethernet, but other types are supported as well. Each interface must be assigned a type, and may optionally be assigned a MAC address, MTU, and IEEE 802.1Q mode (tagged or access). Each interface can also be enabled or disabled, and optionally designated as management-only (for out-of-band management). Additionally, each interface may optionally be assigned to a VRF.
!!! note
Although devices and virtual machines both can have interfaces, a separate model is used for each. Thus, device interfaces have some properties that are not present on virtual machine interfaces and vice versa.

View File

@@ -1,7 +1,7 @@
# Inventory Items
Inventory items represent hardware components installed within a device, such as a power supply or CPU or line card. Inventory items are distinct from other device components in that they cannot be templatized on a device type, and cannot be connected by cables. They are intended to be used primarily for inventory purposes.
Inventory items represent hardware components installed within a device, such as a power supply or CPU or line card. They are intended to be used primarily for inventory purposes.
Each inventory item can be assigned a manufacturer, part ID, serial number, and asset tag (all optional). A boolean toggle is also provided to indicate whether each item was entered manually or discovered automatically (by some process outside of NetBox).
Each inventory item can be assigned a functional role, manufacturer, part ID, serial number, and asset tag (all optional). A boolean toggle is also provided to indicate whether each item was entered manually or discovered automatically (by some process outside NetBox).
Inventory items are hierarchical in nature, such that any individual item may be designated as the parent for other items. For example, an inventory item might be created to represent a line card which houses several SFP optics, each of which exists as a child item within the device.
Inventory items are hierarchical in nature, such that any individual item may be designated as the parent for other items. For example, an inventory item might be created to represent a line card which houses several SFP optics, each of which exists as a child item within the device. An inventory item may also be associated with a specific component within the same device. For example, you may wish to associate a transceiver with an interface.

View File

@@ -0,0 +1,3 @@
# Inventory Item Roles
Inventory items can be organized by functional roles, which are fully customizable by the user. For example, you might create roles for power supplies, fans, interface optics, etc.

View File

@@ -0,0 +1,3 @@
# Inventory Item Templates
A template for an inventory item that will be automatically created when instantiating a new device. All attributes of this object will be copied to the new inventory item, including the associations with a parent item and assigned component, if any.

View File

@@ -0,0 +1,5 @@
# Modules
A module is a field-replaceable hardware component installed within a device which houses its own child components. The most common example is a chassis-based router or switch.
Similar to devices, modules are instantiated from module types, and any components associated with the module type are automatically instantiated on the new model. Each module must be installed within a module bay on a device, and each module bay may have only one module installed in it. A module may optionally be assigned a serial number and asset tag.

View File

@@ -0,0 +1,3 @@
## Module Bays
Module bays represent a space or slot within a device in which a field-replaceable module may be installed. A common example is that of a chassis-based switch such as the Cisco Nexus 9000 or Juniper EX9200. Modules in turn hold additional components that become available to the parent device.

View File

@@ -0,0 +1,3 @@
## Module Bay Templates
A template for a module bay that will be created on all instantiations of the parent device type. Module bays hold installed modules that do not have an independent management plane, such as line cards.

View File

@@ -0,0 +1,23 @@
# Module Types
A module type represent a specific make and model of hardware component which is installable within a device and has its own child components. For example, consider a chassis-based switch or router with a number of field-replaceable line cards. Each line card has its own model number and includes a certain set of components such as interfaces. Each module type may have a manufacturer, model number, and part number assigned to it.
Similar to device types, each module type can have any of the following component templates associated with it:
* Interfaces
* Console ports
* Console server ports
* Power ports
* Power Outlets
* Front pass-through ports
* Rear pass-through ports
Note that device bays and module bays may _not_ be added to modules.
## Automatic Component Renaming
When adding component templates to a module type, the string `{module}` can be used to reference the `position` field of the module bay into which an instance of the module type is being installed.
For example, you can create a module type with interface templates named `Gi{module}/0/[1-48]`. When a new module of this type is "installed" to a module bay with a position of "3", NetBox will automatically name these interfaces `Gi3/0/[1-48]`.
Automatic renaming is supported for all modular component types (those listed above).

View File

@@ -19,6 +19,8 @@ Custom fields may be created by navigating to Customization > Custom Fields. Net
* JSON: Arbitrary data stored in JSON format
* Selection: A selection of one of several pre-defined custom choices
* Multiple selection: A selection field which supports the assignment of multiple values
* Object: A single NetBox object of the type defined by `object_type`
* Multiple object: One or more NetBox objects of the type defined by `object_type`
Each custom field must have a name. This should be a simple database-friendly string (e.g. `tps_report`) and may contain only alphanumeric characters and underscores. You may also assign a corresponding human-friendly label (e.g. "TPS report"); the label will be displayed on web forms. A weight is also required: Higher-weight fields will be ordered lower within a form. (The default weight is 100.) If a description is provided, it will appear beneath the field in a form.
@@ -41,3 +43,7 @@ NetBox supports limited custom validation for custom field values. Following are
Each custom selection field must have at least two choices. These are specified as a comma-separated list. Choices appear in forms in the order they are listed. Note that choice values are saved exactly as they appear, so it's best to avoid superfluous punctuation or symbols where possible.
If a default value is specified for a selection field, it must exactly match one of the provided choices. The value of a multiple selection field will always return a list, even if only one value is selected.
### Custom Object Fields
An object or multi-object custom field can be used to refer to a particular NetBox object or objects as the "value" for a custom field. These custom fields must define an `object_type`, which determines the type of object to which custom field instances point.

View File

@@ -15,7 +15,7 @@ When viewing a device named Router4, this link would render as:
<a href="https://nms.example.com/nodes/?name=Router4">View NMS</a>
```
Custom links appear as buttons in the top right corner of the page. Numeric weighting can be used to influence the ordering of links.
Custom links appear as buttons in the top right corner of the page. Numeric weighting can be used to influence the ordering of links, and each link can be enabled or disabled individually.
!!! warning
Custom links rely on user-created code to generate arbitrary HTML output, which may be dangerous. Only grant permission to create or modify custom links to trusted users.
@@ -55,3 +55,7 @@ The link will only appear when viewing a device with a manufacturer name of "Cis
## Link Groups
Group names can be specified to organize links into groups. Links with the same group name will render as a dropdown menu beneath a single button bearing the name of the group.
## Table Columns
Custom links can also be included in object tables by selecting the desired links from the table configuration form. When displayed, each link will render as a hyperlink for its corresponding object. When exported (e.g. as CSV data), each link render only its URL.

View File

@@ -3,7 +3,7 @@
A webhook is a mechanism for conveying to some external system a change that took place in NetBox. For example, you may want to notify a monitoring system whenever the status of a device is updated in NetBox. This can be done by creating a webhook for the device model in NetBox and identifying the webhook receiver. When NetBox detects a change to a device, an HTTP request containing the details of the change and who made it be sent to the specified receiver. Webhooks are managed under Logging > Webhooks.
!!! warning
Webhooks support the inclusion of user-submitted code to generate custom headers and payloads, which may pose security risks under certain conditions. Only grant permission to create or modify webhooks to trusted users.
Webhooks support the inclusion of user-submitted code to generate URL, custom headers and payloads, which may pose security risks under certain conditions. Only grant permission to create or modify webhooks to trusted users.
## Configuration
@@ -12,7 +12,7 @@ A webhook is a mechanism for conveying to some external system a change that too
* **Enabled** - If unchecked, the webhook will be inactive.
* **Events** - A webhook may trigger on any combination of create, update, and delete events. At least one event type must be selected.
* **HTTP method** - The type of HTTP request to send. Options include `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`.
* **URL** - The fuly-qualified URL of the request to be sent. This may specify a destination port number if needed.
* **URL** - The fully-qualified URL of the request to be sent. This may specify a destination port number if needed. Jinja2 templating is supported for this field.
* **HTTP content type** - The value of the request's `Content-Type` header. (Defaults to `application/json`)
* **Additional headers** - Any additional headers to include with the request (optional). Add one header per line in the format `Name: Value`. Jinja2 templating is supported for this field (see below).
* **Body template** - The content of the request being sent (optional). Jinja2 templating is supported for this field (see below). If blank, NetBox will populate the request body with a raw dump of the webhook context. (If the HTTP cotent type is set to `application/json`, this will be formatted as a JSON object.)
@@ -23,7 +23,7 @@ A webhook is a mechanism for conveying to some external system a change that too
## Jinja2 Template Support
[Jinja2 templating](https://jinja.palletsprojects.com/) is supported for the `additional_headers` and `body_template` fields. This enables the user to convey object data in the request headers as well as to craft a customized request body. Request content can be crafted to enable the direct interaction with external systems by ensuring the outgoing message is in a format the receiver expects and understands.
[Jinja2 templating](https://jinja.palletsprojects.com/) is supported for the `URL`, `additional_headers` and `body_template` fields. This enables the user to convey object data in the request headers as well as to craft a customized request body. Request content can be crafted to enable the direct interaction with external systems by ensuring the outgoing message is in a format the receiver expects and understands.
For example, you might create a NetBox webhook to [trigger a Slack message](https://api.slack.com/messaging/webhooks) any time an IP address is created. You can accomplish this using the following configuration:
@@ -81,16 +81,3 @@ If no body template is specified, the request body will be populated with a JSON
}
}
```
## Conditional Webhooks
A webhook may include a set of conditional logic expressed in JSON used to control whether a webhook triggers for a specific object. For example, you may wish to trigger a webhook for devices only when the `status` field of an object is "active":
```json
{
"attr": "status",
"value": "active"
}
```
For more detail, see the reference documentation for NetBox's [conditional logic](../reference/conditions.md).

View File

@@ -0,0 +1,3 @@
# Service Templates
Service templates can be used to instantiate services on devices and virtual machines. A template defines a name, protocol, and port number(s), and may optionally include a description. Services can be instantiated from templates and applied to devices and/or virtual machines, and may be associated with specific IP addresses.

View File

@@ -2,4 +2,6 @@
VLAN groups can be used to organize VLANs within NetBox. Each VLAN group can be scoped to a particular region, site group, site, location, rack, cluster group, or cluster. Member VLANs will be available for assignment to devices and/or virtual machines within the specified scope.
A minimum and maximum child VLAN ID must be set for each group. (These default to 1 and 4094 respectively.) VLANs created within a group must have a VID that falls between these values (inclusive).
Groups can also be used to enforce uniqueness: Each VLAN within a group must have a unique ID and name. VLANs which are not assigned to a group may have overlapping names and IDs (including VLANs which belong to a common site). For example, you can create two VLANs with ID 123, but they cannot both be assigned to the same group.

View File

@@ -1,3 +1,3 @@
## Interfaces
Virtual machine interfaces behave similarly to device interfaces, and can be assigned IP addresses, VLANs, and services. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.
Virtual machine interfaces behave similarly to device interfaces, and can be assigned to VRFs, and may have IP addresses, VLANs, and services attached to them. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.

View File

@@ -1,5 +1,8 @@
# Plugin Development
!!! info "Help Improve the NetBox Plugins Framework!"
We're looking for volunteers to help improve NetBox's plugins framework. If you have experience developing plugins, we'd love to hear from you! You can find more information about this initiative [here](https://github.com/netbox-community/netbox/discussions/8338).
This documentation covers the development of custom plugins for NetBox. Plugins are essentially self-contained [Django apps](https://docs.djangoproject.com/en/stable/) which integrate with NetBox to provide custom functionality. Since the development of Django apps is already very well-documented, we'll only be covering the aspects that are specific to NetBox.
Plugins can do a lot, including:

View File

@@ -0,0 +1,27 @@
# Background Tasks
By default, Netbox provides 3 differents [RQ](https://python-rq.org/) queues to run background jobs : *high*, *default* and *low*.
These 3 core queues can be used out-of-the-box by plugins to define background tasks.
Plugins can also define dedicated queues. These queues can be configured under the PluginConfig class `queues` attribute. An example configuration
is below:
```python
class MyPluginConfig(PluginConfig):
name = 'myplugin'
...
queues = [
'queue1',
'queue2',
'queue-whatever-the-name'
]
```
The PluginConfig above creates 3 queues with the following names: *myplugin.queue1*, *myplugin.queue2*, *myplugin.queue-whatever-the-name*.
As you can see, the queue's name is always preprended with the plugin's name, to avoid any name clashes between different plugins.
In case you create dedicated queues for your plugin, it is strongly advised to also create a dedicated RQ worker instance. This instance should only listen to the queues defined in your plugin - to avoid impact between your background tasks and netbox internal tasks.
```
python manage.py rqworker myplugin.queue1 myplugin.queue2 myplugin.queue-whatever-the-name
```

View File

@@ -0,0 +1,56 @@
# Filter Sets
Filter sets define the mechanisms available for filtering or searching through a set of objects in NetBox. For instance, sites can be filtered by their parent region or group, status, facility ID, and so on. The same filter set is used consistently for a model whether the request is made via the UI, REST API, or GraphQL API. NetBox employs the [django-filters2](https://django-tables2.readthedocs.io/en/latest/) library to define filter sets.
## FilterSet Classes
To support additional functionality standard to NetBox models, such as tag assignment and custom field support, the `NetBoxModelFilterSet` class is available for use by plugins. This should be used as the base filter set class for plugin models which inherit from `NetBoxModel`. Within this class, individual filters can be declared as directed by the `django-filters` documentation. An example is provided below.
```python
# filtersets.py
import django_filters
from netbox.filtersets import NetBoxModelFilterSet
from .models import MyModel
class MyFilterSet(NetBoxModelFilterSet):
status = django_filters.MultipleChoiceFilter(
choices=(
('foo', 'Foo'),
('bar', 'Bar'),
('baz', 'Baz'),
),
null_value=None
)
class Meta:
model = MyModel
fields = ('some', 'other', 'fields')
```
## Declaring Filter Sets
To utilize a filter set in the subclass of a generic view, such as `ObjectListView` or `BulkEditView`, set it as the `filterset` attribute on the view class:
```python
# views.py
from netbox.views.generic import ObjectListView
from .filtersets import MyModelFitlerSet
from .models import MyModel
class MyModelListView(ObjectListView):
queryset = MyModel.objects.all()
filterset = MyModelFitlerSet
```
To enable a filter on a REST API endpoint, set it as the `filterset_class` attribute on the API view:
```python
# api/views.py
from myplugin import models, filtersets
from . import serializers
class MyModelViewSet(...):
queryset = models.MyModel.objects.all()
serializer_class = serializers.MyModelSerializer
filterset_class = filtersets.MyModelFilterSet
```

View File

@@ -0,0 +1,78 @@
# Forms
## Form Classes
NetBox provides several base form classes for use by plugins. These are documented below.
* `NetBoxModelForm`
* `NetBoxModelCSVForm`
* `NetBoxModelBulkEditForm`
* `NetBoxModelFilterSetForm`
### TODO: Include forms reference
In addition to the [form fields provided by Django](https://docs.djangoproject.com/en/stable/ref/forms/fields/), NetBox provides several field classes for use within forms to handle specific types of data. These can be imported from `utilities.forms.fields` and are documented below.
## General Purpose Fields
::: utilities.forms.ColorField
selection:
members: false
::: utilities.forms.CommentField
selection:
members: false
::: utilities.forms.JSONField
selection:
members: false
::: utilities.forms.MACAddressField
selection:
members: false
::: utilities.forms.SlugField
selection:
members: false
## Dynamic Object Fields
::: utilities.forms.DynamicModelChoiceField
selection:
members: false
::: utilities.forms.DynamicModelMultipleChoiceField
selection:
members: false
## Content Type Fields
::: utilities.forms.ContentTypeChoiceField
selection:
members: false
::: utilities.forms.ContentTypeMultipleChoiceField
selection:
members: false
## CSV Import Fields
::: utilities.forms.CSVChoiceField
selection:
members: false
::: utilities.forms.CSVMultipleChoiceField
selection:
members: false
::: utilities.forms.CSVModelChoiceField
selection:
members: false
::: utilities.forms.CSVContentTypeField
selection:
members: false
::: utilities.forms.CSVMultipleContentTypeField
selection:
members: false

View File

@@ -0,0 +1,59 @@
# GraphQL API
## Defining the Schema Class
A plugin can extend NetBox's GraphQL API by registering its own schema class. By default, NetBox will attempt to import `graphql.schema` from the plugin, if it exists. This path can be overridden by defining `graphql_schema` on the PluginConfig instance as the dotted path to the desired Python class. This class must be a subclass of `graphene.ObjectType`.
### Example
```python
# graphql.py
import graphene
from netbox.graphql.fields import ObjectField, ObjectListField
from . import filtersets, models
class MyModelType(graphene.ObjectType):
class Meta:
model = models.MyModel
fields = '__all__'
filterset_class = filtersets.MyModelFilterSet
class MyQuery(graphene.ObjectType):
mymodel = ObjectField(MyModelType)
mymodel_list = ObjectListField(MyModelType)
schema = MyQuery
```
## GraphQL Objects
NetBox provides two object type classes for use by plugins.
::: netbox.graphql.types.BaseObjectType
selection:
members: false
rendering:
show_source: false
::: netbox.graphql.types.NetBoxObjectType
selection:
members: false
rendering:
show_source: false
## GraphQL Fields
NetBox provides two field classes for use by plugins.
::: netbox.graphql.fields.ObjectField
selection:
members: false
rendering:
show_source: false
::: netbox.graphql.fields.ObjectListField
selection:
members: false
rendering:
show_source: false

View File

@@ -0,0 +1,147 @@
# Plugins Development
!!! info "Help Improve the NetBox Plugins Framework!"
We're looking for volunteers to help improve NetBox's plugins framework. If you have experience developing plugins, we'd love to hear from you! You can find more information about this initiative [here](https://github.com/netbox-community/netbox/discussions/8338).
This documentation covers the development of custom plugins for NetBox. Plugins are essentially self-contained [Django apps](https://docs.djangoproject.com/en/stable/) which integrate with NetBox to provide custom functionality. Since the development of Django apps is already very well-documented, we'll only be covering the aspects that are specific to NetBox.
Plugins can do a lot, including:
* Create Django models to store data in the database
* Provide their own "pages" (views) in the web user interface
* Inject template content and navigation links
* Establish their own REST API endpoints
* Add custom request/response middleware
However, keep in mind that each piece of functionality is entirely optional. For example, if your plugin merely adds a piece of middleware or an API endpoint for existing data, there's no need to define any new models.
!!! warning
While very powerful, the NetBox plugins API is necessarily limited in its scope. The plugins API is discussed here in its entirety: Any part of the NetBox code base not documented here is _not_ part of the supported plugins API, and should not be employed by a plugin. Internal elements of NetBox are subject to change at any time and without warning. Plugin authors are **strongly** encouraged to develop plugins using only the officially supported components discussed here and those provided by the underlying Django framework so as to avoid breaking changes in future releases.
## Initial Setup
### Plugin Structure
Although the specific structure of a plugin is largely left to the discretion of its authors, a typical NetBox plugin might look something like this:
```no-highlight
project-name/
- plugin_name/
- templates/
- plugin_name/
- *.html
- __init__.py
- middleware.py
- navigation.py
- signals.py
- template_content.py
- urls.py
- views.py
- README
- setup.py
```
The top level is the project root, which can have any name that you like. Immediately within the root should exist several items:
* `setup.py` - This is a standard installation script used to install the plugin package within the Python environment.
* `README` - A brief introduction to your plugin, how to install and configure it, where to find help, and any other pertinent information. It is recommended to write README files using a markup language such as Markdown.
* The plugin source directory, with the same name as your plugin. This must be a valid Python package name (e.g. no spaces or hyphens).
The plugin source directory contains all the actual Python code and other resources used by your plugin. Its structure is left to the author's discretion, however it is recommended to follow best practices as outlined in the [Django documentation](https://docs.djangoproject.com/en/stable/intro/reusable-apps/). At a minimum, this directory **must** contain an `__init__.py` file containing an instance of NetBox's `PluginConfig` class.
### Create setup.py
`setup.py` is the [setup script](https://docs.python.org/3.8/distutils/setupscript.html) we'll use to install our plugin once it's finished. The primary function of this script is to call the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to inform the package creation as well as to provide metadata about the plugin. An example `setup.py` is below:
```python
from setuptools import find_packages, setup
setup(
name='netbox-animal-sounds',
version='0.1',
description='An example NetBox plugin',
url='https://github.com/netbox-community/netbox-animal-sounds',
author='Jeremy Stretch',
license='Apache 2.0',
install_requires=[],
packages=find_packages(),
include_package_data=True,
zip_safe=False,
)
```
Many of these are self-explanatory, but for more information, see the [setuptools documentation](https://setuptools.readthedocs.io/en/latest/setuptools.html).
!!! note
`zip_safe=False` is **required** as the current plugin iteration is not zip safe due to upstream python issue [issue19699](https://bugs.python.org/issue19699)
### Define a PluginConfig
The `PluginConfig` class is a NetBox-specific wrapper around Django's built-in [`AppConfig`](https://docs.djangoproject.com/en/stable/ref/applications/) class. It is used to declare NetBox plugin functionality within a Python package. Each plugin should provide its own subclass, defining its name, metadata, and default and required configuration parameters. An example is below:
```python
from extras.plugins import PluginConfig
class AnimalSoundsConfig(PluginConfig):
name = 'netbox_animal_sounds'
verbose_name = 'Animal Sounds'
description = 'An example plugin for development purposes'
version = '0.1'
author = 'Jeremy Stretch'
author_email = 'author@example.com'
base_url = 'animal-sounds'
required_settings = []
default_settings = {
'loud': False
}
config = AnimalSoundsConfig
```
NetBox looks for the `config` variable within a plugin's `__init__.py` to load its configuration. Typically, this will be set to the PluginConfig subclass, but you may wish to dynamically generate a PluginConfig class based on environment variables or other factors.
#### PluginConfig Attributes
| Name | Description |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------|
| `name` | Raw plugin name; same as the plugin's source directory |
| `verbose_name` | Human-friendly name for the plugin |
| `version` | Current release ([semantic versioning](https://semver.org/) is encouraged) |
| `description` | Brief description of the plugin's purpose |
| `author` | Name of plugin's author |
| `author_email` | Author's public email address |
| `base_url` | Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. |
| `required_settings` | A list of any configuration parameters that **must** be defined by the user |
| `default_settings` | A dictionary of configuration parameters and their default values |
| `min_version` | Minimum version of NetBox with which the plugin is compatible |
| `max_version` | Maximum version of NetBox with which the plugin is compatible |
| `middleware` | A list of middleware classes to append after NetBox's build-in middleware |
| `template_extensions` | The dotted path to the list of template extension classes (default: `template_content.template_extensions`) |
| `menu_items` | The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) |
| `graphql_schema` | The dotted path to the plugin's GraphQL schema class, if any (default: `graphql.schema`) |
| `user_preferences` | The dotted path to the dictionary mapping of user preferences defined by the plugin (default: `preferences.preferences`) |
All required settings must be configured by the user. If a configuration parameter is listed in both `required_settings` and `default_settings`, the default setting will be ignored.
### Create a Virtual Environment
It is strongly recommended to create a Python [virtual environment](https://docs.python.org/3/tutorial/venv.html) specific to your plugin. This will afford you complete control over the installed versions of all dependencies and avoid conflicting with any system packages. This environment can live wherever you'd like, however it should be excluded from revision control. (A popular convention is to keep all virtual environments in the user's home directory, e.g. `~/.virtualenvs/`.)
```shell
python3 -m venv /path/to/my/venv
```
You can make NetBox available within this environment by creating a path file pointing to its location. This will add NetBox to the Python path upon activation. (Be sure to adjust the command below to specify your actual virtual environment path, Python version, and NetBox installation.)
```shell
cd $VENV/lib/python3.8/site-packages/
echo /opt/netbox/netbox > netbox.pth
```
### Install the Plugin for Development
To ease development, it is recommended to go ahead and install the plugin at this point using setuptools' `develop` mode. This will create symbolic links within your Python environment to the plugin development directory. Call `setup.py` from the plugin's root directory with the `develop` argument (instead of `install`):
```no-highlight
$ python setup.py develop
```

View File

@@ -0,0 +1,111 @@
# Database Models
## Creating Models
If your plugin introduces a new type of object in NetBox, you'll probably want to create a [Django model](https://docs.djangoproject.com/en/stable/topics/db/models/) for it. A model is essentially a Python representation of a database table, with attributes that represent individual columns. Model instances can be created, manipulated, and deleted using [queries](https://docs.djangoproject.com/en/stable/topics/db/queries/). Models must be defined within a file named `models.py`.
Below is an example `models.py` file containing a model with two character fields:
```python
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=50)
sound = models.CharField(max_length=50)
def __str__(self):
return self.name
```
### Migrations
Once you have defined the model(s) for your plugin, you'll need to create the database schema migrations. A migration file is essentially a set of instructions for manipulating the PostgreSQL database to support your new model, or to alter existing models. Creating migrations can usually be done automatically using Django's `makemigrations` management command.
!!! note
A plugin must be installed before it can be used with Django management commands. If you skipped this step above, run `python setup.py develop` from the plugin's root directory.
```no-highlight
$ ./manage.py makemigrations netbox_animal_sounds
Migrations for 'netbox_animal_sounds':
/home/jstretch/animal_sounds/netbox_animal_sounds/migrations/0001_initial.py
- Create model Animal
```
Next, we can apply the migration to the database with the `migrate` command:
```no-highlight
$ ./manage.py migrate netbox_animal_sounds
Operations to perform:
Apply all migrations: netbox_animal_sounds
Running migrations:
Applying netbox_animal_sounds.0001_initial... OK
```
For more background on schema migrations, see the [Django documentation](https://docs.djangoproject.com/en/stable/topics/migrations/).
## 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 numerous feature, including:
* Change logging
* Custom fields
* Custom links
* Custom validation
* Export templates
* Journaling
* Tags
* Webhooks
This class performs two crucial functions:
1. Apply any fields, methods, or attributes necessary to the operation of these features
2. Register the model with NetBox as utilizing these feature
Simply subclass BaseModel when defining a model in your plugin:
```python
# models.py
from django.db import models
from netbox.models import NetBoxModel
class MyModel(NetBoxModel):
foo = models.CharField()
...
```
### Enabling Features Individually
If you prefer instead to enable only a subset of these features for a plugin model, NetBox provides a discrete "mix-in" class for each feature. You can subclass each of these individually when defining your model. (You will also need to inherit from Django's built-in `Model` class.)
```python
# models.py
from django.db import models
from netbox.models.features import ExportTemplatesMixin, TagsMixin
class MyModel(ExportTemplatesMixin, TagsMixin, models.Model):
foo = models.CharField()
...
```
The example above will enable export templates and tags, but no other NetBox features. A complete list of available feature mixins is included below. (Inheriting all the available mixins is essentially the same as subclassing `BaseModel`.)
## Feature Mixins Reference
!!! note
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.ChangeLoggingMixin
::: netbox.models.features.CustomLinksMixin
::: netbox.models.features.CustomFieldsMixin
::: netbox.models.features.CustomValidationMixin
::: netbox.models.features.ExportTemplatesMixin
::: netbox.models.features.JournalingMixin
::: netbox.models.features.TagsMixin
::: netbox.models.features.WebhooksMixin

View File

@@ -0,0 +1,46 @@
# REST API
Plugins can declare custom endpoints on NetBox's REST API to retrieve or manipulate models or other data. These behave very similarly to views, except that instead of rendering arbitrary content using a template, data is returned in JSON format using a serializer. NetBox uses the [Django REST Framework](https://www.django-rest-framework.org/), which makes writing API serializers and views very simple.
First, we'll create a serializer for our `Animal` model, in `api/serializers.py`:
```python
from rest_framework.serializers import ModelSerializer
from netbox_animal_sounds.models import Animal
class AnimalSerializer(ModelSerializer):
class Meta:
model = Animal
fields = ('id', 'name', 'sound')
```
Next, we'll create a generic API view set that allows basic CRUD (create, read, update, and delete) operations for Animal instances. This is defined in `api/views.py`:
```python
from rest_framework.viewsets import ModelViewSet
from netbox_animal_sounds.models import Animal
from .serializers import AnimalSerializer
class AnimalViewSet(ModelViewSet):
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
```
Finally, we'll register a URL for our endpoint in `api/urls.py`. This file **must** define a variable named `urlpatterns`.
```python
from rest_framework import routers
from .views import AnimalViewSet
router = routers.DefaultRouter()
router.register('animals', AnimalViewSet)
urlpatterns = router.urls
```
With these three components in place, we can request `/api/plugins/animal-sounds/animals/` to retrieve a list of all Animal objects defined.
![NetBox REST API plugin endpoint](../../media/plugins/plugin_rest_api_endpoint.png)
!!! warning
This example is provided as a minimal reference implementation only. It does not address authentication, performance, or myriad other concerns that plugin authors should have.

View File

@@ -0,0 +1,106 @@
# Tables
NetBox employs the [`django-tables2`](https://django-tables2.readthedocs.io/) library for rendering dynamic object tables. These tables display lists of objects, and can be sorted and filtered by various parameters.
## NetBoxTable
To provide additional functionality beyond what is supported by the stock `Table` class in `django-tables2`, NetBox provides the `NetBoxTable` class. This custom table class includes support for:
* User-configurable column display and ordering
* Custom field & custom link columns
* Automatic prefetching of related objects
It also includes several default columns:
* `pk` - A checkbox for selecting the object associated with each table row
* `id` - The object's numeric database ID, as a hyperlink to the object's view
* `actions` - A dropdown menu presenting object-specific actions available to the user.
### Example
```python
# tables.py
import django_tables2 as tables
from netbox.tables import NetBoxTable
from .models import MyModel
class MyModelTable(NetBoxTable):
name = tables.Column(
linkify=True
)
...
class Meta(NetBoxTable.Meta):
model = MyModel
fields = ('pk', 'id', 'name', ...)
default_columns = ('pk', 'name', ...)
```
### Table Configuration
The NetBoxTable class supports dynamic configuration to support pagination and to effect user preferences. To configure a table for a specific request, simply call its `configure()` method and pass the current HTTPRequest object. For example:
```python
table = MyModelTable(data=MyModel.objects.all())
table.configure(request)
```
If using a generic view provided by NetBox, table configuration is handled automatically.
## Columns
The table column classes listed below are supported for use in plugins. These classes can be imported from `netbox.tables.columns`.
::: netbox.tables.BooleanColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.ChoiceFieldColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.ColorColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.ColoredLabelColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.ContentTypeColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.ContentTypesColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.MarkdownColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.TagColumn
rendering:
show_source: false
selection:
members: false
::: netbox.tables.TemplateColumn
rendering:
show_source: false
selection:
members: false

View File

@@ -0,0 +1,235 @@
# Templates
## Base Templates
The following template blocks are available on all templates.
| Name | Required | Description |
|--------------|----------|---------------------------------------------------------------------|
| `title` | Yes | Page title |
| `content` | Yes | Page content |
| `head` | - | Content to include in the HTML `<head>` element |
| `javascript` | - | Javascript content included at the end of the HTML `<body>` element |
!!! note
For more information on how template blocks work, consult the [Django documentation](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#block).
### layout.html
Path: `base/layout.html`
NetBox provides a base template to ensure a consistent user experience, which plugins can extend with their own content. This is a general-purpose template that can be used when none of the function-specific templates below are suitable.
#### Blocks
| Name | Required | Description |
|-----------|----------|----------------------------|
| `header` | - | Page header |
| `tabs` | - | Horizontal navigation tabs |
| `modals` | - | Bootstrap 5 modal elements |
#### Example
An example of a plugin template which extends `layout.html` is included below.
```jinja2
{% extends 'base/layout.html' %}
{% block header %}
<h1>My Custom Header</h1>
{% endblock header %}
{% block content %}
<p>{{ some_plugin_context_var }}</p>
{% endblock content %}
```
The first line of the template instructs Django to extend the NetBox base template and inject our custom content within its `content` block.
!!! note
Django renders templates with its own custom [template language](https://docs.djangoproject.com/en/stable/topics/templates/#the-django-template-language). This is very similar to Jinja2, however there are some important distinctions of which authors should be aware. Be sure to familiarize yourself with Django's template language before attempting to create new templates.
## Generic View Templates
### object.html
Path: `generic/object.html`
This template is used by the `ObjectView` generic view to display a single object.
#### Blocks
| Name | Required | Description |
|---------------------|----------|----------------------------------------------|
| `breadcrumbs` | - | Breadcrumb list items (HTML `<li>` elements) |
| `object_identifier` | - | A unique identifier (string) for the object |
| `extra_controls` | - | Additional action buttons to display |
| `extra_tabs` | - | Additional tabs to include |
#### Context
| Name | Required | Description |
|----------|----------|----------------------------------|
| `object` | Yes | The object instance being viewed |
### object_edit.html
Path: `generic/object_edit.html`
This template is used by the `ObjectEditView` generic view to create or modify a single object.
#### Blocks
| Name | Required | Description |
|------------------|----------|-------------------------------------------------------|
| `form` | - | Custom form content (within the HTML `<form>` element |
| `buttons` | - | Form submission buttons |
#### Context
| Name | Required | Description |
|--------------|----------|-----------------------------------------------------------------|
| `object` | Yes | The object instance being modified (or none, if creating) |
| `form` | Yes | The form class for creating/modifying the object |
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
### object_delete.html
Path: `generic/object_delete.html`
This template is used by the `ObjectDeleteView` generic view to delete a single object.
#### Blocks
None
#### Context
| Name | Required | Description |
|--------------|----------|-----------------------------------------------------------------|
| `object` | Yes | The object instance being deleted |
| `form` | Yes | The form class for confirming the object's deletion |
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
### object_list.html
Path: `generic/object_list.html`
This template is used by the `ObjectListView` generic view to display a filterable list of multiple objects.
#### Blocks
| Name | Required | Description |
|------------------|----------|--------------------------------------------------------------------|
| `extra_controls` | - | Additional action buttons |
| `bulk_buttons` | - | Additional bulk action buttons to display beneath the objects list |
#### Context
| Name | Required | Description |
|------------------|----------|-----------------------------------------------------------------------|
| `model` | Yes | The object class |
| `table` | Yes | The table class used for rendering the list of objects |
| `permissions` | Yes | A mapping of add, change, and delete permissions for the current user |
| `action_buttons` | Yes | A list of buttons to display (options are `add`, `import`, `export`) |
| `filter_form` | - | The bound filterset form for filtering the objects list |
| `return_url` | - | The return URL to pass when submitting a bulk operation form |
### bulk_import.html
Path: `generic/bulk_import.html`
This template is used by the `BulkImportView` generic view to import multiple objects at once from CSV data.
#### Blocks
None
#### Context
| Name | Required | Description |
|--------------|----------|--------------------------------------------------------------|
| `model` | Yes | The object class |
| `form` | Yes | The CSV import form class |
| `return_url` | - | The return URL to pass when submitting a bulk operation form |
| `fields` | - | A dictionary of form fields, to display import options |
### bulk_edit.html
Path: `generic/bulk_edit.html`
This template is used by the `BulkEditView` generic view to modify multiple objects simultaneously.
#### Blocks
None
#### Context
| Name | Required | Description |
|--------------|----------|-----------------------------------------------------------------|
| `model` | Yes | The object class |
| `form` | Yes | The bulk edit form class |
| `table` | Yes | The table class used for rendering the list of objects |
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
### bulk_delete.html
Path: `generic/bulk_delete.html`
This template is used by the `BulkDeleteView` generic view to delete multiple objects simultaneously.
#### Blocks
| Name | Required | Description |
|-----------------|----------|---------------------------------------|
| `message_extra` | - | Supplementary warning message content |
#### Context
| Name | Required | Description |
|--------------|----------|-----------------------------------------------------------------|
| `model` | Yes | The object class |
| `form` | Yes | The bulk delete form class |
| `table` | Yes | The table class used for rendering the list of objects |
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
## Tags
The following custom template tags are available in NetBox.
!!! info
These are loaded automatically by the template backend: You do _not_ need to include a `{% load %}` tag in your template to activate them.
::: utilities.templatetags.builtins.tags.badge
::: utilities.templatetags.builtins.tags.checkmark
::: utilities.templatetags.builtins.tags.tag
## Filters
The following custom template filters are available in NetBox.
!!! info
These are loaded automatically by the template backend: You do _not_ need to include a `{% load %}` tag in your template to activate them.
::: utilities.templatetags.builtins.filters.bettertitle
::: utilities.templatetags.builtins.filters.content_type
::: utilities.templatetags.builtins.filters.content_type_id
::: utilities.templatetags.builtins.filters.meta
::: utilities.templatetags.builtins.filters.placeholder
::: utilities.templatetags.builtins.filters.render_json
::: utilities.templatetags.builtins.filters.render_markdown
::: utilities.templatetags.builtins.filters.render_yaml
::: utilities.templatetags.builtins.filters.split
::: utilities.templatetags.builtins.filters.tzoffset

View File

@@ -0,0 +1,232 @@
# Views
If your plugin needs its own page or pages in the NetBox web UI, you'll need to define views. A view is a particular page tied to a URL within NetBox, which renders content using a template. Views are typically defined in `views.py`, and URL patterns in `urls.py`. As an example, let's write a view which displays a random animal and the sound it makes. First, we'll create the view in `views.py`:
```python
from django.shortcuts import render
from django.views.generic import View
from .models import Animal
class RandomAnimalView(View):
"""
Display a randomly-selected animal.
"""
def get(self, request):
animal = Animal.objects.order_by('?').first()
return render(request, 'netbox_animal_sounds/animal.html', {
'animal': animal,
})
```
This view retrieves a random animal from the database and and passes it as a context variable when rendering a template named `animal.html`, which doesn't exist yet. To create this template, first create a directory named `templates/netbox_animal_sounds/` within the plugin source directory. (We use the plugin's name as a subdirectory to guard against naming collisions with other plugins.) Then, create a template named `animal.html` as described below.
## View Classes
NetBox provides several generic view classes (documented below) to facilitate common operations, such as creating, viewing, modifying, and deleting objects. Plugins can subclass these views for their own use.
| View Class | Description |
|------------|-------------|
| `ObjectView` | View a single object |
| `ObjectEditView` | Create or edit a single object |
| `ObjectDeleteView` | Delete a single object |
| `ObjectListView` | View a list of objects |
| `BulkImportView` | Import a set of new objects |
| `BulkEditView` | Edit multiple objects |
| `BulkDeleteView` | Delete multiple objects |
!!! warning
Please note that only the classes which appear in this documentation are currently supported. Although other classes may be present within the `views.generic` module, they are not yet supported for use by plugins.
### Example Usage
```python
# views.py
from netbox.views.generic import ObjectEditView
from .models import Thing
class ThingEditView(ObjectEditView):
queryset = Thing.objects.all()
template_name = 'myplugin/thing.html'
...
```
## URL Registration
To make the view accessible to users, we need to register a URL for it. We do this in `urls.py` by defining a `urlpatterns` variable containing a list of paths.
```python
from django.urls import path
from . import views
urlpatterns = [
path('random/', views.RandomAnimalView.as_view(), name='random_animal'),
]
```
A URL pattern has three components:
* `route` - The unique portion of the URL dedicated to this view
* `view` - The view itself
* `name` - A short name used to identify the URL path internally
This makes our view accessible at the URL `/plugins/animal-sounds/random/`. (Remember, our `AnimalSoundsConfig` class sets our plugin's base URL to `animal-sounds`.) Viewing this URL should show the base NetBox template with our custom content inside it.
## Extending Core Views
Plugins can inject custom content into certain areas of the detail views of applicable models. This is accomplished by subclassing `PluginTemplateExtension`, designating a particular NetBox model, and defining the desired methods to render custom content. Four methods are available:
* `left_page()` - Inject content on the left side of the page
* `right_page()` - Inject content on the right side of the page
* `full_width_page()` - Inject content across the entire bottom of the page
* `buttons()` - Add buttons to the top of the page
Additionally, a `render()` method is available for convenience. This method accepts the name of a template to render, and any additional context data you want to pass. Its use is optional, however.
When a PluginTemplateExtension is instantiated, context data is assigned to `self.context`. Available data include:
* `object` - The object being viewed
* `request` - The current request
* `settings` - Global NetBox settings
* `config` - Plugin-specific configuration parameters
For example, accessing `{{ request.user }}` within a template will return the current user.
Declared subclasses should be gathered into a list or tuple for integration with NetBox. By default, NetBox looks for an iterable named `template_extensions` within a `template_content.py` file. (This can be overridden by setting `template_extensions` to a custom value on the plugin's PluginConfig.) An example is below.
```python
from extras.plugins import PluginTemplateExtension
from .models import Animal
class SiteAnimalCount(PluginTemplateExtension):
model = 'dcim.site'
def right_page(self):
return self.render('netbox_animal_sounds/inc/animal_count.html', extra_context={
'animal_count': Animal.objects.count(),
})
template_extensions = [SiteAnimalCount]
```
## Navigation Menu Items
To make its views easily accessible to users, a plugin can inject items in NetBox's navigation menu under the "Plugins" header. Menu items are added by defining a list of PluginMenuItem instances. By default, this should be a variable named `menu_items` in the file `navigation.py`. An example is shown below.
```python
from extras.plugins import PluginMenuButton, PluginMenuItem
from utilities.choices import ButtonColorChoices
menu_items = (
PluginMenuItem(
link='plugins:netbox_animal_sounds:random_animal',
link_text='Random sound',
buttons=(
PluginMenuButton('home', 'Button A', 'fa fa-info', ButtonColorChoices.BLUE),
PluginMenuButton('home', 'Button B', 'fa fa-warning', ButtonColorChoices.GREEN),
)
),
)
```
A `PluginMenuItem` has the following attributes:
* `link` - The name of the URL path to which this menu item links
* `link_text` - The text presented to the user
* `permissions` - A list of permissions required to display this link (optional)
* `buttons` - An iterable of PluginMenuButton instances to display (optional)
A `PluginMenuButton` has the following attributes:
* `link` - The name of the URL path to which this button links
* `title` - The tooltip text (displayed when the mouse hovers over the button)
* `icon_class` - Button icon CSS class (NetBox currently supports [Font Awesome 4.7](https://fontawesome.com/v4.7.0/icons/))
* `color` - One of the choices provided by `ButtonColorChoices` (optional)
* `permissions` - A list of permissions required to display this button (optional)
!!! note
Any buttons associated within a menu item will be shown only if the user has permission to view the link, regardless of what permissions are set on the buttons.
## Object Views
Below are the class definitions for NetBox's object views. These views handle CRUD actions for individual objects. The view, add/edit, and delete views each inherit from `BaseObjectView`, which is not intended to be used directly.
::: netbox.views.generic.base.BaseObjectView
rendering:
show_source: false
::: netbox.views.generic.ObjectView
selection:
members:
- get_object
- get_template_name
rendering:
show_source: false
::: netbox.views.generic.ObjectEditView
selection:
members:
- get_object
- alter_object
rendering:
show_source: false
::: netbox.views.generic.ObjectDeleteView
selection:
members:
- get_object
rendering:
show_source: false
## Multi-Object Views
Below are the class definitions for NetBox's multi-object views. These views handle simultaneous actions for sets objects. The list, import, edit, and delete views each inherit from `BaseMultiObjectView`, which is not intended to be used directly.
::: netbox.views.generic.base.BaseMultiObjectView
rendering:
show_source: false
::: netbox.views.generic.ObjectListView
selection:
members:
- get_table
- export_table
- export_template
rendering:
show_source: false
::: netbox.views.generic.BulkImportView
selection:
members: false
rendering:
show_source: false
::: netbox.views.generic.BulkEditView
selection:
members: false
rendering:
show_source: false
::: netbox.views.generic.BulkDeleteView
selection:
members:
- get_form
rendering:
show_source: false
## Feature Views
These views are provided to enable or enhance certain NetBox model features, such as change logging or journaling. These typically do not need to be subclassed: They can be used directly e.g. in a URL path.
::: netbox.views.generic.ObjectChangeLogView
selection:
members:
- get_form
rendering:
show_source: false
::: netbox.views.generic.ObjectJournalView
selection:
members:
- get_form
rendering:
show_source: false

View File

@@ -81,13 +81,16 @@ The following condition will evaluate as true:
```json
{
"attr": "status",
"attr": "status.value",
"value": ["planned", "staging"],
"op": "in",
"negate": true
}
```
!!! note "Evaluating static choice fields"
Pay close attention when evaluating static choice fields, such as the `status` field above. These fields typically render as a dictionary specifying both the field's raw value (`value`) and its human-friendly label (`label`). be sure to specify on which of these you want to match.
## Condition Sets
Multiple conditions can be combined into nested sets using AND or OR logic. This is done by declaring a JSON object with a single key (`and` or `or`) containing a list of condition objects and/or child condition sets.
@@ -102,7 +105,7 @@ Multiple conditions can be combined into nested sets using AND or OR logic. This
{
"and": [
{
"attr": "status",
"attr": "status.value",
"value": "active"
},
{

View File

@@ -1,6 +1,26 @@
# Release Notes
Listed below are the major features introduced in each NetBox release. For more detail on a specific release train, see its individual release notes page.
NetBox releases are numbered as major, minor, and patch releases. For example, version 3.1.0 is a minor release, and v3.1.5 is a patch release. Briefly, these can be described as follows:
* **Major** - Introduces or removes an entire API or other core functionality
* **Minor** - Implements major new features but may include breaking changes for API consumers or other integrations
* **Patch** - A maintenance release which fixes bugs and may introduce backward-compatible enhancements
Minor releases are published in April, August, and December of each calendar year. Patch releases are published as needed to address bugs and fulfill minor feature requests, typically around every one to two weeks.
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.2](./version-3.2.md) (Pending Release)
* Plugins Framework Extensions ([#8333](https://github.com/netbox-community/netbox/issues/8333))
* Modules & Module Types ([#7844](https://github.com/netbox-community/netbox/issues/7844))
* Custom Object Fields ([#7006](https://github.com/netbox-community/netbox/issues/7006))
* Custom Status Choices ([#8054](https://github.com/netbox-community/netbox/issues/8054))
* Improved User Preferences ([#7759](https://github.com/netbox-community/netbox/issues/7759))
* Inventory Item Roles ([#3087](https://github.com/netbox-community/netbox/issues/3087))
* Inventory Item Templates ([#8118](https://github.com/netbox-community/netbox/issues/8118))
* Service Templates ([#1591](https://github.com/netbox-community/netbox/issues/1591))
* Automatic Provisioning of Next Available VLANs ([#2658](https://github.com/netbox-community/netbox/issues/2658))
#### [Version 3.1](./version-3.1.md) (December 2021)

View File

@@ -367,7 +367,7 @@ More information about IP ranges is available [in the documentation](../models/i
#### Custom Model Validation ([#5963](https://github.com/netbox-community/netbox/issues/5963))
This release introduces the [`CUSTOM_VALIDATORS`](../configuration/optional-settings.md#custom_validators) configuration parameter, which allows administrators to map NetBox models to custom validator classes to enforce custom validation logic. For example, the following configuration requires every site to have a name of at least ten characters and a description:
This release introduces the [`CUSTOM_VALIDATORS`](../configuration/dynamic-settings.md#custom_validators) configuration parameter, which allows administrators to map NetBox models to custom validator classes to enforce custom validation logic. For example, the following configuration requires every site to have a name of at least ten characters and a description:
```python
from extras.validators import CustomValidator

View File

@@ -1,5 +1,180 @@
# NetBox v3.1
## v3.1.9 (FUTURE)
---
## v3.1.8 (2022-02-15)
### Enhancements
* [#7150](https://github.com/netbox-community/netbox/issues/7150) - Linkify devices on the far side of a rack elevation
* [#8398](https://github.com/netbox-community/netbox/issues/8398) - Embiggen configuration form fields for banner message content
* [#8556](https://github.com/netbox-community/netbox/issues/8556) - Add full username column to changelog table
* [#8620](https://github.com/netbox-community/netbox/issues/8620) - Enable tab completion for `nbshell`
### Bug Fixes
* [#8331](https://github.com/netbox-community/netbox/issues/8331) - Implement `replaceAll` string utility function to improve browser compatibility
* [#8391](https://github.com/netbox-community/netbox/issues/8391) - Null date columns should return empty strings during CSV export
* [#8548](https://github.com/netbox-community/netbox/issues/8548) - Fix display of VC members when position is zero
* [#8561](https://github.com/netbox-community/netbox/issues/8561) - Include option to connect a rear port to a console port
* [#8564](https://github.com/netbox-community/netbox/issues/8564) - Fix errant table configuration key `available_columns`
* [#8577](https://github.com/netbox-community/netbox/issues/8577) - Show contact assignment counts in global search results
* [#8578](https://github.com/netbox-community/netbox/issues/8578) - Object change log tables should honor user's configured preferences
* [#8604](https://github.com/netbox-community/netbox/issues/8604) - Fix tag filter on config context list filter form
* [#8609](https://github.com/netbox-community/netbox/issues/8609) - Display validation error when attempting to assign VLANs to interface with no mode during bulk edit
* [#8611](https://github.com/netbox-community/netbox/issues/8611) - Fix bulk editing for certain custom link, webhook, and journal entry fields
---
## v3.1.7 (2022-02-03)
### Enhancements
* [#7504](https://github.com/netbox-community/netbox/issues/7504) - Include IP range data under IPAM role views
* [#8275](https://github.com/netbox-community/netbox/issues/8275) - Introduce alternative ASDOT-formatted column for ASNs
* [#8367](https://github.com/netbox-community/netbox/issues/8367) - Add ASNs to global search function
* [#8368](https://github.com/netbox-community/netbox/issues/8368) - Enable controlling the order of custom script form fields with `field_order`
* [#8381](https://github.com/netbox-community/netbox/issues/8381) - Add contacts to global search function
* [#8462](https://github.com/netbox-community/netbox/issues/8462) - Linkify manufacturer column in device type table
* [#8476](https://github.com/netbox-community/netbox/issues/8476) - Bring the ASN Web UI up to the standard set by other objects
* [#8494](https://github.com/netbox-community/netbox/issues/8494) - Include locations count under tenant view
* [#8517](https://github.com/netbox-community/netbox/issues/8517) - Render boolean custom fields as icons in object tables
* [#8530](https://github.com/netbox-community/netbox/issues/8530) - Indicate CSV or YAML as format for "all data" export
### Bug Fixes
* [#8315](https://github.com/netbox-community/netbox/issues/8315) - Fix display of NAT link for primary IPv4 address under device view
* [#8377](https://github.com/netbox-community/netbox/issues/8377) - Fix calculation of absolute cable lengths when specified in fractional units
* [#8425](https://github.com/netbox-community/netbox/issues/8425) - Fix exception when viewing change list/records with removed plugins
* [#8456](https://github.com/netbox-community/netbox/issues/8456) - Fix redundant display of VRF RD in prefix view
* [#8465](https://github.com/netbox-community/netbox/issues/8465) - Accept empty string values for Interface `rf_channel` in REST API
* [#8498](https://github.com/netbox-community/netbox/issues/8498) - Fix display of selected content type filters in object list views
* [#8499](https://github.com/netbox-community/netbox/issues/8499) - Content types REST API endpoint should not require model permission
* [#8512](https://github.com/netbox-community/netbox/issues/8512) - Correct file permissions to allow execution of housekeeping script
* [#8527](https://github.com/netbox-community/netbox/issues/8527) - Fix display of changelog retention period
---
## v3.1.6 (2022-01-17)
### Enhancements
* [#8246](https://github.com/netbox-community/netbox/issues/8246) - Show human-friendly values for commit rates in circuits table
* [#8262](https://github.com/netbox-community/netbox/issues/8262) - Add cable count to tenant stats
* [#8265](https://github.com/netbox-community/netbox/issues/8265) - Add Stackwise-n interface types
* [#8293](https://github.com/netbox-community/netbox/issues/8293) - Show 4-byte ASNs in ASDOT notation
* [#8302](https://github.com/netbox-community/netbox/issues/8302) - Linkify role column in device & VM tables
* [#8337](https://github.com/netbox-community/netbox/issues/8337) - Enable sorting object tables by created & updated times
### Bug Fixes
* [#8279](https://github.com/netbox-community/netbox/issues/8279) - Fix display of virtual chassis members in rack elevations
* [#8285](https://github.com/netbox-community/netbox/issues/8285) - Fix `cluster_count` under tenant REST API serializer
* [#8287](https://github.com/netbox-community/netbox/issues/8287) - Correct label in export template form
* [#8301](https://github.com/netbox-community/netbox/issues/8301) - Fix delete button for various object children views
* [#8305](https://github.com/netbox-community/netbox/issues/8305) - Fix assignment of custom field data to FHRP groups via UI
* [#8306](https://github.com/netbox-community/netbox/issues/8306) - Redirect user to previous page after login
* [#8314](https://github.com/netbox-community/netbox/issues/8314) - Prevent custom fields with default values from appearing as applied filters erroneously
* [#8317](https://github.com/netbox-community/netbox/issues/8317) - Fix CSV import of multi-select custom field values
* [#8319](https://github.com/netbox-community/netbox/issues/8319) - Custom URL fields should honor `ALLOWED_URL_SCHEMES` config parameter
* [#8342](https://github.com/netbox-community/netbox/issues/8342) - Restore `created` & `last_updated` fields missing from several REST API serializers
* [#8357](https://github.com/netbox-community/netbox/issues/8357) - Add missing tags field to location filter form
* [#8358](https://github.com/netbox-community/netbox/issues/8358) - Fix inconsistent styling of custom fields on filter & bulk edit forms
---
## v3.1.5 (2022-01-06)
### Enhancements
* [#8231](https://github.com/netbox-community/netbox/issues/8231) - Use in-page dialogs for confirming object deletion
* [#8244](https://github.com/netbox-community/netbox/issues/8244) - Add length & length unit fields to cable filter form
* [#8252](https://github.com/netbox-community/netbox/issues/8252) - Linkify type and group columns in clusters table
### Bug Fixes
* [#8213](https://github.com/netbox-community/netbox/issues/8213) - Fix ValueError exception under prefix IP addresses view
* [#8224](https://github.com/netbox-community/netbox/issues/8224) - Fix KeyError exception when creating FHRP group with IP address and protocol "other"
* [#8226](https://github.com/netbox-community/netbox/issues/8226) - Honor return URL after populating a device bay
* [#8228](https://github.com/netbox-community/netbox/issues/8228) - Optional ChoiceVar fields should not force a selection
* [#8255](https://github.com/netbox-community/netbox/issues/8255) - Fix bulk editing of authentication parameters for wireless LANs and links
---
## v3.1.4 (2022-01-03)
### Enhancements
* [#8192](https://github.com/netbox-community/netbox/issues/8192) - Add "add prefix" button to aggregate child prefixes view
* [#8194](https://github.com/netbox-community/netbox/issues/8194) - Enable bulk user assignment to groups under admin UI
* [#8197](https://github.com/netbox-community/netbox/issues/8197) - Allow filtering sites by group when connecting a cable
* [#8210](https://github.com/netbox-community/netbox/issues/8210) - Establish `netbox/local/` as a path for local resources
### Bug Fixes
* [#8187](https://github.com/netbox-community/netbox/issues/8187) - Fix rendering of tags column in object tables
* [#8191](https://github.com/netbox-community/netbox/issues/8191) - Fix return URL when adding IP addresses to VM interfaces
* [#8196](https://github.com/netbox-community/netbox/issues/8196) - Fix IndexError exception when viewing large IPv6 prefixes in UI
* [#8201](https://github.com/netbox-community/netbox/issues/8201) - Custom integer fields should allow negative integers as minimum/maximum values
---
## v3.1.3 (2021-12-29)
### Enhancements
* [#6782](https://github.com/netbox-community/netbox/issues/6782) - Enable the inclusion of custom links in tables
* [#7600](https://github.com/netbox-community/netbox/issues/7600) - Include count of available IPs on prefix view
* [#8034](https://github.com/netbox-community/netbox/issues/8034) - Enable specifying custom field validators during CSV import
* [#8100](https://github.com/netbox-community/netbox/issues/8100) - Add "other" choice for FHRP group protocol
* [#8175](https://github.com/netbox-community/netbox/issues/8175) - Display parent object when attaching an image
### Bug Fixes
* [#7246](https://github.com/netbox-community/netbox/issues/7246) - Don't attempt to URL-decode NAPALM response payloads
* [#7290](https://github.com/netbox-community/netbox/issues/7290) - Defer loading API-backed form fields
* [#7887](https://github.com/netbox-community/netbox/issues/7887) - Forward `HTTP_X_FORWARDED_FOR` to custom scripts
* [#7962](https://github.com/netbox-community/netbox/issues/7962) - Fix user menu under report/script result view
* [#7972](https://github.com/netbox-community/netbox/issues/7972) - Standardize name of `RemoteUserBackend` logger
* [#8097](https://github.com/netbox-community/netbox/issues/8097) - Fix styling of Markdown tables
* [#8127](https://github.com/netbox-community/netbox/issues/8127) - Fix disassociation of interface under IP address edit view
* [#8131](https://github.com/netbox-community/netbox/issues/8131) - Restore annotation of available IPs under prefix IPs view
* [#8134](https://github.com/netbox-community/netbox/issues/8134) - Fix bulk editing of objects within dynamic tables
* [#8139](https://github.com/netbox-community/netbox/issues/8139) - Fix rendering of table configuration form under VM interfaces view
* [#8140](https://github.com/netbox-community/netbox/issues/8140) - Restore missing fields on wireless LAN & link REST API serializers
---
## v3.1.2 (2021-12-20)
### Enhancements
* [#7661](https://github.com/netbox-community/netbox/issues/7661) - Remove forced styling of custom banners
* [#7665](https://github.com/netbox-community/netbox/issues/7665) - Add toggle to show only available child prefixes
* [#7999](https://github.com/netbox-community/netbox/issues/7999) - Add 6 GHz and 60 GHz wireless channels
* [#8057](https://github.com/netbox-community/netbox/issues/8057) - Dynamic object tables using HTMX
* [#8080](https://github.com/netbox-community/netbox/issues/8080) - Link to NAT IPs for device/VM primary IPs
* [#8081](https://github.com/netbox-community/netbox/issues/8081) - Allow creating services directly from navigation menu
* [#8083](https://github.com/netbox-community/netbox/issues/8083) - Removed "related devices" panel from device view
* [#8108](https://github.com/netbox-community/netbox/issues/8108) - Improve breadcrumb links for device/VM components
### Bug Fixes
* [#7674](https://github.com/netbox-community/netbox/issues/7674) - Fix inadvertent application of device type context to virtual machines
* [#8074](https://github.com/netbox-community/netbox/issues/8074) - Ordering VMs by name should reference naturalized value
* [#8077](https://github.com/netbox-community/netbox/issues/8077) - Fix exception when attaching image to location, circuit, or power panel
* [#8078](https://github.com/netbox-community/netbox/issues/8078) - Add missing wireless models to `lsmodels()` in `nbshell`
* [#8079](https://github.com/netbox-community/netbox/issues/8079) - Fix validation of LLDP neighbors when connected device has an asset tag
* [#8088](https://github.com/netbox-community/netbox/issues/8088) - Improve legibility of text in labels with light-colored backgrounds
* [#8092](https://github.com/netbox-community/netbox/issues/8092) - Rack elevations should not include device asset tags
* [#8096](https://github.com/netbox-community/netbox/issues/8096) - Fix DataError during change logging of objects with very long string representations
* [#8101](https://github.com/netbox-community/netbox/issues/8101) - Preserve return URL when using "create and add another" button
* [#8102](https://github.com/netbox-community/netbox/issues/8102) - Raise validation error when attempting to assign an IP address to multiple objects
---
## v3.1.1 (2021-12-13)
### Enhancements

View File

@@ -0,0 +1,198 @@
# NetBox v3.2
## v3.2.0 (FUTURE)
!!! warning "Python 3.8 or Later Required"
NetBox v3.2 requires Python 3.8 or later.
### Breaking Changes
* Automatic redirection of legacy slug-based URL paths has been removed. URL-based slugs were changed to use numeric IDs in v2.11.0.
* The `asn` field has been removed from the site model. Please replicate any site ASN assignments to the ASN model introduced in NetBox v3.1 prior to upgrading.
* The `asn` query filter for sites now matches against the AS number of assigned ASN objects.
* The `contact_name`, `contact_phone`, and `contact_email` fields have been removed from the site model. Please replicate any data remaining in these fields to the contact model introduced in NetBox v3.1 prior to upgrading.
* The `created` field of all change-logged models now conveys a full datetime object, rather than only a date. (Previous date-only values will receive a timestamp of 00:00.) While this change is largely unconcerning, strictly-typed API consumers may need to be updated.
* A `pre_run()` method has been added to the base Report class. Although unlikely to affect most installations, you may need to alter any reports which already use this name for a method.
* Webhook URLs now support Jinja2 templating. Although this is unlikely to introduce any issues, it's possible that an unusual URL might trigger a Jinja2 rendering error, in which case the URL would need to be properly escaped.
### New Features
#### Plugins Framework Extensions ([#8333](https://github.com/netbox-community/netbox/issues/8333))
NetBox's plugins framework has been extended considerably in this release. Additions include:
* Officially-supported generic view classes for common CRUD operations:
* `ObjectView`
* `ObjectEditView`
* `ObjectDeleteView`
* `ObjectListView`
* `BulkImportView`
* `BulkEditView`
* `BulkDeleteView`
* The `NetBoxModel` base class, which enables various NetBox features, including:
* Change logging
* Custom fields
* Custom links
* Custom validation
* Export templates
* Journaling
* Tags
* Webhooks
* Four base form classes for manipulating objects via the UI:
* `NetBoxModelForm`
* `NetBoxModelCSVForm`
* `NetBoxModelBulkEditForm`
* `NetBoxModelFilterSetForm`
* The `NetBoxModelFilterSet` base class for plugin filter sets
* The `NetBoxTable` base class for rendering object tables with `django-tables2`, as well as various custom column classes
* Function-specific templates (for generic views)
* Various custom template tags and filters
* Plugins can now extend NetBox's GraphQL API with their own schema
No breaking changes to previously supported components have been introduced in this release. However, plugin authors are encouraged to audit their existing code for misuse of unsupported components, as much of NetBox's internal code base has been reorganized.
#### Modules & Module Types ([#7844](https://github.com/netbox-community/netbox/issues/7844))
Several new models have been added to represent field-replaceable device modules, such as line cards installed within a chassis-based switch or router. Similar to devices, each module is instantiated from a user-defined module type, and can have components (interfaces, console ports, etc.) associated with it. These components become available to the parent device once the module has been installed within a module bay. This provides a convenient mechanism to effect the addition and deletion of device components as modules are installed and removed.
Automatic renaming of module components is also supported. When a new module is created, any occurrence of the string `{module}` in a component name will be replaced with the position of the module bay into which the module is being installed.
As with device types, the NetBox community offers a selection of curated real-world module type definitions in our [device type library](https://github.com/netbox-community/devicetype-library). These YAML files can be imported directly to NetBox for your convenience.
#### Custom Object Fields ([#7006](https://github.com/netbox-community/netbox/issues/7006))
Two new types of custom field have been introduced: object and multi-object. These can be used to associate an object in NetBox with some other arbitrary object(s) regardless of its type. For example, you might create a custom field named `primary_site` on the tenant model so that each tenant can have particular site designated as its primary. The multi-object custom field type allows for the assignment of multiple objects of the same type.
Custom field object assignment is fully supported in the REST API, and functions similarly to built-in foreign key relations. Nested representations are provided automatically for each custom field object.
#### Custom Status Choices ([#8054](https://github.com/netbox-community/netbox/issues/8054))
Custom choices can be now added to most object status fields in NetBox. This is done by defining the [`FIELD_CHOICES`](../configuration/optional-settings.md#field_choices) configuration parameter to map field identifiers to an iterable of custom choices an (optionally) colors. These choices are populated automatically when NetBox initializes. For example, the following configuration will add three custom choices for the site status field, each with a designated color:
```python
FIELD_CHOICES = {
'dcim.Site.status': (
('foo', 'Foo', 'red'),
('bar', 'Bar', 'green'),
('baz', 'Baz', 'blue'),
)
}
```
This will replace all default choices for this field with those listed. If instead the intent is to _extend_ the set of default choices, this can be done by appending a plus sign (`+`) to the end of the field identifier. For example, the following will add a single extra choice while retaining the defaults provided by NetBox:
```python
FIELD_CHOICES = {
'dcim.Site.status+': (
('fubar', 'FUBAR', 'red'),
)
}
```
#### Improved User Preferences ([#7759](https://github.com/netbox-community/netbox/issues/7759))
A robust new mechanism for managing user preferences is included in this release. The user preferences form has been improved for better usability, and administrators can now define default preferences for all users with the [`DEFAULT_USER_PREFERENCES`](../configuration/dynamic-settings.md##default_user_preferences) configuration parameter. For example, this can be used to define the columns which appear by default in a table:
```python
DEFAULT_USER_PREFERENCES = {
'tables': {
'IPAddressTable': {
'columns': ['address', 'status', 'created', 'description']
}
}
}
```
Users can adjust their own preferences under their user profile. A complete list of supported preferences is available in NetBox's [developer documentation](../development/user-preferences.md).
#### Inventory Item Roles ([#3087](https://github.com/netbox-community/netbox/issues/3087))
A new model has been introduced to represent functional roles for inventory items, similar to device roles. The assignment of roles to inventory items is optional.
#### Inventory Item Templates ([#8118](https://github.com/netbox-community/netbox/issues/8118))
Inventory items can now be templatized on a device type similar to other components (such as interfaces or console ports). This enables users to better pre-model fixed hardware components such as power supplies or hard disks.
Inventory item templates can be arranged hierarchically within a device type, and may be assigned to other templated components. These relationships will be mirrored when instantiating inventory items on a newly-created device (see [#7846](https://github.com/netbox-community/netbox/issues/7846)). For example, if defining an optic assigned to an interface template on a device type, the instantiated device will mimic this relationship between the optic and interface.
#### Service Templates ([#1591](https://github.com/netbox-community/netbox/issues/1591))
A new service template model has been introduced to assist in standardizing the definition and association of applications with devices and virtual machines. As an alternative to manually defining a name, protocol, and port(s) each time a service is created, a user now has the option of selecting a pre-defined template from which these values will be populated.
#### Automatic Provisioning of Next Available VLANs ([#2658](https://github.com/netbox-community/netbox/issues/2658))
A new REST API endpoint has been added at `/api/ipam/vlan-groups/<id>/available-vlans/`. A GET request to this endpoint will return a list of available VLANs within the group. A POST request can be made specifying the name(s) of one or more VLANs to create within the group, and their VLAN IDs will be assigned automatically from the available pool.
Where it is desired to limit the range of available VLANs within a group, users can define a minimum and/or maximum VLAN ID per group (see [#8168](https://github.com/netbox-community/netbox/issues/8168)).
### Enhancements
* [#5429](https://github.com/netbox-community/netbox/issues/5429) - Enable toggling the placement of table pagination controls
* [#6954](https://github.com/netbox-community/netbox/issues/6954) - Remember users' table ordering preferences
* [#7650](https://github.com/netbox-community/netbox/issues/7650) - Expose `AUTH_PASSWORD_VALIDATORS` setting to enforce password validation for local accounts
* [#7679](https://github.com/netbox-community/netbox/issues/7679) - Add actions menu to all object tables
* [#7681](https://github.com/netbox-community/netbox/issues/7681) - Add `service_id` field for provider networks
* [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts
* [#7846](https://github.com/netbox-community/netbox/issues/7846) - Enable associating inventory items with device components
* [#7852](https://github.com/netbox-community/netbox/issues/7852) - Enable the assignment of interfaces to VRFs
* [#7853](https://github.com/netbox-community/netbox/issues/7853) - Add `speed` and `duplex` fields to device interface model
* [#8168](https://github.com/netbox-community/netbox/issues/8168) - Add `min_vid` and `max_vid` fields to VLAN group
* [#8295](https://github.com/netbox-community/netbox/issues/8295) - Jinja2 rendering is now supported for webhook URLs
* [#8296](https://github.com/netbox-community/netbox/issues/8296) - Allow disabling custom links
* [#8307](https://github.com/netbox-community/netbox/issues/8307) - Add `data_type` indicator to REST API serializer for custom fields
* [#8463](https://github.com/netbox-community/netbox/issues/8463) - Change the `created` field on all change-logged models from date to datetime
* [#8572](https://github.com/netbox-community/netbox/issues/8572) - Add a `pre_run()` method for reports
* [#8649](https://github.com/netbox-community/netbox/issues/8649) - Enable customization of configuration module using `NETBOX_CONFIGURATION` environment variable
### Other Changes
* [#7731](https://github.com/netbox-community/netbox/issues/7731) - Require Python 3.8 or later
* [#7743](https://github.com/netbox-community/netbox/issues/7743) - Remove legacy ASN field from site model
* [#7748](https://github.com/netbox-community/netbox/issues/7748) - Remove legacy contact fields from site model
* [#8031](https://github.com/netbox-community/netbox/issues/8031) - Remove automatic redirection of legacy slug-based URLs
* [#8195](https://github.com/netbox-community/netbox/issues/8195), [#8454](https://github.com/netbox-community/netbox/issues/8454) - Use 64-bit integers for all primary keys
* [#8509](https://github.com/netbox-community/netbox/issues/8509) - `CSRF_TRUSTED_ORIGINS` is now a discrete configuration parameter (rather than being populated from `ALLOWED_HOSTS`)
### REST API Changes
* Added the following endpoints:
* `/api/dcim/inventory-item-roles/`
* `/api/dcim/inventory-item-templates/`
* `/api/dcim/modules/`
* `/api/dcim/module-bays/`
* `/api/dcim/module-bay-templates/`
* `/api/dcim/module-types/`
* `/api/ipam/service-templates/`
* `/api/ipam/vlan-groups/<id>/available-vlans/`
* circuits.ProviderNetwork
* Added `service_id` field
* dcim.ConsolePort
* Added `module` field
* dcim.ConsoleServerPort
* Added `module` field
* dcim.FrontPort
* Added `module` field
* dcim.Interface
* Added `module`, `speed`, `duplex`, and `vrf` fields
* dcim.InventoryItem
* Added `component_type`, `component_id`, and `role` fields
* Added read-only `component` field (GFK)
* dcim.PowerPort
* Added `module` field
* dcim.PowerOutlet
* Added `module` field
* dcim.RearPort
* Added `module` field
* dcim.Site
* Removed the `asn`, `contact_name`, `contact_phone`, and `contact_email` fields
* extras.ConfigContext
* Add `cluster_types` field
* extras.CustomField
* Added `data_type` and `object_type` fields
* extras.CustomLink
* Added `enabled` field
* ipam.VLANGroup
* Added the `/availables-vlans/` endpoint
* Added the `min_vid` and `max_vid` fields
* virtualization.VMInterface
* Added `vrf` field

View File

@@ -1,7 +0,0 @@
# File inclusion plugin for Python-Markdown
# https://github.com/cmacmackin/markdown-include
markdown-include
# MkDocs Material theme (for documentation build)
# https://github.com/squidfunk/mkdocs-material
mkdocs-material

View File

@@ -42,7 +42,7 @@ $ curl -X POST \
https://netbox/api/users/tokens/provision/ \
--data '{
"username": "hankhill",
"password: "I<3C3H8",
"password": "I<3C3H8",
}'
```

View File

@@ -16,6 +16,22 @@ theme:
toggle:
icon: material/lightbulb
name: Switch to Light Mode
plugins:
- mkdocstrings:
handlers:
python:
setup_commands:
- import os
- import django
- os.chdir('netbox/')
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "netbox.settings")
- django.setup()
rendering:
heading_level: 3
members_order: source
show_root_heading: true
show_root_full_path: false
show_root_toc_entry: false
extra:
social:
- icon: fontawesome/brands/github
@@ -59,6 +75,7 @@ nav:
- Sites and Racks: 'core-functionality/sites-and-racks.md'
- Devices and Cabling: 'core-functionality/devices.md'
- Device Types: 'core-functionality/device-types.md'
- Modules: 'core-functionality/modules.md'
- Virtualization: 'core-functionality/virtualization.md'
- Service Mapping: 'core-functionality/services.md'
- Circuits: 'core-functionality/circuits.md'
@@ -83,7 +100,17 @@ nav:
- Webhooks: 'additional-features/webhooks.md'
- Plugins:
- Using Plugins: 'plugins/index.md'
- Developing Plugins: 'plugins/development.md'
- Developing Plugins:
- Getting Started: 'plugins/development/index.md'
- Models: 'plugins/development/models.md'
- Views: 'plugins/development/views.md'
- Templates: 'plugins/development/templates.md'
- Tables: 'plugins/development/tables.md'
- Forms: 'plugins/development/forms.md'
- Filter Sets: 'plugins/development/filtersets.md'
- REST API: 'plugins/development/rest-api.md'
- GraphQL API: 'plugins/development/graphql.md'
- Background Tasks: 'plugins/development/background-tasks.md'
- Administration:
- Authentication: 'administration/authentication.md'
- Permissions: 'administration/permissions.md'
@@ -112,6 +139,7 @@ nav:
- Release Checklist: 'development/release-checklist.md'
- Release Notes:
- Summary: 'release-notes/index.md'
- Version 3.2: 'release-notes/version-3.2.md'
- Version 3.1: 'release-notes/version-3.1.md'
- Version 3.0: 'release-notes/version-3.0.md'
- Version 2.11: 'release-notes/version-2.11.md'

View File

@@ -37,8 +37,8 @@ class ProviderNetworkSerializer(PrimaryModelSerializer):
class Meta:
model = ProviderNetwork
fields = [
'id', 'url', 'display', 'provider', 'name', 'description', 'comments', 'tags', 'custom_fields', 'created',
'last_updated',
'id', 'url', 'display', 'provider', 'name', 'service_id', 'description', 'comments', 'tags',
'custom_fields', 'created', 'last_updated',
]
@@ -100,5 +100,5 @@ class CircuitTerminationSerializer(ValidatedModelSerializer, LinkTerminationSeri
fields = [
'id', 'url', 'display', 'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed',
'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type',
'_occupied',
'_occupied', 'created', 'last_updated',
]

View File

@@ -6,6 +6,7 @@ from utilities.choices import ChoiceSet
#
class CircuitStatusChoices(ChoiceSet):
key = 'circuits.Circuit.status'
STATUS_DEPROVISIONING = 'deprovisioning'
STATUS_ACTIVE = 'active'
@@ -14,23 +15,14 @@ class CircuitStatusChoices(ChoiceSet):
STATUS_OFFLINE = 'offline'
STATUS_DECOMMISSIONED = 'decommissioned'
CHOICES = (
(STATUS_PLANNED, 'Planned'),
(STATUS_PROVISIONING, 'Provisioning'),
(STATUS_ACTIVE, 'Active'),
(STATUS_OFFLINE, 'Offline'),
(STATUS_DEPROVISIONING, 'Deprovisioning'),
(STATUS_DECOMMISSIONED, 'Decommissioned'),
)
CSS_CLASSES = {
STATUS_DEPROVISIONING: 'warning',
STATUS_ACTIVE: 'success',
STATUS_PLANNED: 'info',
STATUS_PROVISIONING: 'primary',
STATUS_OFFLINE: 'danger',
STATUS_DECOMMISSIONED: 'secondary',
}
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'),
]
#

View File

@@ -3,8 +3,7 @@ from django.db.models import Q
from dcim.filtersets import CableTerminationFilterSet
from dcim.models import Region, Site, SiteGroup
from extras.filters import TagFilter
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter
from .choices import *
@@ -19,7 +18,7 @@ __all__ = (
)
class ProviderFilterSet(PrimaryModelFilterSet):
class ProviderFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -61,7 +60,6 @@ class ProviderFilterSet(PrimaryModelFilterSet):
to_field_name='slug',
label='Site (slug)',
)
tag = TagFilter()
class Meta:
model = Provider
@@ -79,7 +77,7 @@ class ProviderFilterSet(PrimaryModelFilterSet):
)
class ProviderNetworkFilterSet(PrimaryModelFilterSet):
class ProviderNetworkFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -94,31 +92,30 @@ class ProviderNetworkFilterSet(PrimaryModelFilterSet):
to_field_name='slug',
label='Provider (slug)',
)
tag = TagFilter()
class Meta:
model = ProviderNetwork
fields = ['id', 'name']
fields = ['id', 'name', 'service_id']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(service_id__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
).distinct()
class CircuitTypeFilterSet(OrganizationalModelFilterSet):
tag = TagFilter()
class Meta:
model = CircuitType
fields = ['id', 'name', 'slug']
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -189,7 +186,6 @@ class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
to_field_name='slug',
label='Site (slug)',
)
tag = TagFilter()
class Meta:
model = Circuit

View File

@@ -2,7 +2,7 @@ from django import forms
from circuits.choices import CircuitStatusChoices
from circuits.models import *
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant
from utilities.forms import add_blank_choice, CommentField, DynamicModelChoiceField, SmallTextarea, StaticSelect
@@ -14,11 +14,7 @@ __all__ = (
)
class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Provider.objects.all(),
widget=forms.MultipleHiddenInput
)
class ProviderBulkEditForm(NetBoxModelBulkEditForm):
asn = forms.IntegerField(
required=False,
label='ASN'
@@ -47,23 +43,27 @@ class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
label='Comments'
)
class Meta:
nullable_fields = [
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
]
class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ProviderNetwork.objects.all(),
widget=forms.MultipleHiddenInput
model = Provider
fieldsets = (
(None, ('asn', 'account', 'portal_url', 'noc_contact', 'admin_contact')),
)
nullable_fields = (
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
)
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
required=False
)
description = forms.CharField(
service_id = forms.CharField(
max_length=100,
required=False,
label='Service ID'
)
description = forms.CharField(
max_length=200,
required=False
)
comments = CommentField(
@@ -71,31 +71,29 @@ class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
label='Comments'
)
class Meta:
nullable_fields = [
'description', 'comments',
]
class CircuitTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=CircuitType.objects.all(),
widget=forms.MultipleHiddenInput
model = ProviderNetwork
fieldsets = (
(None, ('provider', 'service_id', 'description')),
)
nullable_fields = (
'service_id', 'description', 'comments',
)
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class CircuitBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Circuit.objects.all(),
widget=forms.MultipleHiddenInput
model = CircuitType
fieldsets = (
(None, ('description',)),
)
nullable_fields = ('description',)
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
type = DynamicModelChoiceField(
queryset=CircuitType.objects.all(),
required=False
@@ -127,7 +125,10 @@ class CircuitBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
label='Comments'
)
class Meta:
nullable_fields = [
'tenant', 'commit_rate', 'description', 'comments',
]
model = Circuit
fieldsets = (
(None, ('type', 'provider', 'status', 'tenant', 'commit_rate', 'description')),
)
nullable_fields = (
'tenant', 'commit_rate', 'description', 'comments',
)

View File

@@ -1,6 +1,6 @@
from circuits.choices import CircuitStatusChoices
from circuits.models import *
from extras.forms import CustomFieldModelCSVForm
from netbox.forms import NetBoxModelCSVForm
from tenancy.models import Tenant
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
@@ -12,7 +12,7 @@ __all__ = (
)
class ProviderCSVForm(CustomFieldModelCSVForm):
class ProviderCSVForm(NetBoxModelCSVForm):
slug = SlugField()
class Meta:
@@ -22,7 +22,7 @@ class ProviderCSVForm(CustomFieldModelCSVForm):
)
class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
class ProviderNetworkCSVForm(NetBoxModelCSVForm):
provider = CSVModelChoiceField(
queryset=Provider.objects.all(),
to_field_name='name',
@@ -32,11 +32,11 @@ class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
class Meta:
model = ProviderNetwork
fields = [
'provider', 'name', 'description', 'comments',
'provider', 'name', 'service_id', 'description', 'comments',
]
class CircuitTypeCSVForm(CustomFieldModelCSVForm):
class CircuitTypeCSVForm(NetBoxModelCSVForm):
slug = SlugField()
class Meta:
@@ -47,7 +47,7 @@ class CircuitTypeCSVForm(CustomFieldModelCSVForm):
}
class CircuitCSVForm(CustomFieldModelCSVForm):
class CircuitCSVForm(NetBoxModelCSVForm):
provider = CSVModelChoiceField(
queryset=Provider.objects.all(),
to_field_name='name',

View File

@@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
from circuits.choices import CircuitStatusChoices
from circuits.models import *
from dcim.models import Region, Site, SiteGroup
from extras.forms import CustomFieldModelFilterForm
from netbox.forms import NetBoxModelFilterSetForm
from tenancy.forms import TenancyFilterForm
from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
@@ -16,24 +16,22 @@ __all__ = (
)
class ProviderFilterForm(CustomFieldModelFilterForm):
class ProviderFilterForm(NetBoxModelFilterSetForm):
model = Provider
field_groups = [
['q', 'tag'],
['region_id', 'site_group_id', 'site_id'],
['asn'],
]
fieldsets = (
(None, ('q', 'tag')),
('Location', ('region_id', 'site_group_id', 'site_id')),
('ASN', ('asn',)),
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
label=_('Region')
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@@ -42,8 +40,7 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
'region_id': '$region_id',
'site_group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
label=_('Site')
)
asn = forms.IntegerField(
required=False,
@@ -52,46 +49,47 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model)
class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
model = ProviderNetwork
field_groups = (
('q', 'tag'),
('provider_id',),
fieldsets = (
(None, ('q', 'tag')),
('Attributes', ('provider_id', 'service_id')),
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider'),
fetch_trigger='open'
label=_('Provider')
)
service_id = forms.CharField(
max_length=100,
required=False
)
tag = TagFilterField(model)
class CircuitTypeFilterForm(CustomFieldModelFilterForm):
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
model = CircuitType
tag = TagFilterField(model)
class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
class CircuitFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = Circuit
field_groups = [
['q', 'tag'],
['provider_id', 'provider_network_id'],
['type_id', 'status', 'commit_rate'],
['region_id', 'site_group_id', 'site_id'],
['tenant_group_id', 'tenant_id'],
]
fieldsets = (
(None, ('q', 'tag')),
('Provider', ('provider_id', 'provider_network_id')),
('Attributes', ('type_id', 'status', 'commit_rate')),
('Location', ('region_id', 'site_group_id', 'site_id')),
('Tenant', ('tenant_group_id', 'tenant_id')),
)
type_id = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(),
required=False,
label=_('Type'),
fetch_trigger='open'
label=_('Type')
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider'),
fetch_trigger='open'
label=_('Provider')
)
provider_network_id = DynamicModelMultipleChoiceField(
queryset=ProviderNetwork.objects.all(),
@@ -99,8 +97,7 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
query_params={
'provider_id': '$provider_id'
},
label=_('Provider network'),
fetch_trigger='open'
label=_('Provider network')
)
status = forms.MultipleChoiceField(
choices=CircuitStatusChoices,
@@ -110,14 +107,12 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region'),
fetch_trigger='open'
label=_('Region')
)
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group'),
fetch_trigger='open'
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@@ -126,8 +121,7 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
'region_id': '$region_id',
'site_group_id': '$site_group_id',
},
label=_('Site'),
fetch_trigger='open'
label=_('Site')
)
commit_rate = forms.IntegerField(
required=False,

View File

@@ -2,8 +2,8 @@ from django import forms
from circuits.models import *
from dcim.models import Region, Site, SiteGroup
from extras.forms import CustomFieldModelForm
from extras.models import Tag
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms import (
BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
@@ -19,7 +19,7 @@ __all__ = (
)
class ProviderForm(CustomFieldModelForm):
class ProviderForm(NetBoxModelForm):
slug = SlugField()
comments = CommentField()
tags = DynamicModelMultipleChoiceField(
@@ -27,15 +27,16 @@ class ProviderForm(CustomFieldModelForm):
required=False
)
fieldsets = (
('Provider', ('name', 'slug', 'asn', 'tags')),
('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
)
class Meta:
model = Provider
fields = [
'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
]
fieldsets = (
('Provider', ('name', 'slug', 'asn', 'tags')),
('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
)
widgets = {
'noc_contact': SmallTextarea(
attrs={'rows': 5}
@@ -53,7 +54,7 @@ class ProviderForm(CustomFieldModelForm):
}
class ProviderNetworkForm(CustomFieldModelForm):
class ProviderNetworkForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all()
)
@@ -63,17 +64,18 @@ class ProviderNetworkForm(CustomFieldModelForm):
required=False
)
fieldsets = (
('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
)
class Meta:
model = ProviderNetwork
fields = [
'provider', 'name', 'description', 'comments', 'tags',
'provider', 'name', 'service_id', 'description', 'comments', 'tags',
]
fieldsets = (
('Provider Network', ('provider', 'name', 'description', 'tags')),
)
class CircuitTypeForm(CustomFieldModelForm):
class CircuitTypeForm(NetBoxModelForm):
slug = SlugField()
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
@@ -87,7 +89,7 @@ class CircuitTypeForm(CustomFieldModelForm):
]
class CircuitForm(TenancyForm, CustomFieldModelForm):
class CircuitForm(TenancyForm, NetBoxModelForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all()
)
@@ -100,16 +102,17 @@ class CircuitForm(TenancyForm, CustomFieldModelForm):
required=False
)
fieldsets = (
('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
('Tenancy', ('tenant_group', 'tenant')),
)
class Meta:
model = Circuit
fields = [
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
'comments', 'tags',
]
fieldsets = (
('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
('Tenancy', ('tenant_group', 'tenant')),
)
help_texts = {
'cid': "Unique circuit ID",
'commit_rate': "Committed rate",

View File

@@ -1,5 +1,5 @@
from circuits import filtersets, models
from netbox.graphql.types import ObjectType, OrganizationalObjectType, PrimaryObjectType
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
__all__ = (
'CircuitTerminationType',
@@ -18,7 +18,7 @@ class CircuitTerminationType(ObjectType):
filterset_class = filtersets.CircuitTerminationFilterSet
class CircuitType(PrimaryObjectType):
class CircuitType(NetBoxObjectType):
class Meta:
model = models.Circuit
@@ -34,7 +34,7 @@ class CircuitTypeType(OrganizationalObjectType):
filterset_class = filtersets.CircuitTypeFilterSet
class ProviderType(PrimaryObjectType):
class ProviderType(NetBoxObjectType):
class Meta:
model = models.Provider
@@ -42,7 +42,7 @@ class ProviderType(PrimaryObjectType):
filterset_class = filtersets.ProviderFilterSet
class ProviderNetworkType(PrimaryObjectType):
class ProviderNetworkType(NetBoxObjectType):
class Meta:
model = models.ProviderNetwork

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0004_rename_cable_peer'),
]
operations = [
migrations.AddField(
model_name='providernetwork',
name='service_id',
field=models.CharField(blank=True, max_length=100),
),
]

View File

@@ -0,0 +1,44 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0032_provider_service_id'),
]
operations = [
# Model IDs
migrations.AlterField(
model_name='circuit',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='circuittermination',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='circuittype',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='provider',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='providernetwork',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
# GFK IDs
migrations.AlterField(
model_name='circuittermination',
name='_link_peer_id',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 4.0.2 on 2022-02-08 18:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0033_standardize_id_fields'),
]
operations = [
migrations.AlterField(
model_name='circuit',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='circuittermination',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='circuittype',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='provider',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='providernetwork',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
]

View File

@@ -5,8 +5,8 @@ from django.urls import reverse
from circuits.choices import *
from dcim.models import LinkTermination
from extras.utils import extras_features
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
from netbox.models import ChangeLoggedModel, OrganizationalModel, NetBoxModel
from netbox.models.features import WebhooksMixin
__all__ = (
'Circuit',
@@ -15,7 +15,6 @@ __all__ = (
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class CircuitType(OrganizationalModel):
"""
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
@@ -44,8 +43,7 @@ class CircuitType(OrganizationalModel):
return reverse('circuits:circuittype', args=[self.pk])
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Circuit(PrimaryModel):
class Circuit(NetBoxModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
@@ -134,12 +132,11 @@ class Circuit(PrimaryModel):
def get_absolute_url(self):
return reverse('circuits:circuit', args=[self.pk])
def get_status_class(self):
return CircuitStatusChoices.CSS_CLASSES.get(self.status)
def get_status_color(self):
return CircuitStatusChoices.colors.get(self.status)
@extras_features('webhooks')
class CircuitTermination(ChangeLoggedModel, LinkTermination):
class CircuitTermination(WebhooksMixin, ChangeLoggedModel, LinkTermination):
circuit = models.ForeignKey(
to='circuits.Circuit',
on_delete=models.CASCADE,
@@ -212,13 +209,9 @@ class CircuitTermination(ChangeLoggedModel, LinkTermination):
raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
def to_objectchange(self, action):
# Annotate the parent Circuit
try:
circuit = self.circuit
except Circuit.DoesNotExist:
# Parent circuit has been deleted
circuit = None
return super().to_objectchange(action, related_object=circuit)
objectchange = super().to_objectchange(action)
objectchange.related_object = self.circuit
return objectchange
@property
def parent_object(self):

View File

@@ -3,9 +3,7 @@ from django.db import models
from django.urls import reverse
from dcim.fields import ASNField
from extras.utils import extras_features
from netbox.models import PrimaryModel
from utilities.querysets import RestrictedQuerySet
from netbox.models import NetBoxModel
__all__ = (
'ProviderNetwork',
@@ -13,8 +11,7 @@ __all__ = (
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Provider(PrimaryModel):
class Provider(NetBoxModel):
"""
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.
@@ -73,8 +70,7 @@ class Provider(PrimaryModel):
return reverse('circuits:provider', args=[self.pk])
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ProviderNetwork(PrimaryModel):
class ProviderNetwork(NetBoxModel):
"""
This represents a provider network which exists outside of NetBox, the details of which are unknown or
unimportant to the user.
@@ -87,6 +83,11 @@ class ProviderNetwork(PrimaryModel):
on_delete=models.PROTECT,
related_name='networks'
)
service_id = models.CharField(
max_length=100,
blank=True,
verbose_name='Service ID'
)
description = models.CharField(
max_length=200,
blank=True

View File

@@ -1,11 +1,10 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
from netbox.tables import NetBoxTable, columns
from tenancy.tables import TenantColumn
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, MarkdownColumn, TagColumn, ToggleColumn
from .models import *
__all__ = (
'CircuitTable',
'CircuitTypeTable',
@@ -23,12 +22,32 @@ CIRCUITTERMINATION_LINK = """
"""
#
# Table columns
#
class CommitRateColumn(tables.TemplateColumn):
"""
Humanize the commit rate in the column view
"""
template_code = """
{% load helpers %}
{{ record.commit_rate|humanize_speed }}
"""
def __init__(self, *args, **kwargs):
super().__init__(template_code=self.template_code, *args, **kwargs)
def value(self, value):
return str(value) if value else None
#
# Providers
#
class ProviderTable(BaseTable):
pk = ToggleColumn()
class ProviderTable(NetBoxTable):
name = tables.Column(
linkify=True
)
@@ -36,16 +55,16 @@ class ProviderTable(BaseTable):
accessor=Accessor('count_circuits'),
verbose_name='Circuits'
)
comments = MarkdownColumn()
tags = TagColumn(
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:provider_list'
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = Provider
fields = (
'pk', 'id', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count',
'comments', 'tags',
'comments', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
@@ -54,54 +73,54 @@ class ProviderTable(BaseTable):
# Provider networks
#
class ProviderNetworkTable(BaseTable):
pk = ToggleColumn()
class ProviderNetworkTable(NetBoxTable):
name = tables.Column(
linkify=True
)
provider = tables.Column(
linkify=True
)
comments = MarkdownColumn()
tags = TagColumn(
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:providernetwork_list'
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = ProviderNetwork
fields = ('pk', 'id', 'name', 'provider', 'description', 'comments', 'tags')
default_columns = ('pk', 'name', 'provider', 'description')
fields = (
'pk', 'id', 'name', 'provider', 'service_id', 'description', 'comments', 'created', 'last_updated', 'tags',
)
default_columns = ('pk', 'name', 'provider', 'service_id', 'description')
#
# Circuit types
#
class CircuitTypeTable(BaseTable):
pk = ToggleColumn()
class CircuitTypeTable(NetBoxTable):
name = tables.Column(
linkify=True
)
tags = TagColumn(
tags = columns.TagColumn(
url_name='circuits:circuittype_list'
)
circuit_count = tables.Column(
verbose_name='Circuits'
)
actions = ButtonsColumn(CircuitType)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = CircuitType
fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
fields = (
'pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug')
#
# Circuits
#
class CircuitTable(BaseTable):
pk = ToggleColumn()
class CircuitTable(NetBoxTable):
cid = tables.Column(
linkify=True,
verbose_name='Circuit ID'
@@ -109,7 +128,7 @@ class CircuitTable(BaseTable):
provider = tables.Column(
linkify=True
)
status = ChoiceFieldColumn()
status = columns.ChoiceFieldColumn()
tenant = TenantColumn()
termination_a = tables.TemplateColumn(
template_code=CIRCUITTERMINATION_LINK,
@@ -119,16 +138,17 @@ class CircuitTable(BaseTable):
template_code=CIRCUITTERMINATION_LINK,
verbose_name='Side Z'
)
comments = MarkdownColumn()
tags = TagColumn(
commit_rate = CommitRateColumn()
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:circuit_list'
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = Circuit
fields = (
'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date',
'commit_rate', 'description', 'comments', 'tags',
'commit_rate', 'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'description',

View File

@@ -1,8 +1,7 @@
from django.urls import path
from dcim.views import CableCreateView, PathTraceView
from extras.views import ObjectChangeLogView, ObjectJournalView
from utilities.views import SlugRedirectView
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
from . import views
from .models import *
@@ -16,7 +15,6 @@ urlpatterns = [
path('providers/edit/', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'),
path('providers/delete/', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
path('providers/<int:pk>/', views.ProviderView.as_view(), name='provider'),
path('providers/<slug:slug>/', SlugRedirectView.as_view(), kwargs={'model': Provider}),
path('providers/<int:pk>/edit/', views.ProviderEditView.as_view(), name='provider_edit'),
path('providers/<int:pk>/delete/', views.ProviderDeleteView.as_view(), name='provider_delete'),
path('providers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),

View File

@@ -5,10 +5,8 @@ from django.shortcuts import get_object_or_404, redirect, render
from netbox.views import generic
from utilities.forms import ConfirmationForm
from utilities.tables import paginate_table
from utilities.utils import count_related
from . import filtersets, forms, tables
from .choices import CircuitTerminationSideChoices
from .models import *
@@ -35,7 +33,7 @@ class ProviderView(generic.ObjectView):
'type', 'tenant', 'terminations__site'
)
circuits_table = tables.CircuitTable(circuits, exclude=('provider',))
paginate_table(circuits_table, request)
circuits_table.configure(request)
return {
'circuits_table': circuits_table,
@@ -96,7 +94,7 @@ class ProviderNetworkView(generic.ObjectView):
'type', 'tenant', 'terminations__site'
)
circuits_table = tables.CircuitTable(circuits)
paginate_table(circuits_table, request)
circuits_table.configure(request)
return {
'circuits_table': circuits_table,
@@ -150,7 +148,7 @@ class CircuitTypeView(generic.ObjectView):
def get_extra_context(self, request, instance):
circuits = Circuit.objects.restrict(request.user, 'view').filter(type=instance)
circuits_table = tables.CircuitTable(circuits, exclude=('type',))
paginate_table(circuits_table, request)
circuits_table.configure(request)
return {
'circuits_table': circuits_table,
@@ -320,7 +318,7 @@ class CircuitTerminationEditView(generic.ObjectEditView):
model_form = forms.CircuitTerminationForm
template_name = 'circuits/circuittermination_edit.html'
def alter_obj(self, obj, request, url_args, url_kwargs):
def alter_object(self, obj, request, url_args, url_kwargs):
if 'circuit' in url_kwargs:
obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit'])
return obj

View File

@@ -4,6 +4,7 @@ from dcim import models
from netbox.api.serializers import BaseModelSerializer, WritableNestedSerializer
__all__ = [
'ComponentNestedModuleSerializer',
'NestedCableSerializer',
'NestedConsolePortSerializer',
'NestedConsolePortTemplateSerializer',
@@ -19,7 +20,13 @@ __all__ = [
'NestedInterfaceSerializer',
'NestedInterfaceTemplateSerializer',
'NestedInventoryItemSerializer',
'NestedInventoryItemRoleSerializer',
'NestedInventoryItemTemplateSerializer',
'NestedManufacturerSerializer',
'NestedModuleBaySerializer',
'NestedModuleBayTemplateSerializer',
'NestedModuleSerializer',
'NestedModuleTypeSerializer',
'NestedPlatformSerializer',
'NestedPowerFeedSerializer',
'NestedPowerOutletSerializer',
@@ -117,7 +124,7 @@ class NestedRackReservationSerializer(WritableNestedSerializer):
#
# Device types
# Device/module types
#
class NestedManufacturerSerializer(WritableNestedSerializer):
@@ -139,6 +146,20 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'device_count']
class NestedModuleTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
manufacturer = NestedManufacturerSerializer(read_only=True)
# module_count = serializers.IntegerField(read_only=True)
class Meta:
model = models.ModuleType
fields = ['id', 'url', 'display', 'manufacturer', 'model']
#
# Component templates
#
class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail')
@@ -195,6 +216,14 @@ class NestedFrontPortTemplateSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'name']
class NestedModuleBayTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebaytemplate-detail')
class Meta:
model = models.ModuleBayTemplate
fields = ['id', 'url', 'display', 'name']
class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
@@ -203,6 +232,15 @@ class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'name']
class NestedInventoryItemTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = models.InventoryItemTemplate
fields = ['id', 'url', 'display', 'name', '_depth']
#
# Devices
#
@@ -235,6 +273,37 @@ class NestedDeviceSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'name']
class ModuleNestedModuleBaySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
class Meta:
model = models.ModuleBay
fields = ['id', 'url', 'display', 'name']
class ComponentNestedModuleSerializer(WritableNestedSerializer):
"""
Used by device component serializers.
"""
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
module_bay = ModuleNestedModuleBaySerializer(read_only=True)
class Meta:
model = models.Module
fields = ['id', 'url', 'display', 'device', 'module_bay']
class NestedModuleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
device = NestedDeviceSerializer(read_only=True)
module_bay = ModuleNestedModuleBaySerializer(read_only=True)
module_type = NestedModuleTypeSerializer(read_only=True)
class Meta:
model = models.Module
fields = ['id', 'url', 'display', 'device', 'module_bay', 'module_type']
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
device = NestedDeviceSerializer(read_only=True)
@@ -298,6 +367,15 @@ class NestedFrontPortSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'device', 'name', 'cable', '_occupied']
class NestedModuleBaySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
module = NestedModuleSerializer(read_only=True)
class Meta:
model = models.ModuleBay
fields = ['id', 'url', 'display', 'module', 'name']
class NestedDeviceBaySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
device = NestedDeviceSerializer(read_only=True)
@@ -317,6 +395,15 @@ class NestedInventoryItemSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'display', 'device', 'name', '_depth']
class NestedInventoryItemRoleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemrole-detail')
inventoryitem_count = serializers.IntegerField(read_only=True)
class Meta:
model = models.InventoryItemRole
fields = ['id', 'url', 'display', 'name', 'slug', 'inventoryitem_count']
#
# Cables
#

View File

@@ -6,7 +6,9 @@ from timezone_field.rest_framework import TimeZoneSerializerField
from dcim.choices import *
from dcim.constants import *
from dcim.models import *
from ipam.api.nested_serializers import NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer
from ipam.api.nested_serializers import (
NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer,
)
from ipam.models import ASN, VLAN
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import (
@@ -132,10 +134,10 @@ class SiteSerializer(PrimaryModelSerializer):
class Meta:
model = Site
fields = [
'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'asns',
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asns',
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments',
'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count', 'device_count', 'prefix_count',
'rack_count', 'virtualmachine_count', 'vlan_count',
]
@@ -219,7 +221,7 @@ class RackReservationSerializer(PrimaryModelSerializer):
class Meta:
model = RackReservation
fields = [
'id', 'url', 'display', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags',
'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description', 'tags',
'custom_fields',
]
@@ -261,7 +263,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer):
#
# Device types
# Device/module types
#
class ManufacturerSerializer(PrimaryModelSerializer):
@@ -294,6 +296,23 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
]
class ModuleTypeSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
manufacturer = NestedManufacturerSerializer()
# module_count = serializers.IntegerField(read_only=True)
class Meta:
model = ModuleType
fields = [
'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'custom_fields',
'created', 'last_updated',
]
#
# Component templates
#
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail')
device_type = NestedDeviceTypeSerializer()
@@ -409,6 +428,18 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer):
]
class ModuleBayTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebaytemplate-detail')
device_type = NestedDeviceTypeSerializer()
class Meta:
model = ModuleBayTemplate
fields = [
'id', 'url', 'display', 'device_type', 'name', 'label', 'position', 'description', 'created',
'last_updated',
]
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebaytemplate-detail')
device_type = NestedDeviceTypeSerializer()
@@ -418,6 +449,40 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']
class InventoryItemTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
device_type = NestedDeviceTypeSerializer()
parent = serializers.PrimaryKeyRelatedField(
queryset=InventoryItemTemplate.objects.all(),
allow_null=True,
default=None
)
role = NestedInventoryItemRoleSerializer(required=False, allow_null=True)
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
component_type = ContentTypeField(
queryset=ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS),
required=False,
allow_null=True
)
component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = InventoryItemTemplate
fields = [
'id', 'url', 'display', 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id',
'description', 'component_type', 'component_id', 'component', 'created', 'last_updated', '_depth',
]
@swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_component(self, obj):
if obj.component is None:
return None
serializer = get_serializer_for_model(obj.component, prefix='Nested')
context = {'request': self.context['request']}
return serializer(obj.component, context=context).data
#
# Devices
#
@@ -491,6 +556,20 @@ class DeviceSerializer(PrimaryModelSerializer):
return data
class ModuleSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
device = NestedDeviceSerializer()
module_bay = NestedModuleBaySerializer()
module_type = NestedModuleTypeSerializer()
class Meta:
model = Module
fields = [
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags',
'custom_fields', 'created', 'last_updated',
]
class DeviceWithConfigContextSerializer(DeviceSerializer):
config_context = serializers.SerializerMethodField()
@@ -518,6 +597,10 @@ class DeviceNAPALMSerializer(serializers.Serializer):
class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(
choices=ConsolePortTypeChoices,
allow_blank=True,
@@ -533,8 +616,8 @@ class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSeriali
class Meta:
model = ConsoleServerPort
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
]
@@ -542,6 +625,10 @@ class ConsoleServerPortSerializer(PrimaryModelSerializer, LinkTerminationSeriali
class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(
choices=ConsolePortTypeChoices,
allow_blank=True,
@@ -557,8 +644,8 @@ class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
class Meta:
model = ConsolePort
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected',
'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
]
@@ -566,6 +653,10 @@ class ConsolePortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
class PowerOutletSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(
choices=PowerOutletTypeChoices,
allow_blank=True,
@@ -587,15 +678,20 @@ class PowerOutletSerializer(PrimaryModelSerializer, LinkTerminationSerializer, C
class Meta:
model = PowerOutlet
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg',
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint',
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
'last_updated', '_occupied',
]
class PowerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(
choices=PowerPortTypeChoices,
allow_blank=True,
@@ -606,22 +702,28 @@ class PowerPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
class Meta:
model = PowerPort
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'connected_endpoint',
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
'last_updated', '_occupied',
]
class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(choices=InterfaceTypeChoices)
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, allow_blank=True, required=False)
duplex = ChoiceField(choices=InterfaceDuplexChoices, allow_blank=True, required=False)
rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_null=True)
rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False)
rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
@@ -629,6 +731,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
required=False,
many=True
)
vrf = NestedVRFSerializer(required=False, allow_null=True)
cable = NestedCableSerializer(read_only=True)
wireless_link = NestedWirelessLinkSerializer(read_only=True)
wireless_lans = SerializedPKRelatedField(
@@ -643,12 +746,12 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
class Meta:
model = Interface
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu',
'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'wireless_link',
'link_peer', 'link_peer_type', 'wireless_lans', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
'count_fhrp_groups', '_occupied',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag',
'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected',
'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint',
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
]
def validate(self, data):
@@ -668,13 +771,17 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
class RearPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(choices=PortTypeChoices)
cable = NestedCableSerializer(read_only=True)
class Meta:
model = RearPort
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'positions', 'description',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'description',
'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags', 'custom_fields', 'created',
'last_updated', '_occupied',
]
@@ -694,6 +801,10 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
class FrontPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
device = NestedDeviceSerializer()
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
)
type = ChoiceField(choices=PortTypeChoices)
rear_port = FrontPortRearPortSerializer()
cable = NestedCableSerializer(read_only=True)
@@ -701,9 +812,22 @@ class FrontPortSerializer(PrimaryModelSerializer, LinkTerminationSerializer):
class Meta:
model = FrontPort
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags', 'custom_fields',
'created', 'last_updated', '_occupied',
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port',
'rear_port_position', 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type', 'tags',
'custom_fields', 'created', 'last_updated', '_occupied',
]
class ModuleBaySerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
device = NestedDeviceSerializer()
# installed_module = NestedModuleSerializer(required=False, allow_null=True)
class Meta:
model = ModuleBay
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'position', 'description', 'tags', 'custom_fields',
'created', 'last_updated',
]
@@ -720,22 +844,50 @@ class DeviceBaySerializer(PrimaryModelSerializer):
]
#
# Inventory items
#
class InventoryItemSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
device = NestedDeviceSerializer()
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
role = NestedInventoryItemRoleSerializer(required=False, allow_null=True)
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
component_type = ContentTypeField(
queryset=ContentType.objects.filter(MODULAR_COMPONENT_MODELS),
required=False,
allow_null=True
)
component = serializers.SerializerMethodField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = InventoryItem
fields = [
'id', 'url', 'display', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial',
'asset_tag', 'discovered', 'description', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
'id', 'url', 'display', 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial',
'asset_tag', 'discovered', 'description', 'component_type', 'component_id', 'component', 'tags',
'custom_fields', 'created', 'last_updated', '_depth',
]
@swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_component(self, obj):
if obj.component is None:
return None
serializer = get_serializer_for_model(obj.component, prefix='Nested')
context = {'request': self.context['request']}
return serializer(obj.component, context=context).data
#
# Device component roles
#
class InventoryItemRoleSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemrole-detail')
inventoryitem_count = serializers.IntegerField(read_only=True)
class Meta:
model = InventoryItemRole
fields = [
'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields', 'created',
'last_updated', 'inventoryitem_count',
]
@@ -762,7 +914,7 @@ class CableSerializer(PrimaryModelSerializer):
fields = [
'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
'termination_b_id', 'termination_b', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit',
'tags', 'custom_fields',
'tags', 'custom_fields', 'created', 'last_updated',
]
def _get_termination(self, obj, side):
@@ -856,7 +1008,10 @@ class VirtualChassisSerializer(PrimaryModelSerializer):
class Meta:
model = VirtualChassis
fields = ['id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
fields = [
'id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count',
'created', 'last_updated',
]
#
@@ -875,7 +1030,10 @@ class PowerPanelSerializer(PrimaryModelSerializer):
class Meta:
model = PowerPanel
fields = ['id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count']
fields = [
'id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count',
'created', 'last_updated',
]
class PowerFeedSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):

View File

@@ -16,9 +16,10 @@ router.register('rack-roles', views.RackRoleViewSet)
router.register('racks', views.RackViewSet)
router.register('rack-reservations', views.RackReservationViewSet)
# Device types
# Device/module types
router.register('manufacturers', views.ManufacturerViewSet)
router.register('device-types', views.DeviceTypeViewSet)
router.register('module-types', views.ModuleTypeViewSet)
# Device type components
router.register('console-port-templates', views.ConsolePortTemplateViewSet)
@@ -28,12 +29,15 @@ router.register('power-outlet-templates', views.PowerOutletTemplateViewSet)
router.register('interface-templates', views.InterfaceTemplateViewSet)
router.register('front-port-templates', views.FrontPortTemplateViewSet)
router.register('rear-port-templates', views.RearPortTemplateViewSet)
router.register('module-bay-templates', views.ModuleBayTemplateViewSet)
router.register('device-bay-templates', views.DeviceBayTemplateViewSet)
router.register('inventory-item-templates', views.InventoryItemTemplateViewSet)
# Devices
# Device/modules
router.register('device-roles', views.DeviceRoleViewSet)
router.register('platforms', views.PlatformViewSet)
router.register('devices', views.DeviceViewSet)
router.register('modules', views.ModuleViewSet)
# Device components
router.register('console-ports', views.ConsolePortViewSet)
@@ -43,9 +47,13 @@ router.register('power-outlets', views.PowerOutletViewSet)
router.register('interfaces', views.InterfaceViewSet)
router.register('front-ports', views.FrontPortViewSet)
router.register('rear-ports', views.RearPortViewSet)
router.register('module-bays', views.ModuleBayViewSet)
router.register('device-bays', views.DeviceBayViewSet)
router.register('inventory-items', views.InventoryItemViewSet)
# Device component roles
router.register('inventory-item-roles', views.InventoryItemRoleViewSet)
# Cables
router.register('cables', views.CableViewSet)

View File

@@ -15,14 +15,14 @@ from circuits.models import Circuit
from dcim import filtersets
from dcim.models import *
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
from ipam.models import Prefix, VLAN, ASN
from ipam.models import Prefix, VLAN
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.exceptions import ServiceUnavailable
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.views import ModelViewSet
from netbox.config import get_config
from utilities.api import get_serializer_for_model
from utilities.utils import count_related, decode_dict
from utilities.utils import count_related
from virtualization.models import VirtualMachine
from . import serializers
from .exceptions import MissingFilterException
@@ -271,7 +271,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet):
#
# Device types
# Device/module types
#
class DeviceTypeViewSet(CustomFieldModelViewSet):
@@ -283,6 +283,15 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
brief_prefetch_fields = ['manufacturer']
class ModuleTypeViewSet(CustomFieldModelViewSet):
queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate(
# module_count=count_related(Module, 'module_type')
)
serializer_class = serializers.ModuleTypeSerializer
filterset_class = filtersets.ModuleTypeFilterSet
brief_prefetch_fields = ['manufacturer']
#
# Device type components
#
@@ -329,12 +338,24 @@ class RearPortTemplateViewSet(ModelViewSet):
filterset_class = filtersets.RearPortTemplateFilterSet
class ModuleBayTemplateViewSet(ModelViewSet):
queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer')
serializer_class = serializers.ModuleBayTemplateSerializer
filterset_class = filtersets.ModuleBayTemplateFilterSet
class DeviceBayTemplateViewSet(ModelViewSet):
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
serializer_class = serializers.DeviceBayTemplateSerializer
filterset_class = filtersets.DeviceBayTemplateFilterSet
class InventoryItemTemplateViewSet(ModelViewSet):
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
serializer_class = serializers.InventoryItemTemplateSerializer
filterset_class = filtersets.InventoryItemTemplateFilterSet
#
# Device roles
#
@@ -362,7 +383,7 @@ class PlatformViewSet(CustomFieldModelViewSet):
#
# Devices
# Devices/modules
#
class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
@@ -501,7 +522,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
response[method] = {'error': 'Only get_* NAPALM methods are supported'}
continue
try:
response[method] = decode_dict(getattr(d, method)())
response[method] = getattr(d, method)()
except NotImplementedError:
response[method] = {'error': 'Method {} not implemented for NAPALM driver {}'.format(method, driver)}
except Exception as e:
@@ -511,12 +532,22 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
return Response(response)
class ModuleViewSet(CustomFieldModelViewSet):
queryset = Module.objects.prefetch_related(
'device', 'module_bay', 'module_type__manufacturer', 'tags',
)
serializer_class = serializers.ModuleSerializer
filterset_class = filtersets.ModuleFilterSet
#
# Device components
#
class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
queryset = ConsolePort.objects.prefetch_related(
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
)
serializer_class = serializers.ConsolePortSerializer
filterset_class = filtersets.ConsolePortFilterSet
brief_prefetch_fields = ['device']
@@ -524,7 +555,7 @@ class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
queryset = ConsoleServerPort.objects.prefetch_related(
'device', '_path__destination', 'cable', '_link_peer', 'tags'
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
)
serializer_class = serializers.ConsoleServerPortSerializer
filterset_class = filtersets.ConsoleServerPortFilterSet
@@ -532,14 +563,18 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
queryset = PowerPort.objects.prefetch_related(
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
)
serializer_class = serializers.PowerPortSerializer
filterset_class = filtersets.PowerPortFilterSet
brief_prefetch_fields = ['device']
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_link_peer', 'tags')
queryset = PowerOutlet.objects.prefetch_related(
'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
)
serializer_class = serializers.PowerOutletSerializer
filterset_class = filtersets.PowerOutletFilterSet
brief_prefetch_fields = ['device']
@@ -547,8 +582,8 @@ class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
queryset = Interface.objects.prefetch_related(
'device', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer', 'wireless_lans',
'untagged_vlan', 'tagged_vlans', 'ip_addresses', 'fhrp_group_assignments', 'tags'
'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer',
'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags'
)
serializer_class = serializers.InterfaceSerializer
filterset_class = filtersets.InterfaceFilterSet
@@ -556,33 +591,56 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
queryset = FrontPort.objects.prefetch_related(
'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable', 'tags'
)
serializer_class = serializers.FrontPortSerializer
filterset_class = filtersets.FrontPortFilterSet
brief_prefetch_fields = ['device']
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
queryset = RearPort.objects.prefetch_related(
'device__device_type__manufacturer', 'module__module_bay', 'cable', 'tags'
)
serializer_class = serializers.RearPortSerializer
filterset_class = filtersets.RearPortFilterSet
brief_prefetch_fields = ['device']
class ModuleBayViewSet(ModelViewSet):
queryset = ModuleBay.objects.prefetch_related('tags')
serializer_class = serializers.ModuleBaySerializer
filterset_class = filtersets.ModuleBayFilterSet
brief_prefetch_fields = ['device']
class DeviceBayViewSet(ModelViewSet):
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags')
serializer_class = serializers.DeviceBaySerializer
filterset_class = filtersets.DeviceBayFilterSet
brief_prefetch_fields = ['device']
class InventoryItemViewSet(ModelViewSet):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
serializer_class = serializers.InventoryItemSerializer
filterset_class = filtersets.InventoryItemFilterSet
brief_prefetch_fields = ['device']
#
# Device component roles
#
class InventoryItemRoleViewSet(CustomFieldModelViewSet):
queryset = InventoryItemRole.objects.prefetch_related('tags').annotate(
inventoryitem_count=count_related(InventoryItem, 'role')
)
serializer_class = serializers.InventoryItemRoleSerializer
filterset_class = filtersets.InventoryItemRoleFilterSet
#
# Cables
#

View File

@@ -6,6 +6,7 @@ from utilities.choices import ChoiceSet
#
class SiteStatusChoices(ChoiceSet):
key = 'dcim.Site.status'
STATUS_PLANNED = 'planned'
STATUS_STAGING = 'staging'
@@ -13,21 +14,13 @@ class SiteStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
STATUS_RETIRED = 'retired'
CHOICES = (
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGING, 'Staging'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
(STATUS_RETIRED, 'Retired'),
)
CSS_CLASSES = {
STATUS_PLANNED: 'info',
STATUS_STAGING: 'primary',
STATUS_ACTIVE: 'success',
STATUS_DECOMMISSIONING: 'warning',
STATUS_RETIRED: 'danger',
}
CHOICES = [
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_STAGING, 'Staging', 'blue'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
(STATUS_RETIRED, 'Retired', 'red'),
]
#
@@ -67,6 +60,7 @@ class RackWidthChoices(ChoiceSet):
class RackStatusChoices(ChoiceSet):
key = 'dcim.Rack.status'
STATUS_RESERVED = 'reserved'
STATUS_AVAILABLE = 'available'
@@ -74,21 +68,13 @@ class RackStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
(STATUS_RESERVED, 'Reserved'),
(STATUS_AVAILABLE, 'Available'),
(STATUS_PLANNED, 'Planned'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DEPRECATED, 'Deprecated'),
)
CSS_CLASSES = {
STATUS_RESERVED: 'warning',
STATUS_AVAILABLE: 'success',
STATUS_PLANNED: 'info',
STATUS_ACTIVE: 'primary',
STATUS_DEPRECATED: 'danger',
}
CHOICES = [
(STATUS_RESERVED, 'Reserved', 'yellow'),
(STATUS_AVAILABLE, 'Available', 'green'),
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_ACTIVE, 'Active', 'blue'),
(STATUS_DEPRECATED, 'Deprecated', 'red'),
]
class RackDimensionUnitChoices(ChoiceSet):
@@ -144,6 +130,7 @@ class DeviceFaceChoices(ChoiceSet):
class DeviceStatusChoices(ChoiceSet):
key = 'dcim.Device.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
@@ -153,25 +140,15 @@ class DeviceStatusChoices(ChoiceSet):
STATUS_INVENTORY = 'inventory'
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGED, 'Staged'),
(STATUS_FAILED, 'Failed'),
(STATUS_INVENTORY, 'Inventory'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
)
CSS_CLASSES = {
STATUS_OFFLINE: 'warning',
STATUS_ACTIVE: 'success',
STATUS_PLANNED: 'info',
STATUS_STAGED: 'primary',
STATUS_FAILED: 'danger',
STATUS_INVENTORY: 'secondary',
STATUS_DECOMMISSIONING: 'warning',
}
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'),
]
class DeviceAirflowChoices(ChoiceSet):
@@ -816,6 +793,10 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus'
TYPE_FLEXSTACK = 'cisco-flexstack'
TYPE_FLEXSTACK_PLUS = 'cisco-flexstack-plus'
TYPE_STACKWISE80 = 'cisco-stackwise-80'
TYPE_STACKWISE160 = 'cisco-stackwise-160'
TYPE_STACKWISE320 = 'cisco-stackwise-320'
TYPE_STACKWISE480 = 'cisco-stackwise-480'
TYPE_JUNIPER_VCP = 'juniper-vcp'
TYPE_SUMMITSTACK = 'extreme-summitstack'
TYPE_SUMMITSTACK128 = 'extreme-summitstack-128'
@@ -950,6 +931,10 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'),
(TYPE_FLEXSTACK, 'Cisco FlexStack'),
(TYPE_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'),
(TYPE_STACKWISE80, 'Cisco StackWise-80'),
(TYPE_STACKWISE160, 'Cisco StackWise-160'),
(TYPE_STACKWISE320, 'Cisco StackWise-320'),
(TYPE_STACKWISE480, 'Cisco StackWise-480'),
(TYPE_JUNIPER_VCP, 'Juniper VCP'),
(TYPE_SUMMITSTACK, 'Extreme SummitStack'),
(TYPE_SUMMITSTACK128, 'Extreme SummitStack-128'),
@@ -966,6 +951,19 @@ class InterfaceTypeChoices(ChoiceSet):
)
class InterfaceDuplexChoices(ChoiceSet):
DUPLEX_HALF = 'half'
DUPLEX_FULL = 'full'
DUPLEX_AUTO = 'auto'
CHOICES = (
(DUPLEX_HALF, 'Half'),
(DUPLEX_FULL, 'Full'),
(DUPLEX_AUTO, 'Auto'),
)
class InterfaceModeChoices(ChoiceSet):
MODE_ACCESS = 'access'
@@ -1144,17 +1142,11 @@ class LinkStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
(STATUS_CONNECTED, 'Connected'),
(STATUS_PLANNED, 'Planned'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
(STATUS_CONNECTED, 'Connected', 'green'),
(STATUS_PLANNED, 'Planned', 'blue'),
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
)
CSS_CLASSES = {
STATUS_CONNECTED: 'success',
STATUS_PLANNED: 'info',
STATUS_DECOMMISSIONING: 'warning',
}
class CableLengthUnitChoices(ChoiceSet):
@@ -1183,25 +1175,19 @@ class CableLengthUnitChoices(ChoiceSet):
#
class PowerFeedStatusChoices(ChoiceSet):
key = 'dcim.PowerFeed.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
STATUS_PLANNED = 'planned'
STATUS_FAILED = 'failed'
CHOICES = (
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_FAILED, 'Failed'),
)
CSS_CLASSES = {
STATUS_OFFLINE: 'warning',
STATUS_ACTIVE: 'success',
STATUS_PLANNED: 'info',
STATUS_FAILED: 'danger',
}
CHOICES = [
(STATUS_OFFLINE, 'Offline', 'gray'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_PLANNED, 'Planned', 'blue'),
(STATUS_FAILED, 'Failed', 'red'),
]
class PowerFeedTypeChoices(ChoiceSet):
@@ -1210,15 +1196,10 @@ class PowerFeedTypeChoices(ChoiceSet):
TYPE_REDUNDANT = 'redundant'
CHOICES = (
(TYPE_PRIMARY, 'Primary'),
(TYPE_REDUNDANT, 'Redundant'),
(TYPE_PRIMARY, 'Primary', 'green'),
(TYPE_REDUNDANT, 'Redundant', 'cyan'),
)
CSS_CLASSES = {
TYPE_PRIMARY: 'success',
TYPE_REDUNDANT: 'info',
}
class PowerFeedSupplyChoices(ChoiceSet):

View File

@@ -50,16 +50,43 @@ NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
#
# PowerFeeds
# Power feeds
#
POWERFEED_VOLTAGE_DEFAULT = 120
POWERFEED_AMPERAGE_DEFAULT = 20
POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage
#
# Device components
#
MODULAR_COMPONENT_TEMPLATE_MODELS = Q(
app_label='dcim',
model__in=(
'consoleporttemplate',
'consoleserverporttemplate',
'frontporttemplate',
'interfacetemplate',
'poweroutlettemplate',
'powerporttemplate',
'rearporttemplate',
))
MODULAR_COMPONENT_MODELS = Q(
app_label='dcim',
model__in=(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'poweroutlet',
'powerport',
'rearport',
))
#
# Cabling and connections
#

View File

@@ -1,11 +1,10 @@
import django_filters
from django.contrib.auth.models import User
from extras.filters import TagFilter
from extras.filtersets import LocalConfigContextFilterSet
from ipam.models import ASN
from ipam.models import ASN, VRF
from netbox.filtersets import (
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
)
from tenancy.filtersets import TenancyFilterSet
from tenancy.models import Tenant
@@ -39,8 +38,14 @@ __all__ = (
'InterfaceFilterSet',
'InterfaceTemplateFilterSet',
'InventoryItemFilterSet',
'InventoryItemRoleFilterSet',
'InventoryItemTemplateFilterSet',
'LocationFilterSet',
'ManufacturerFilterSet',
'ModuleBayFilterSet',
'ModuleBayTemplateFilterSet',
'ModuleFilterSet',
'ModuleTypeFilterSet',
'PathEndpointFilterSet',
'PlatformFilterSet',
'PowerConnectionFilterSet',
@@ -73,7 +78,6 @@ class RegionFilterSet(OrganizationalModelFilterSet):
to_field_name='slug',
label='Parent region (slug)',
)
tag = TagFilter()
class Meta:
model = Region
@@ -91,14 +95,13 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet):
to_field_name='slug',
label='Parent site group (slug)',
)
tag = TagFilter()
class Meta:
model = SiteGroup
fields = ['id', 'name', 'slug', 'description']
class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -131,19 +134,23 @@ class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
to_field_name='slug',
label='Group (slug)',
)
asn = django_filters.ModelMultipleChoiceFilter(
field_name='asns__asn',
queryset=ASN.objects.all(),
to_field_name='asn',
label='AS (ID)',
)
asn_id = django_filters.ModelMultipleChoiceFilter(
field_name='asns',
queryset=ASN.objects.all(),
label='AS (ID)',
)
tag = TagFilter()
class Meta:
model = Site
fields = [
'id', 'name', 'slug', 'facility', 'asn', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email',
]
fields = (
'id', 'name', 'slug', 'facility', 'latitude', 'longitude',
)
def search(self, queryset, name, value):
if not value.strip():
@@ -154,9 +161,6 @@ class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
Q(description__icontains=value) |
Q(physical_address__icontains=value) |
Q(shipping_address__icontains=value) |
Q(contact_name__icontains=value) |
Q(contact_phone__icontains=value) |
Q(contact_email__icontains=value) |
Q(comments__icontains=value)
)
try:
@@ -217,7 +221,6 @@ class LocationFilterSet(TenancyFilterSet, OrganizationalModelFilterSet):
to_field_name='slug',
label='Location (slug)',
)
tag = TagFilter()
class Meta:
model = Location
@@ -233,14 +236,13 @@ class LocationFilterSet(TenancyFilterSet, OrganizationalModelFilterSet):
class RackRoleFilterSet(OrganizationalModelFilterSet):
tag = TagFilter()
class Meta:
model = RackRole
fields = ['id', 'name', 'slug', 'color']
class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -317,7 +319,6 @@ class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
serial = django_filters.CharFilter(
lookup_expr='iexact'
)
tag = TagFilter()
class Meta:
model = Rack
@@ -338,7 +339,7 @@ class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
)
class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -381,7 +382,6 @@ class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
to_field_name='username',
label='User (name)',
)
tag = TagFilter()
class Meta:
model = RackReservation
@@ -399,14 +399,13 @@ class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class ManufacturerFilterSet(OrganizationalModelFilterSet):
tag = TagFilter()
class Meta:
model = Manufacturer
fields = ['id', 'name', 'slug', 'description']
class DeviceTypeFilterSet(PrimaryModelFilterSet):
class DeviceTypeFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -445,11 +444,14 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
method='_pass_through_ports',
label='Has pass-through ports',
)
module_bays = django_filters.BooleanFilter(
method='_module_bays',
label='Has module bays',
)
device_bays = django_filters.BooleanFilter(
method='_device_bays',
label='Has device bays',
)
tag = TagFilter()
class Meta:
model = DeviceType
@@ -488,10 +490,89 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
rearporttemplates__isnull=value
)
def _module_bays(self, queryset, name, value):
return queryset.exclude(modulebaytemplates__isnull=value)
def _device_bays(self, queryset, name, value):
return queryset.exclude(devicebaytemplates__isnull=value)
class ModuleTypeFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
console_ports = django_filters.BooleanFilter(
method='_console_ports',
label='Has console ports',
)
console_server_ports = django_filters.BooleanFilter(
method='_console_server_ports',
label='Has console server ports',
)
power_ports = django_filters.BooleanFilter(
method='_power_ports',
label='Has power ports',
)
power_outlets = django_filters.BooleanFilter(
method='_power_outlets',
label='Has power outlets',
)
interfaces = django_filters.BooleanFilter(
method='_interfaces',
label='Has interfaces',
)
pass_through_ports = django_filters.BooleanFilter(
method='_pass_through_ports',
label='Has pass-through ports',
)
class Meta:
model = ModuleType
fields = ['id', 'model', 'part_number']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(manufacturer__name__icontains=value) |
Q(model__icontains=value) |
Q(part_number__icontains=value) |
Q(comments__icontains=value)
)
def _console_ports(self, queryset, name, value):
return queryset.exclude(consoleporttemplates__isnull=value)
def _console_server_ports(self, queryset, name, value):
return queryset.exclude(consoleserverporttemplates__isnull=value)
def _power_ports(self, queryset, name, value):
return queryset.exclude(powerporttemplates__isnull=value)
def _power_outlets(self, queryset, name, value):
return queryset.exclude(poweroutlettemplates__isnull=value)
def _interfaces(self, queryset, name, value):
return queryset.exclude(interfacetemplates__isnull=value)
def _pass_through_ports(self, queryset, name, value):
return queryset.exclude(
frontporttemplates__isnull=value,
rearporttemplates__isnull=value
)
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
@@ -509,28 +590,36 @@ class DeviceTypeComponentFilterSet(django_filters.FilterSet):
return queryset.filter(name__icontains=value)
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class ModularDeviceTypeComponentFilterSet(DeviceTypeComponentFilterSet):
moduletype_id = django_filters.ModelMultipleChoiceFilter(
queryset=ModuleType.objects.all(),
field_name='module_type_id',
label='Module type (ID)',
)
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
class Meta:
model = ConsolePortTemplate
fields = ['id', 'name', 'type']
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
class Meta:
model = ConsoleServerPortTemplate
fields = ['id', 'name', 'type']
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
class Meta:
model = PowerPortTemplate
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
feed_leg = django_filters.MultipleChoiceFilter(
choices=PowerOutletFeedLegChoices,
null_value=None
@@ -541,7 +630,7 @@ class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeCompone
fields = ['id', 'name', 'type', 'feed_leg']
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=InterfaceTypeChoices,
null_value=None
@@ -552,7 +641,7 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
fields = ['id', 'name', 'type', 'mgmt_only']
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@@ -563,7 +652,7 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
fields = ['id', 'name', 'type', 'color']
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@@ -574,6 +663,13 @@ class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentF
fields = ['id', 'name', 'type', 'color', 'positions']
class ModuleBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class Meta:
model = ModuleBayTemplate
fields = ['id', 'name']
class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
class Meta:
@@ -581,8 +677,50 @@ class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
fields = ['id', 'name']
class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemTemplate.objects.all(),
label='Parent inventory item (ID)',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug',
queryset=InventoryItemRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
component_type = ContentTypeFilter()
component_id = MultiValueNumberFilter()
class Meta:
model = InventoryItemTemplate
fields = ['id', 'name', 'label', 'part_id']
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(part_id__icontains=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)
class DeviceRoleFilterSet(OrganizationalModelFilterSet):
tag = TagFilter()
class Meta:
model = DeviceRole
@@ -601,14 +739,13 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
to_field_name='slug',
label='Manufacturer (slug)',
)
tag = TagFilter()
class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -758,11 +895,14 @@ class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContex
method='_pass_through_ports',
label='Has pass-through ports',
)
module_bays = django_filters.BooleanFilter(
method='_module_bays',
label='Has module bays',
)
device_bays = django_filters.BooleanFilter(
method='_device_bays',
label='Has device bays',
)
tag = TagFilter()
class Meta:
model = Device
@@ -809,10 +949,48 @@ class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContex
rearports__isnull=value
)
def _module_bays(self, queryset, name, value):
return queryset.exclude(modulebays__isnull=value)
def _device_bays(self, queryset, name, value):
return queryset.exclude(devicebays__isnull=value)
class ModuleFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
field_name='module_type__manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='module_type__manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
device_id = django_filters.ModelMultipleChoiceFilter(
queryset=Device.objects.all(),
label='Device (ID)',
)
class Meta:
model = Module
fields = ['id', 'serial', 'asset_tag']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) |
Q(comments__icontains=value)
).distinct()
class DeviceComponentFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
@@ -887,7 +1065,6 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name',
label='Virtual Chassis',
)
tag = TagFilter()
def search(self, queryset, name, value):
if not value.strip():
@@ -919,7 +1096,7 @@ class PathEndpointFilterSet(django_filters.FilterSet):
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class ConsolePortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices,
null_value=None
@@ -930,7 +1107,7 @@ class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, Cabl
fields = ['id', 'name', 'label', 'description']
class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class ConsoleServerPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices,
null_value=None
@@ -941,7 +1118,7 @@ class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet
fields = ['id', 'name', 'label', 'description']
class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerPortTypeChoices,
null_value=None
@@ -952,7 +1129,7 @@ class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerOutletFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerOutletTypeChoices,
null_value=None
@@ -967,7 +1144,7 @@ class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, Cabl
fields = ['id', 'name', 'label', 'feed_leg', 'description']
class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class InterfaceFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1003,9 +1180,12 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
queryset=Interface.objects.all(),
label='LAG interface (ID)',
)
speed = MultiValueNumberFilter()
duplex = django_filters.MultipleChoiceFilter(
choices=InterfaceDuplexChoices
)
mac_address = MultiValueMACAddressFilter()
wwn = MultiValueWWNFilter()
tag = TagFilter()
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',
label='Assigned VLAN'
@@ -1024,6 +1204,17 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
rf_channel = django_filters.MultipleChoiceFilter(
choices=WirelessChannelChoices
)
vrf_id = django_filters.ModelMultipleChoiceFilter(
field_name='vrf',
queryset=VRF.objects.all(),
label='VRF',
)
vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd',
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF (RD)',
)
class Meta:
model = Interface
@@ -1080,7 +1271,7 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
}.get(value, queryset.none())
class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
class FrontPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@@ -1091,7 +1282,7 @@ class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
fields = ['id', 'name', 'label', 'type', 'color', 'description']
class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
class RearPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@@ -1102,14 +1293,21 @@ class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTe
fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description']
class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
class ModuleBayFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
class Meta:
model = ModuleBay
fields = ['id', 'name', 'label', 'description']
class DeviceBayFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
class Meta:
model = DeviceBay
fields = ['id', 'name', 'label', 'description']
class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
class InventoryItemFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1128,6 +1326,18 @@ class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
to_field_name='slug',
label='Manufacturer (slug)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug',
queryset=InventoryItemRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
component_type = ContentTypeFilter()
component_id = MultiValueNumberFilter()
serial = django_filters.CharFilter(
lookup_expr='iexact'
)
@@ -1149,7 +1359,14 @@ class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
return queryset.filter(qs_filter)
class VirtualChassisFilterSet(PrimaryModelFilterSet):
class InventoryItemRoleFilterSet(OrganizationalModelFilterSet):
class Meta:
model = InventoryItemRole
fields = ['id', 'name', 'slug', 'color']
class VirtualChassisFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1212,7 +1429,6 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
to_field_name='slug',
label='Tenant (slug)',
)
tag = TagFilter()
class Meta:
model = VirtualChassis
@@ -1229,7 +1445,7 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter).distinct()
class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1270,7 +1486,6 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
method='filter_device',
field_name='device__site__slug'
)
tag = TagFilter()
class Meta:
model = Cable
@@ -1289,7 +1504,7 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
return queryset
class PowerPanelFilterSet(PrimaryModelFilterSet):
class PowerPanelFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1336,7 +1551,6 @@ class PowerPanelFilterSet(PrimaryModelFilterSet):
lookup_expr='in',
label='Location (ID)',
)
tag = TagFilter()
class Meta:
model = PowerPanel
@@ -1351,7 +1565,7 @@ class PowerPanelFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter)
class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerFeedFilterSet(NetBoxModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -1406,7 +1620,6 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathE
choices=PowerFeedStatusChoices,
null_value=None
)
tag = TagFilter()
class Meta:
model = PowerFeed

View File

@@ -4,7 +4,7 @@ from dcim.models import *
from extras.forms import CustomFieldsMixin
from extras.models import Tag
from utilities.forms import DynamicModelMultipleChoiceField, form_from_model
from .object_create import ComponentForm
from .object_create import ComponentCreateForm
__all__ = (
'ConsolePortBulkCreateForm',
@@ -13,6 +13,7 @@ __all__ = (
# 'FrontPortBulkCreateForm',
'InterfaceBulkCreateForm',
'InventoryItemBulkCreateForm',
'ModuleBayBulkCreateForm',
'PowerOutletBulkCreateForm',
'PowerPortBulkCreateForm',
'RearPortBulkCreateForm',
@@ -23,7 +24,7 @@ __all__ = (
# Device components
#
class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentForm):
class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
@@ -71,12 +72,12 @@ class PowerOutletBulkCreateForm(
class InterfaceBulkCreateForm(
form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected']),
form_from_model(Interface, ['type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected']),
DeviceBulkAddComponentForm
):
model = Interface
field_order = (
'name_pattern', 'label_pattern', 'type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags',
'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags',
)
@@ -95,17 +96,22 @@ class RearPortBulkCreateForm(
field_order = ('name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags')
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
model = ModuleBay
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
model = DeviceBay
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
class InventoryItemBulkCreateForm(
form_from_model(InventoryItem, ['manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered']),
form_from_model(InventoryItem, ['role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered']),
DeviceBulkAddComponentForm
):
model = InventoryItem
field_order = (
'name_pattern', 'label_pattern', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
'tags',
'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description', 'tags',
)

View File

@@ -6,13 +6,12 @@ from timezone_field import TimeZoneFormField
from dcim.choices import *
from dcim.constants import *
from dcim.models import *
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.constants import BGP_ASN_MIN, BGP_ASN_MAX
from ipam.models import VLAN, ASN
from ipam.models import ASN, VLAN, VRF
from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant
from utilities.forms import (
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect,
DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, SelectSpeedWidget,
)
__all__ = (
@@ -31,8 +30,14 @@ __all__ = (
'InterfaceBulkEditForm',
'InterfaceTemplateBulkEditForm',
'InventoryItemBulkEditForm',
'InventoryItemRoleBulkEditForm',
'InventoryItemTemplateBulkEditForm',
'LocationBulkEditForm',
'ManufacturerBulkEditForm',
'ModuleBulkEditForm',
'ModuleBayBulkEditForm',
'ModuleBayTemplateBulkEditForm',
'ModuleTypeBulkEditForm',
'PlatformBulkEditForm',
'PowerFeedBulkEditForm',
'PowerOutletBulkEditForm',
@@ -52,11 +57,7 @@ __all__ = (
)
class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Region.objects.all(),
widget=forms.MultipleHiddenInput
)
class RegionBulkEditForm(NetBoxModelBulkEditForm):
parent = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False
@@ -66,15 +67,14 @@ class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['parent', 'description']
class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
widget=forms.MultipleHiddenInput
model = Region
fieldsets = (
(None, ('parent', 'description')),
)
nullable_fields = ('parent', 'description')
class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
parent = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False
@@ -84,15 +84,14 @@ class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['parent', 'description']
class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Site.objects.all(),
widget=forms.MultipleHiddenInput
model = SiteGroup
fieldsets = (
(None, ('parent', 'description')),
)
nullable_fields = ('parent', 'description')
class SiteBulkEditForm(NetBoxModelBulkEditForm):
status = forms.ChoiceField(
choices=add_blank_choice(SiteStatusChoices),
required=False,
@@ -111,12 +110,6 @@ class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
queryset=Tenant.objects.all(),
required=False
)
asn = forms.IntegerField(
min_value=BGP_ASN_MIN,
max_value=BGP_ASN_MAX,
required=False,
label='ASN'
)
asns = DynamicModelMultipleChoiceField(
queryset=ASN.objects.all(),
label=_('ASNs'),
@@ -132,17 +125,16 @@ class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
widget=StaticSelect()
)
class Meta:
nullable_fields = [
'region', 'group', 'tenant', 'asn', 'asns', 'description', 'time_zone',
]
class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Location.objects.all(),
widget=forms.MultipleHiddenInput
model = Site
fieldsets = (
(None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
)
nullable_fields = (
'region', 'group', 'tenant', 'asns', 'description', 'time_zone',
)
class LocationBulkEditForm(NetBoxModelBulkEditForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False
@@ -163,15 +155,14 @@ class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['parent', 'tenant', 'description']
class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=RackRole.objects.all(),
widget=forms.MultipleHiddenInput
model = Location
fieldsets = (
(None, ('site', 'parent', 'tenant', 'description')),
)
nullable_fields = ('parent', 'tenant', 'description')
class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
color = ColorField(
required=False
)
@@ -180,15 +171,14 @@ class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['color', 'description']
class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Rack.objects.all(),
widget=forms.MultipleHiddenInput
model = RackRole
fieldsets = (
(None, ('color', 'description')),
)
nullable_fields = ('color', 'description')
class RackBulkEditForm(NetBoxModelBulkEditForm):
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
@@ -278,17 +268,18 @@ class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
label='Comments'
)
class Meta:
nullable_fields = [
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
]
class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=RackReservation.objects.all(),
widget=forms.MultipleHiddenInput()
model = Rack
fieldsets = (
('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')),
('Location', ('region', 'site_group', 'site', 'location')),
('Hardware', ('type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit')),
)
nullable_fields = (
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
)
class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
user = forms.ModelChoiceField(
queryset=User.objects.order_by(
'username'
@@ -305,33 +296,33 @@ class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
required=False
)
class Meta:
nullable_fields = []
class ManufacturerBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
widget=forms.MultipleHiddenInput
model = RackReservation
fieldsets = (
(None, ('user', 'tenant', 'description')),
)
class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['description']
class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
widget=forms.MultipleHiddenInput()
model = Manufacturer
fieldsets = (
(None, ('description',)),
)
nullable_fields = ('description',)
class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
part_number = forms.CharField(
required=False
)
u_height = forms.IntegerField(
min_value=1,
required=False
@@ -347,15 +338,30 @@ class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
widget=StaticSelect()
)
class Meta:
nullable_fields = ['airflow']
class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
widget=forms.MultipleHiddenInput
model = DeviceType
fieldsets = (
(None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')),
)
nullable_fields = ('part_number', 'airflow')
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
part_number = forms.CharField(
required=False
)
model = ModuleType
fieldsets = (
(None, ('manufacturer', 'part_number')),
)
nullable_fields = ('part_number',)
class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
color = ColorField(
required=False
)
@@ -369,15 +375,14 @@ class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['color', 'description']
class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Platform.objects.all(),
widget=forms.MultipleHiddenInput
model = DeviceRole
fieldsets = (
(None, ('color', 'vm_role', 'description')),
)
nullable_fields = ('color', 'description')
class PlatformBulkEditForm(NetBoxModelBulkEditForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
@@ -392,15 +397,14 @@ class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
required=False
)
class Meta:
nullable_fields = ['manufacturer', 'napalm_driver', 'description']
class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
model = Platform
fieldsets = (
(None, ('manufacturer', 'napalm_driver', 'description')),
)
nullable_fields = ('manufacturer', 'napalm_driver', 'description')
class DeviceBulkEditForm(NetBoxModelBulkEditForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
@@ -451,17 +455,43 @@ class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
label='Serial Number'
)
class Meta:
nullable_fields = [
'tenant', 'platform', 'serial', 'airflow',
]
class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Cable.objects.all(),
widget=forms.MultipleHiddenInput
model = Device
fieldsets = (
('Device', ('device_role', 'status', 'tenant', 'platform')),
('Location', ('site', 'location')),
('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
)
nullable_fields = (
'tenant', 'platform', 'serial', 'airflow',
)
class ModuleBulkEditForm(NetBoxModelBulkEditForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
module_type = DynamicModelChoiceField(
queryset=ModuleType.objects.all(),
required=False,
query_params={
'manufacturer_id': '$manufacturer'
}
)
serial = forms.CharField(
max_length=50,
required=False,
label='Serial Number'
)
model = Module
fieldsets = (
(None, ('manufacturer', 'module_type', 'serial')),
)
nullable_fields = ('serial',)
class CableBulkEditForm(NetBoxModelBulkEditForm):
type = forms.ChoiceField(
choices=add_blank_choice(CableTypeChoices),
required=False,
@@ -496,10 +526,14 @@ class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
widget=StaticSelect()
)
class Meta:
nullable_fields = [
'type', 'status', 'tenant', 'label', 'color', 'length',
]
model = Cable
fieldsets = (
(None, ('type', 'status', 'tenant', 'label')),
('Attributes', ('color', 'length', 'length_unit')),
)
nullable_fields = (
'type', 'status', 'tenant', 'label', 'color', 'length',
)
def clean(self):
super().clean()
@@ -513,25 +547,20 @@ class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
})
class VirtualChassisBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualChassis.objects.all(),
widget=forms.MultipleHiddenInput()
)
class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
domain = forms.CharField(
max_length=30,
required=False
)
class Meta:
nullable_fields = ['domain']
class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
widget=forms.MultipleHiddenInput
model = VirtualChassis
fieldsets = (
(None, ('domain',)),
)
nullable_fields = ('domain',)
class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
@@ -562,15 +591,14 @@ class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
}
)
class Meta:
nullable_fields = ['location']
class PowerFeedBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
widget=forms.MultipleHiddenInput
model = PowerPanel
fieldsets = (
(None, ('region', 'site_group', 'site', 'location')),
)
nullable_fields = ('location',)
class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
power_panel = DynamicModelChoiceField(
queryset=PowerPanel.objects.all(),
required=False
@@ -621,10 +649,12 @@ class PowerFeedBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
label='Comments'
)
class Meta:
nullable_fields = [
'location', 'comments',
]
model = PowerFeed
fieldsets = (
(None, ('power_panel', 'rack', 'status', 'type', 'mark_connected')),
('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
)
nullable_fields = ('location', 'comments')
#
@@ -646,8 +676,7 @@ class ConsolePortTemplateBulkEditForm(BulkEditForm):
widget=StaticSelect()
)
class Meta:
nullable_fields = ('label', 'type', 'description')
nullable_fields = ('label', 'type', 'description')
class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
@@ -668,8 +697,7 @@ class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('label', 'type', 'description')
nullable_fields = ('label', 'type', 'description')
class PowerPortTemplateBulkEditForm(BulkEditForm):
@@ -700,8 +728,7 @@ class PowerPortTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
class PowerOutletTemplateBulkEditForm(BulkEditForm):
@@ -737,8 +764,7 @@ class PowerOutletTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -775,8 +801,7 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('label', 'description')
nullable_fields = ('label', 'description')
class FrontPortTemplateBulkEditForm(BulkEditForm):
@@ -800,8 +825,7 @@ class FrontPortTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('description',)
nullable_fields = ('description',)
class RearPortTemplateBulkEditForm(BulkEditForm):
@@ -825,8 +849,23 @@ class RearPortTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('description',)
nullable_fields = ('description',)
class ModuleBayTemplateBulkEditForm(BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ModuleBayTemplate.objects.all(),
widget=forms.MultipleHiddenInput()
)
label = forms.CharField(
max_length=64,
required=False
)
description = forms.CharField(
required=False
)
nullable_fields = ('label', 'position', 'description')
class DeviceBayTemplateBulkEditForm(BulkEditForm):
@@ -842,8 +881,31 @@ class DeviceBayTemplateBulkEditForm(BulkEditForm):
required=False
)
class Meta:
nullable_fields = ('label', 'description')
nullable_fields = ('label', 'description')
class InventoryItemTemplateBulkEditForm(BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=InventoryItemTemplate.objects.all(),
widget=forms.MultipleHiddenInput()
)
label = forms.CharField(
max_length=64,
required=False
)
description = forms.CharField(
required=False
)
role = DynamicModelChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
#
@@ -852,67 +914,57 @@ class DeviceBayTemplateBulkEditForm(BulkEditForm):
class ConsolePortBulkEditForm(
form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=ConsolePort.objects.all(),
widget=forms.MultipleHiddenInput()
)
mark_connected = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect
)
class Meta:
nullable_fields = ['label', 'description']
model = ConsolePort
fieldsets = (
(None, ('type', 'label', 'speed', 'description', 'mark_connected')),
)
nullable_fields = ('label', 'description')
class ConsoleServerPortBulkEditForm(
form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=ConsoleServerPort.objects.all(),
widget=forms.MultipleHiddenInput()
)
mark_connected = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect
)
class Meta:
nullable_fields = ['label', 'description']
model = ConsoleServerPort
fieldsets = (
(None, ('type', 'label', 'speed', 'description', 'mark_connected')),
)
nullable_fields = ('label', 'description')
class PowerPortBulkEditForm(
form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=PowerPort.objects.all(),
widget=forms.MultipleHiddenInput()
)
mark_connected = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect
)
class Meta:
nullable_fields = ['label', 'description']
model = PowerPort
fieldsets = (
(None, ('type', 'label', 'description', 'mark_connected')),
('Power', ('maximum_draw', 'allocated_draw')),
)
nullable_fields = ('label', 'description')
class PowerOutletBulkEditForm(
form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=PowerOutlet.objects.all(),
widget=forms.MultipleHiddenInput()
)
device = forms.ModelChoiceField(
queryset=Device.objects.all(),
required=False,
@@ -924,8 +976,12 @@ class PowerOutletBulkEditForm(
widget=BulkEditNullBooleanSelect
)
class Meta:
nullable_fields = ['label', 'type', 'feed_leg', 'power_port', 'description']
model = PowerOutlet
fieldsets = (
(None, ('type', 'label', 'description', 'mark_connected')),
('Power', ('feed_leg', 'power_port')),
)
nullable_fields = ('label', 'type', 'feed_leg', 'power_port', 'description')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -941,16 +997,12 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm(
form_from_model(Interface, [
'label', 'type', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power',
]),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=Interface.objects.all(),
widget=forms.MultipleHiddenInput()
)
device = forms.ModelChoiceField(
queryset=Device.objects.all(),
required=False,
@@ -974,7 +1026,13 @@ class InterfaceBulkEditForm(
required=False,
query_params={
'type': 'lag',
}
},
label='LAG'
)
speed = forms.IntegerField(
required=False,
widget=SelectSpeedWidget(),
label='Speed'
)
mgmt_only = forms.NullBooleanField(
required=False,
@@ -993,12 +1051,25 @@ class InterfaceBulkEditForm(
queryset=VLAN.objects.all(),
required=False
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
)
class Meta:
nullable_fields = [
'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans',
]
model = Interface
fieldsets = (
(None, ('type', 'label', 'speed', 'duplex', 'description')),
('Addressing', ('vrf', 'mac_address', 'wwn')),
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
('Related Interfaces', ('parent', 'bridge', 'lag')),
('802.1Q Switching', ('mode', 'untagged_vlan', 'tagged_vlans')),
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')),
)
nullable_fields = (
'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', 'mode',
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1043,8 +1114,14 @@ class InterfaceBulkEditForm(
def clean(self):
super().clean()
if not self.cleaned_data['mode']:
if self.cleaned_data['untagged_vlan']:
raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
elif self.cleaned_data['tagged_vlans']:
raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
# Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned."
})
@@ -1056,59 +1133,83 @@ class InterfaceBulkEditForm(
class FrontPortBulkEditForm(
form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=FrontPort.objects.all(),
widget=forms.MultipleHiddenInput()
model = FrontPort
fieldsets = (
(None, ('type', 'label', 'color', 'description', 'mark_connected')),
)
class Meta:
nullable_fields = ['label', 'description']
nullable_fields = ('label', 'description')
class RearPortBulkEditForm(
form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=RearPort.objects.all(),
widget=forms.MultipleHiddenInput()
model = RearPort
fieldsets = (
(None, ('type', 'label', 'color', 'description', 'mark_connected')),
)
nullable_fields = ('label', 'description')
class Meta:
nullable_fields = ['label', 'description']
class ModuleBayBulkEditForm(
form_from_model(ModuleBay, ['label', 'position', 'description']),
NetBoxModelBulkEditForm
):
model = ModuleBay
fieldsets = (
(None, ('label', 'position', 'description')),
)
nullable_fields = ('label', 'position', 'description')
class DeviceBayBulkEditForm(
form_from_model(DeviceBay, ['label', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=DeviceBay.objects.all(),
widget=forms.MultipleHiddenInput()
model = DeviceBay
fieldsets = (
(None, ('label', 'description')),
)
class Meta:
nullable_fields = ['label', 'description']
nullable_fields = ('label', 'description')
class InventoryItemBulkEditForm(
form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'description']),
AddRemoveTagsForm,
CustomFieldModelBulkEditForm
form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
NetBoxModelBulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=InventoryItem.objects.all(),
widget=forms.MultipleHiddenInput()
role = DynamicModelChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
class Meta:
nullable_fields = ['label', 'manufacturer', 'part_id', 'description']
model = InventoryItem
fieldsets = (
(None, ('label', 'role', 'manufacturer', 'part_id', 'description')),
)
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
#
# Device component roles
#
class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
color = ColorField(
required=False
)
description = forms.CharField(
max_length=200,
required=False
)
model = InventoryItemRole
fieldsets = (
(None, ('color', 'description')),
)
nullable_fields = ('color', 'description')

View File

@@ -7,7 +7,8 @@ from django.utils.safestring import mark_safe
from dcim.choices import *
from dcim.constants import *
from dcim.models import *
from extras.forms import CustomFieldModelCSVForm
from ipam.models import VRF
from netbox.forms import NetBoxModelCSVForm
from tenancy.models import Tenant
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField
from virtualization.models import Cluster
@@ -24,8 +25,11 @@ __all__ = (
'FrontPortCSVForm',
'InterfaceCSVForm',
'InventoryItemCSVForm',
'InventoryItemRoleCSVForm',
'LocationCSVForm',
'ManufacturerCSVForm',
'ModuleCSVForm',
'ModuleBayCSVForm',
'PlatformCSVForm',
'PowerFeedCSVForm',
'PowerOutletCSVForm',
@@ -42,7 +46,7 @@ __all__ = (
)
class RegionCSVForm(CustomFieldModelCSVForm):
class RegionCSVForm(NetBoxModelCSVForm):
parent = CSVModelChoiceField(
queryset=Region.objects.all(),
required=False,
@@ -55,7 +59,7 @@ class RegionCSVForm(CustomFieldModelCSVForm):
fields = ('name', 'slug', 'parent', 'description')
class SiteGroupCSVForm(CustomFieldModelCSVForm):
class SiteGroupCSVForm(NetBoxModelCSVForm):
parent = CSVModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
@@ -68,7 +72,7 @@ class SiteGroupCSVForm(CustomFieldModelCSVForm):
fields = ('name', 'slug', 'parent', 'description')
class SiteCSVForm(CustomFieldModelCSVForm):
class SiteCSVForm(NetBoxModelCSVForm):
status = CSVChoiceField(
choices=SiteStatusChoices,
help_text='Operational status'
@@ -96,8 +100,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
model = Site
fields = (
'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email', 'comments',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments',
)
help_texts = {
'time_zone': mark_safe(
@@ -106,7 +109,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
}
class LocationCSVForm(CustomFieldModelCSVForm):
class LocationCSVForm(NetBoxModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
@@ -133,7 +136,7 @@ class LocationCSVForm(CustomFieldModelCSVForm):
fields = ('site', 'parent', 'name', 'slug', 'tenant', 'description')
class RackRoleCSVForm(CustomFieldModelCSVForm):
class RackRoleCSVForm(NetBoxModelCSVForm):
slug = SlugField()
class Meta:
@@ -144,7 +147,7 @@ class RackRoleCSVForm(CustomFieldModelCSVForm):
}
class RackCSVForm(CustomFieldModelCSVForm):
class RackCSVForm(NetBoxModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name'
@@ -202,7 +205,7 @@ class RackCSVForm(CustomFieldModelCSVForm):
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
class RackReservationCSVForm(CustomFieldModelCSVForm):
class RackReservationCSVForm(NetBoxModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
@@ -252,14 +255,14 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
class ManufacturerCSVForm(CustomFieldModelCSVForm):
class ManufacturerCSVForm(NetBoxModelCSVForm):
class Meta:
model = Manufacturer
fields = ('name', 'slug', 'description')
class DeviceRoleCSVForm(CustomFieldModelCSVForm):
class DeviceRoleCSVForm(NetBoxModelCSVForm):
slug = SlugField()
class Meta:
@@ -270,7 +273,7 @@ class DeviceRoleCSVForm(CustomFieldModelCSVForm):
}
class PlatformCSVForm(CustomFieldModelCSVForm):
class PlatformCSVForm(NetBoxModelCSVForm):
slug = SlugField()
manufacturer = CSVModelChoiceField(
queryset=Manufacturer.objects.all(),
@@ -284,7 +287,7 @@ class PlatformCSVForm(CustomFieldModelCSVForm):
fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description')
class BaseDeviceCSVForm(CustomFieldModelCSVForm):
class BaseDeviceCSVForm(NetBoxModelCSVForm):
device_role = CSVModelChoiceField(
queryset=DeviceRole.objects.all(),
to_field_name='name',
@@ -400,6 +403,35 @@ class DeviceCSVForm(BaseDeviceCSVForm):
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
class ModuleCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
)
module_bay = CSVModelChoiceField(
queryset=ModuleBay.objects.all(),
to_field_name='name'
)
module_type = CSVModelChoiceField(
queryset=ModuleType.objects.all(),
to_field_name='model'
)
class Meta:
model = Module
fields = (
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments',
)
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit module_bay queryset by assigned device
params = {f"device__{self.fields['device'].to_field_name}": data.get('device')}
self.fields['module_bay'].queryset = self.fields['module_bay'].queryset.filter(**params)
class ChildDeviceCSVForm(BaseDeviceCSVForm):
parent = CSVModelChoiceField(
queryset=Device.objects.all(),
@@ -446,7 +478,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
# Device components
#
class ConsolePortCSVForm(CustomFieldModelCSVForm):
class ConsolePortCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -469,7 +501,7 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm):
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -492,7 +524,7 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
class PowerPortCSVForm(CustomFieldModelCSVForm):
class PowerPortCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -510,7 +542,7 @@ class PowerPortCSVForm(CustomFieldModelCSVForm):
)
class PowerOutletCSVForm(CustomFieldModelCSVForm):
class PowerOutletCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -559,7 +591,7 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm):
self.fields['power_port'].queryset = PowerPort.objects.none()
class InterfaceCSVForm(CustomFieldModelCSVForm):
class InterfaceCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -586,11 +618,21 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
choices=InterfaceTypeChoices,
help_text='Physical medium'
)
duplex = CSVChoiceField(
choices=InterfaceDuplexChoices,
required=False
)
mode = CSVChoiceField(
choices=InterfaceModeChoices,
required=False,
help_text='IEEE 802.1Q operational mode (for L2 interfaces)'
)
vrf = CSVModelChoiceField(
queryset=VRF.objects.all(),
required=False,
to_field_name='rd',
help_text='Assigned VRF'
)
rf_role = CSVChoiceField(
choices=WirelessRoleChoices,
required=False,
@@ -600,8 +642,8 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
class Meta:
model = Interface
fields = (
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address',
'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled', 'mark_connected', 'mac_address',
'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power',
)
@@ -613,7 +655,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
return self.cleaned_data['enabled']
class FrontPortCSVForm(CustomFieldModelCSVForm):
class FrontPortCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -661,7 +703,7 @@ class FrontPortCSVForm(CustomFieldModelCSVForm):
self.fields['rear_port'].queryset = RearPort.objects.none()
class RearPortCSVForm(CustomFieldModelCSVForm):
class RearPortCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -679,7 +721,18 @@ class RearPortCSVForm(CustomFieldModelCSVForm):
}
class DeviceBayCSVForm(CustomFieldModelCSVForm):
class ModuleBayCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
)
class Meta:
model = ModuleBay
fields = ('device', 'name', 'label', 'position', 'description')
class DeviceBayCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
@@ -725,11 +778,16 @@ class DeviceBayCSVForm(CustomFieldModelCSVForm):
self.fields['installed_device'].queryset = Interface.objects.none()
class InventoryItemCSVForm(CustomFieldModelCSVForm):
class InventoryItemCSVForm(NetBoxModelCSVForm):
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name'
)
role = CSVModelChoiceField(
queryset=InventoryItemRole.objects.all(),
to_field_name='name',
required=False
)
manufacturer = CSVModelChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='name',
@@ -745,7 +803,8 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm):
class Meta:
model = InventoryItem
fields = (
'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
'device', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description',
)
def __init__(self, *args, **kwargs):
@@ -764,7 +823,26 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm):
self.fields['parent'].queryset = InventoryItem.objects.none()
class CableCSVForm(CustomFieldModelCSVForm):
#
# Device component roles
#
class InventoryItemRoleCSVForm(NetBoxModelCSVForm):
slug = SlugField()
class Meta:
model = InventoryItemRole
fields = ('name', 'slug', 'color', 'description')
help_texts = {
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
}
#
# Cables
#
class CableCSVForm(NetBoxModelCSVForm):
# Termination A
side_a_device = CSVModelChoiceField(
queryset=Device.objects.all(),
@@ -865,7 +943,11 @@ class CableCSVForm(CustomFieldModelCSVForm):
return length_unit if length_unit is not None else ''
class VirtualChassisCSVForm(CustomFieldModelCSVForm):
#
# Virtual chassis
#
class VirtualChassisCSVForm(NetBoxModelCSVForm):
master = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
@@ -878,7 +960,11 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm):
fields = ('name', 'domain', 'master')
class PowerPanelCSVForm(CustomFieldModelCSVForm):
#
# Power
#
class PowerPanelCSVForm(NetBoxModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
@@ -904,7 +990,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
class PowerFeedCSVForm(CustomFieldModelCSVForm):
class PowerFeedCSVForm(NetBoxModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',

View File

@@ -1,7 +1,7 @@
from circuits.models import Circuit, CircuitTermination, Provider
from dcim.models import *
from extras.forms import CustomFieldModelForm
from extras.models import Tag
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
@@ -18,7 +18,7 @@ __all__ = (
)
class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
"""
Base form for connecting a Cable to a Device component
"""
@@ -27,7 +27,7 @@ class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
label='Region',
required=False
)
termination_b_site_group = DynamicModelChoiceField(
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False
@@ -38,7 +38,7 @@ class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_site_group',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_location = DynamicModelChoiceField(
@@ -78,9 +78,9 @@ class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
class Meta:
model = Cable
fields = [
'termination_b_region', 'termination_b_site', 'termination_b_rack', 'termination_b_device',
'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit',
'tags',
'termination_b_region', 'termination_b_sitegroup', 'termination_b_site', 'termination_b_rack',
'termination_b_device', 'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
'length', 'length_unit', 'tags',
]
widgets = {
'status': StaticSelect,
@@ -171,7 +171,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
)
class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
termination_b_provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
label='Provider',
@@ -182,7 +182,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
label='Region',
required=False
)
termination_b_site_group = DynamicModelChoiceField(
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False
@@ -193,7 +193,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_site_group',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_circuit = DynamicModelChoiceField(
@@ -219,9 +219,9 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
class Meta(ConnectCableToDeviceForm.Meta):
fields = [
'termination_b_provider', 'termination_b_region', 'termination_b_site', 'termination_b_circuit',
'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit',
'tags',
'termination_b_provider', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
'termination_b_circuit', 'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
'length', 'length_unit', 'tags',
]
def clean_termination_b_id(self):
@@ -229,13 +229,13 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
class ConnectCableToPowerFeedForm(TenancyForm, CustomFieldModelForm):
class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
termination_b_region = DynamicModelChoiceField(
queryset=Region.objects.all(),
label='Region',
required=False
)
termination_b_site_group = DynamicModelChoiceField(
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False
@@ -246,7 +246,7 @@ class ConnectCableToPowerFeedForm(TenancyForm, CustomFieldModelForm):
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_site_group',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_location = DynamicModelChoiceField(
@@ -281,8 +281,9 @@ class ConnectCableToPowerFeedForm(TenancyForm, CustomFieldModelForm):
class Meta(ConnectCableToDeviceForm.Meta):
fields = [
'termination_b_location', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'tenant_group',
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
'termination_b_region', 'termination_b_sitegroup', 'termination_b_site', 'termination_b_location',
'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label',
'color', 'length', 'length_unit', 'tags',
]
def clean_termination_b_id(self):

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