nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (697) hide show
  1. nautobot/apps/forms.py +4 -0
  2. nautobot/apps/models.py +10 -1
  3. nautobot/circuits/__init__.py +0 -1
  4. nautobot/circuits/apps.py +1 -0
  5. nautobot/circuits/factory.py +15 -3
  6. nautobot/circuits/filters.py +13 -0
  7. nautobot/circuits/forms.py +13 -0
  8. nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
  9. nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
  10. nautobot/circuits/models.py +16 -3
  11. nautobot/circuits/tables.py +16 -2
  12. nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
  13. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
  14. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
  15. nautobot/circuits/tests/test_api.py +7 -5
  16. nautobot/circuits/tests/test_filters.py +12 -5
  17. nautobot/circuits/tests/test_models.py +33 -2
  18. nautobot/circuits/views.py +2 -3
  19. nautobot/cloud/__init__.py +0 -0
  20. nautobot/cloud/api/__init__.py +0 -0
  21. nautobot/cloud/api/serializers.py +54 -0
  22. nautobot/cloud/api/urls.py +16 -0
  23. nautobot/cloud/api/views.py +48 -0
  24. nautobot/cloud/apps.py +13 -0
  25. nautobot/cloud/factory.py +111 -0
  26. nautobot/cloud/filters.py +184 -0
  27. nautobot/cloud/forms.py +333 -0
  28. nautobot/cloud/homepage.py +43 -0
  29. nautobot/cloud/migrations/0001_initial.py +304 -0
  30. nautobot/cloud/migrations/__init__.py +0 -0
  31. nautobot/cloud/models.py +247 -0
  32. nautobot/cloud/navigation.py +85 -0
  33. nautobot/cloud/tables.py +173 -0
  34. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
  35. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
  36. nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
  37. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
  38. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -0
  39. nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
  40. nautobot/cloud/tests/__init__.py +0 -0
  41. nautobot/cloud/tests/test_api.py +248 -0
  42. nautobot/cloud/tests/test_filters.py +113 -0
  43. nautobot/cloud/tests/test_models.py +43 -0
  44. nautobot/cloud/tests/test_views.py +153 -0
  45. nautobot/cloud/urls.py +14 -0
  46. nautobot/cloud/views.py +181 -0
  47. nautobot/core/__init__.py +0 -3
  48. nautobot/core/api/metadata.py +1 -0
  49. nautobot/core/api/parsers.py +7 -1
  50. nautobot/core/api/urls.py +1 -0
  51. nautobot/core/api/utils.py +1 -0
  52. nautobot/core/api/views.py +4 -0
  53. nautobot/core/apps/__init__.py +6 -3
  54. nautobot/core/constants.py +8 -0
  55. nautobot/core/factory.py +32 -1
  56. nautobot/core/filters.py +96 -28
  57. nautobot/core/forms/fields.py +10 -4
  58. nautobot/core/forms/forms.py +1 -1
  59. nautobot/core/forms/widgets.py +18 -1
  60. nautobot/core/graphql/generators.py +2 -2
  61. nautobot/core/graphql/schema.py +34 -4
  62. nautobot/core/jobs/__init__.py +17 -6
  63. nautobot/core/jobs/cleanup.py +100 -0
  64. nautobot/core/jobs/groups.py +38 -0
  65. nautobot/core/management/commands/generate_test_data.py +116 -3
  66. nautobot/core/models/__init__.py +34 -9
  67. nautobot/core/models/generics.py +19 -3
  68. nautobot/core/models/name_color_content_types.py +7 -28
  69. nautobot/core/models/querysets.py +4 -3
  70. nautobot/core/models/tree_queries.py +1 -1
  71. nautobot/core/models/utils.py +21 -5
  72. nautobot/core/settings.py +4 -30
  73. nautobot/core/settings.yaml +34 -27
  74. nautobot/core/settings_funcs.py +103 -0
  75. nautobot/core/tables.py +127 -56
  76. nautobot/core/templates/admin/search_form.html +1 -1
  77. nautobot/core/templates/buttons/add.html +11 -3
  78. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  80. nautobot/core/templates/buttons/export.html +101 -53
  81. nautobot/core/templates/buttons/job_import.html +11 -3
  82. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  83. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  84. nautobot/core/templates/generic/object_changelog.html +0 -9
  85. nautobot/core/templates/generic/object_list.html +156 -17
  86. nautobot/core/templates/generic/object_retrieve.html +80 -16
  87. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  88. nautobot/core/templates/inc/javascript.html +2 -0
  89. nautobot/core/templates/inc/media.html +2 -2
  90. nautobot/core/templates/inc/nav_menu.html +1 -0
  91. nautobot/core/templates/inc/paginator.html +7 -7
  92. nautobot/core/templates/inc/search_panel.html +2 -2
  93. nautobot/core/templates/inc/table.html +2 -2
  94. nautobot/core/templates/nautobot_config.py.j2 +13 -23
  95. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  96. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  97. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  98. nautobot/core/templates/utilities/theme_preview.html +25 -8
  99. nautobot/core/templates/utilities/worker_status.html +152 -0
  100. nautobot/core/templatetags/buttons.py +335 -38
  101. nautobot/core/templatetags/form_helpers.py +1 -1
  102. nautobot/core/templatetags/helpers.py +181 -11
  103. nautobot/core/testing/api.py +5 -4
  104. nautobot/core/testing/filters.py +51 -13
  105. nautobot/core/testing/mixins.py +46 -0
  106. nautobot/core/testing/models.py +22 -0
  107. nautobot/core/testing/schema.py +4 -8
  108. nautobot/core/testing/views.py +31 -14
  109. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  110. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  111. nautobot/core/tests/integration/test_swagger.py +1 -1
  112. nautobot/core/tests/nautobot_config.py +0 -1
  113. nautobot/core/tests/runner.py +2 -2
  114. nautobot/core/tests/test_api.py +1 -0
  115. nautobot/core/tests/test_authentication.py +7 -2
  116. nautobot/core/tests/test_filters.py +11 -9
  117. nautobot/core/tests/test_forms.py +9 -0
  118. nautobot/core/tests/test_graphql.py +27 -16
  119. nautobot/core/tests/test_jobs.py +123 -74
  120. nautobot/core/tests/test_tables.py +3 -1
  121. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  122. nautobot/core/tests/test_utils.py +31 -20
  123. nautobot/core/tests/test_views.py +6 -6
  124. nautobot/core/urls.py +8 -3
  125. nautobot/core/utils/deprecation.py +29 -0
  126. nautobot/core/utils/filtering.py +12 -9
  127. nautobot/core/utils/lookup.py +37 -2
  128. nautobot/core/utils/requests.py +4 -1
  129. nautobot/core/views/__init__.py +137 -24
  130. nautobot/core/views/generic.py +118 -66
  131. nautobot/core/views/mixins.py +104 -35
  132. nautobot/core/views/paginator.py +9 -3
  133. nautobot/core/views/renderers.py +121 -56
  134. nautobot/core/views/utils.py +79 -1
  135. nautobot/dcim/__init__.py +0 -1
  136. nautobot/dcim/api/serializers.py +180 -44
  137. nautobot/dcim/api/urls.py +7 -3
  138. nautobot/dcim/api/views.py +53 -7
  139. nautobot/dcim/apps.py +3 -0
  140. nautobot/dcim/choices.py +25 -0
  141. nautobot/dcim/constants.py +7 -0
  142. nautobot/dcim/factory.py +249 -18
  143. nautobot/dcim/filters/__init__.py +369 -193
  144. nautobot/dcim/filters/mixins.py +274 -1
  145. nautobot/dcim/forms.py +817 -109
  146. nautobot/dcim/graphql/types.py +2 -2
  147. nautobot/dcim/homepage.py +1 -1
  148. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  149. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  150. nautobot/dcim/migrations/0061_module_models.py +861 -0
  151. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  152. nautobot/dcim/models/__init__.py +8 -0
  153. nautobot/dcim/models/cables.py +15 -0
  154. nautobot/dcim/models/device_component_templates.py +207 -53
  155. nautobot/dcim/models/device_components.py +275 -106
  156. nautobot/dcim/models/devices.py +466 -13
  157. nautobot/dcim/navigation.py +47 -0
  158. nautobot/dcim/signals.py +3 -3
  159. nautobot/dcim/tables/__init__.py +35 -23
  160. nautobot/dcim/tables/devices.py +231 -59
  161. nautobot/dcim/tables/devicetypes.py +65 -9
  162. nautobot/dcim/tables/racks.py +5 -1
  163. nautobot/dcim/tables/template_code.py +46 -26
  164. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  165. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  166. nautobot/dcim/templates/dcim/device/base.html +15 -7
  167. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  168. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  169. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  170. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  171. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  172. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  173. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  174. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  175. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  176. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  177. nautobot/dcim/templates/dcim/device.html +45 -1
  178. nautobot/dcim/templates/dcim/device_component.html +13 -5
  179. nautobot/dcim/templates/dcim/device_list.html +2 -1
  180. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
  181. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  182. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  183. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  184. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  185. nautobot/dcim/templates/dcim/interface.html +17 -2
  186. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  187. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  188. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  189. nautobot/dcim/templates/dcim/module/base.html +97 -0
  190. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  191. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  192. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  193. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  194. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  195. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  196. nautobot/dcim/templates/dcim/module_list.html +20 -0
  197. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  198. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  199. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  200. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  201. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  202. nautobot/dcim/templates/dcim/module_update.html +71 -0
  203. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  204. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  205. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  206. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  207. nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
  208. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  209. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  210. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  211. nautobot/dcim/tests/test_api.py +691 -208
  212. nautobot/dcim/tests/test_filters.py +836 -217
  213. nautobot/dcim/tests/test_models.py +1072 -39
  214. nautobot/dcim/tests/test_views.py +1488 -358
  215. nautobot/dcim/urls.py +17 -2
  216. nautobot/dcim/utils.py +2 -3
  217. nautobot/dcim/views.py +1107 -120
  218. nautobot/extras/__init__.py +0 -1
  219. nautobot/extras/api/serializers.py +115 -3
  220. nautobot/extras/api/urls.py +12 -0
  221. nautobot/extras/api/views.py +125 -7
  222. nautobot/extras/apps.py +2 -2
  223. nautobot/extras/choices.py +43 -0
  224. nautobot/extras/context_managers.py +13 -8
  225. nautobot/extras/datasources/git.py +2 -0
  226. nautobot/extras/factory.py +422 -9
  227. nautobot/extras/filters/__init__.py +174 -3
  228. nautobot/extras/filters/mixins.py +46 -43
  229. nautobot/extras/forms/base.py +17 -4
  230. nautobot/extras/forms/forms.py +227 -8
  231. nautobot/extras/forms/mixins.py +93 -0
  232. nautobot/extras/graphql/types.py +23 -10
  233. nautobot/extras/homepage.py +16 -13
  234. nautobot/extras/jobs.py +2 -2
  235. nautobot/extras/management/__init__.py +1 -0
  236. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  237. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  238. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  239. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  240. nautobot/extras/migrations/0111_metadata.py +162 -0
  241. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  242. nautobot/extras/migrations/0113_saved_views.py +77 -0
  243. nautobot/extras/models/__init__.py +15 -1
  244. nautobot/extras/models/change_logging.py +3 -3
  245. nautobot/extras/models/contacts.py +4 -0
  246. nautobot/extras/models/customfields.py +18 -3
  247. nautobot/extras/models/groups.py +389 -225
  248. nautobot/extras/models/jobs.py +4 -84
  249. nautobot/extras/models/metadata.py +441 -0
  250. nautobot/extras/models/mixins.py +72 -62
  251. nautobot/extras/models/models.py +116 -9
  252. nautobot/extras/models/relationships.py +9 -2
  253. nautobot/extras/models/tags.py +13 -2
  254. nautobot/extras/navigation.py +57 -0
  255. nautobot/extras/plugins/__init__.py +3 -1
  256. nautobot/extras/querysets.py +30 -66
  257. nautobot/extras/signals.py +96 -114
  258. nautobot/extras/tables.py +171 -47
  259. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  260. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  261. nautobot/extras/templates/extras/job.html +1 -1
  262. nautobot/extras/templates/extras/job_detail.html +0 -11
  263. nautobot/extras/templates/extras/jobresult.html +61 -74
  264. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  265. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  266. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  267. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  268. nautobot/extras/templates/extras/plugins_list.html +1 -3
  269. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  270. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  271. nautobot/extras/tests/integration/test_customfields.py +1 -0
  272. nautobot/extras/tests/test_api.py +501 -22
  273. nautobot/extras/tests/test_changelog.py +20 -9
  274. nautobot/extras/tests/test_context_managers.py +22 -15
  275. nautobot/extras/tests/test_datasources.py +13 -1
  276. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  277. nautobot/extras/tests/test_filters.py +211 -12
  278. nautobot/extras/tests/test_jobs.py +4 -4
  279. nautobot/extras/tests/test_models.py +499 -4
  280. nautobot/extras/tests/test_relationships.py +1 -0
  281. nautobot/extras/tests/test_views.py +565 -28
  282. nautobot/extras/tests/test_webhooks.py +1 -1
  283. nautobot/extras/urls.py +5 -0
  284. nautobot/extras/utils.py +56 -45
  285. nautobot/extras/views.py +585 -96
  286. nautobot/ipam/__init__.py +0 -1
  287. nautobot/ipam/apps.py +1 -0
  288. nautobot/ipam/factory.py +17 -19
  289. nautobot/ipam/filters.py +14 -1
  290. nautobot/ipam/forms.py +9 -5
  291. nautobot/ipam/graphql/types.py +2 -2
  292. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  293. nautobot/ipam/models.py +23 -9
  294. nautobot/ipam/querysets.py +1 -1
  295. nautobot/ipam/signals.py +4 -2
  296. nautobot/ipam/tables.py +1 -0
  297. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  298. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  299. nautobot/ipam/templates/ipam/prefix.html +1 -0
  300. nautobot/ipam/tests/test_api.py +37 -18
  301. nautobot/ipam/tests/test_filters.py +26 -2
  302. nautobot/ipam/tests/test_models.py +8 -3
  303. nautobot/ipam/tests/test_querysets.py +1 -1
  304. nautobot/ipam/tests/test_views.py +3 -2
  305. nautobot/ipam/urls.py +2 -2
  306. nautobot/ipam/views.py +25 -28
  307. nautobot/project-static/css/base.css +20 -1
  308. nautobot/project-static/css/dark.css +11 -0
  309. nautobot/project-static/docs/404.html +884 -80
  310. nautobot/project-static/docs/apps/index.html +884 -80
  311. nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
  312. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  313. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
  314. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
  315. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
  316. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
  317. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
  318. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
  319. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
  320. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
  321. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
  322. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
  323. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
  324. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
  325. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
  326. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
  327. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
  328. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
  329. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
  330. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
  331. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
  332. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
  333. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
  334. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
  335. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
  336. nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
  337. nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
  338. nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
  339. nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
  340. nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
  341. nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
  342. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
  343. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
  344. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
  345. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
  346. nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
  347. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
  348. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
  349. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
  350. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
  351. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
  352. nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
  353. nautobot/project-static/docs/development/apps/api/setup.html +884 -80
  354. nautobot/project-static/docs/development/apps/api/testing.html +884 -80
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
  358. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
  359. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
  360. nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
  361. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
  362. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
  363. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
  364. nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
  365. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
  366. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
  367. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
  368. nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
  369. nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
  370. nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
  371. nautobot/project-static/docs/development/apps/index.html +884 -80
  372. nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
  373. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
  374. nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
  375. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
  376. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
  377. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
  378. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
  380. nautobot/project-static/docs/development/core/application-registry.html +884 -80
  381. nautobot/project-static/docs/development/core/best-practices.html +885 -80
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
  383. nautobot/project-static/docs/development/core/caching.html +884 -80
  384. nautobot/project-static/docs/development/core/controllers.html +884 -80
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
  386. nautobot/project-static/docs/development/core/generic-views.html +884 -80
  387. nautobot/project-static/docs/development/core/getting-started.html +884 -80
  388. nautobot/project-static/docs/development/core/homepage.html +884 -80
  389. nautobot/project-static/docs/development/core/index.html +884 -91
  390. nautobot/project-static/docs/development/core/model-checklist.html +887 -81
  391. nautobot/project-static/docs/development/core/model-features.html +884 -80
  392. nautobot/project-static/docs/development/core/natural-keys.html +884 -80
  393. nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
  394. nautobot/project-static/docs/development/core/release-checklist.html +887 -83
  395. nautobot/project-static/docs/development/core/role-internals.html +884 -80
  396. nautobot/project-static/docs/development/core/settings.html +884 -80
  397. nautobot/project-static/docs/development/core/style-guide.html +885 -81
  398. nautobot/project-static/docs/development/core/templates.html +896 -81
  399. nautobot/project-static/docs/development/core/testing.html +884 -80
  400. nautobot/project-static/docs/development/core/user-preferences.html +884 -80
  401. nautobot/project-static/docs/development/index.html +884 -80
  402. nautobot/project-static/docs/development/jobs/index.html +1247 -457
  403. nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
  404. nautobot/project-static/docs/index.html +13 -8228
  405. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  406. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  407. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  408. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  409. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  410. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  411. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  412. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  413. nautobot/project-static/docs/models/dcim/module.html +15 -0
  414. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  415. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  416. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  417. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  418. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  419. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  420. nautobot/project-static/docs/models/extras/role.html +15 -0
  421. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  422. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  423. nautobot/project-static/docs/models/extras/status.html +15 -0
  424. nautobot/project-static/docs/objects.inv +0 -0
  425. nautobot/project-static/docs/overview/application_stack.html +892 -81
  426. nautobot/project-static/docs/overview/design_philosophy.html +886 -82
  427. nautobot/project-static/docs/overview/index.html +9032 -13
  428. nautobot/project-static/docs/release-notes/index.html +887 -83
  429. nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
  430. nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
  431. nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
  432. nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
  433. nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
  434. nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
  435. nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
  436. nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
  437. nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
  438. nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
  439. nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
  440. nautobot/project-static/docs/requirements.txt +4 -4
  441. nautobot/project-static/docs/search/search_index.json +1 -1
  442. nautobot/project-static/docs/sitemap.xml +335 -260
  443. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  444. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
  445. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
  447. nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
  448. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
  449. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
  450. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
  451. nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
  452. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
  453. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
  454. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
  455. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
  456. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
  457. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
  458. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
  459. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
  460. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
  461. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
  462. nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
  463. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
  464. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
  465. nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
  466. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
  467. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
  468. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
  471. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
  472. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
  473. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
  474. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
  475. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
  481. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
  482. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
  483. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
  487. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  488. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
  535. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
  536. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
  537. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
  539. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
  540. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
  541. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
  548. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
  549. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
  550. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
  551. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
  552. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
  553. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
  556. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
  557. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
  558. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
  559. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
  560. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
  561. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
  564. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
  567. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
  568. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
  569. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  570. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  571. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  572. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  592. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  593. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  594. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
  595. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
  596. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
  597. nautobot/project-static/docs/user-guide/index.html +884 -80
  598. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
  599. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
  600. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
  601. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
  602. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
  603. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
  604. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
  605. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
  606. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
  607. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
  608. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
  613. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
  614. nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
  615. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
  616. nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
  617. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
  632. nautobot/project-static/js/forms.js +71 -0
  633. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  634. nautobot/project-static/js/tableconfig.js +6 -1
  635. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  636. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  637. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  638. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  639. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  640. nautobot/tenancy/__init__.py +0 -1
  641. nautobot/tenancy/apps.py +1 -0
  642. nautobot/tenancy/factory.py +3 -2
  643. nautobot/tenancy/filters/__init__.py +1 -0
  644. nautobot/tenancy/forms.py +1 -1
  645. nautobot/tenancy/templates/tenancy/tenant.html +22 -18
  646. nautobot/tenancy/views.py +11 -10
  647. nautobot/users/__init__.py +0 -1
  648. nautobot/users/api/serializers.py +1 -1
  649. nautobot/users/api/views.py +4 -2
  650. nautobot/users/apps.py +3 -2
  651. nautobot/users/factory.py +3 -3
  652. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  653. nautobot/users/models.py +12 -0
  654. nautobot/users/tests/test_filters.py +6 -3
  655. nautobot/users/urls.py +8 -0
  656. nautobot/virtualization/__init__.py +0 -1
  657. nautobot/virtualization/apps.py +1 -0
  658. nautobot/virtualization/filters.py +6 -1
  659. nautobot/virtualization/forms.py +11 -3
  660. nautobot/virtualization/graphql/types.py +2 -2
  661. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  662. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  663. nautobot/virtualization/tables.py +15 -5
  664. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  665. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  666. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  667. nautobot/virtualization/tests/test_api.py +9 -4
  668. nautobot/virtualization/tests/test_filters.py +22 -0
  669. nautobot/virtualization/tests/test_models.py +7 -3
  670. nautobot/virtualization/tests/test_views.py +19 -3
  671. nautobot/virtualization/urls.py +2 -2
  672. nautobot/virtualization/views.py +10 -32
  673. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
  674. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
  675. nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
  676. nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
  677. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
  678. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
  679. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
  680. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
  681. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
  682. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
  683. nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
  684. nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
  685. nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
  686. nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
  687. nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
  688. nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
  689. nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
  690. nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
  691. nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
  692. nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
  693. /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
  694. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
  695. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
  696. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
  697. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
nautobot/dcim/forms.py CHANGED
@@ -49,11 +49,21 @@ from nautobot.extras.forms import (
49
49
  NoteModelFormMixin,
50
50
  RoleModelBulkEditFormMixin,
51
51
  RoleModelFilterFormMixin,
52
+ RoleNotRequiredModelFormMixin,
52
53
  StatusModelBulkEditFormMixin,
53
54
  StatusModelFilterFormMixin,
54
55
  TagsBulkEditFormMixin,
55
56
  )
56
- from nautobot.extras.models import Contact, ContactAssociation, ExternalIntegration, Role, SecretsGroup, Status, Team
57
+ from nautobot.extras.models import (
58
+ Contact,
59
+ ContactAssociation,
60
+ ExternalIntegration,
61
+ Role,
62
+ SecretsGroup,
63
+ Status,
64
+ Tag,
65
+ Team,
66
+ )
57
67
  from nautobot.ipam.constants import BGP_ASN_MAX, BGP_ASN_MIN
58
68
  from nautobot.ipam.models import IPAddress, IPAddressToInterface, VLAN, VLANLocationAssignment, VRF
59
69
  from nautobot.tenancy.forms import TenancyFilterForm, TenancyForm
@@ -113,6 +123,10 @@ from .models import (
113
123
  Location,
114
124
  LocationType,
115
125
  Manufacturer,
126
+ Module,
127
+ ModuleBay,
128
+ ModuleBayTemplate,
129
+ ModuleType,
116
130
  Platform,
117
131
  PowerFeed,
118
132
  PowerOutlet,
@@ -172,6 +186,14 @@ class DeviceComponentFilterForm(NautobotFilterForm):
172
186
  )
173
187
 
174
188
 
189
+ class ModularDeviceComponentFilterForm(DeviceComponentFilterForm):
190
+ module = DynamicModelMultipleChoiceField(
191
+ queryset=Module.objects.all(),
192
+ required=False,
193
+ label="Module",
194
+ )
195
+
196
+
175
197
  class InterfaceCommonForm(forms.Form):
176
198
  def clean(self):
177
199
  super().clean()
@@ -242,6 +264,26 @@ class ComponentForm(BootstrapMixin, forms.Form):
242
264
  )
243
265
 
244
266
 
267
+ class ModularComponentForm(ComponentForm):
268
+ name_pattern = ExpandableNameField(
269
+ label="Name",
270
+ help_text="""
271
+ Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
272
+ are not supported. Examples:
273
+ <ul>
274
+ <li><code>[ge,xe]-0/0/[0-9]</code></li>
275
+ <li><code>e[0-3][a-d,f]</code></li>
276
+ </ul>
277
+
278
+ The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
279
+ may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
280
+ module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
281
+ multiple times in the component name and there is no limit to the depth of parent levels.
282
+ Any variables that cannot be replaced by a suitable position value will remain unchanged.
283
+ """,
284
+ )
285
+
286
+
245
287
  #
246
288
  # Fields
247
289
  #
@@ -897,48 +939,156 @@ class DeviceTypeFilterForm(NautobotFilterForm):
897
939
  tags = TagFilterField(model)
898
940
 
899
941
 
942
+ #
943
+ # Module types
944
+ #
945
+
946
+
947
+ class ModuleTypeForm(NautobotModelForm):
948
+ manufacturer = DynamicModelChoiceField(queryset=Manufacturer.objects.all())
949
+
950
+ class Meta:
951
+ model = ModuleType
952
+ fields = [
953
+ "manufacturer",
954
+ "model",
955
+ "part_number",
956
+ "tags",
957
+ ]
958
+
959
+
960
+ class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
961
+ """
962
+ Form for JSON/YAML import of ModuleType objects.
963
+
964
+ TODO: at some point we'll want to add general-purpose YAML serialization/deserialization,
965
+ similar to what we've done for CSV in 2.0, but for the moment we're leaving this as-is so that we can remain
966
+ at least nominally compatible with the netbox-community/devicetype-library repo.
967
+ """
968
+
969
+ manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), to_field_name="name")
970
+
971
+ class Meta:
972
+ model = ModuleType
973
+ fields = [
974
+ "manufacturer",
975
+ "model",
976
+ "part_number",
977
+ ]
978
+
979
+
980
+ class ModuleTypeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
981
+ pk = forms.ModelMultipleChoiceField(queryset=ModuleType.objects.all(), widget=forms.MultipleHiddenInput())
982
+ manufacturer = DynamicModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
983
+ part_number = forms.CharField(required=False)
984
+
985
+ class Meta:
986
+ nullable_fields = []
987
+
988
+
989
+ class ModuleTypeFilterForm(NautobotFilterForm):
990
+ model = ModuleType
991
+ q = forms.CharField(required=False, label="Search")
992
+ manufacturer = DynamicModelMultipleChoiceField(
993
+ queryset=Manufacturer.objects.all(), to_field_name="name", required=False
994
+ )
995
+ has_console_ports = forms.NullBooleanField(
996
+ required=False,
997
+ label="Has console ports",
998
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
999
+ )
1000
+ has_console_server_ports = forms.NullBooleanField(
1001
+ required=False,
1002
+ label="Has console server ports",
1003
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
1004
+ )
1005
+ has_power_ports = forms.NullBooleanField(
1006
+ required=False,
1007
+ label="Has power ports",
1008
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
1009
+ )
1010
+ has_power_outlets = forms.NullBooleanField(
1011
+ required=False,
1012
+ label="Has power outlets",
1013
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
1014
+ )
1015
+ has_interfaces = forms.NullBooleanField(
1016
+ required=False,
1017
+ label="Has interfaces",
1018
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
1019
+ )
1020
+ tags = TagFilterField(model)
1021
+
1022
+
900
1023
  #
901
1024
  # Device component templates
902
1025
  #
903
1026
 
904
1027
 
1028
+ class ComponentTemplateForm(NautobotModelForm):
1029
+ # TODO: placeholder values shouldn't be form controls, instead use:
1030
+ # <p class="form-control-static">{{ obj|hyperlinked_object_target_new_tab }}</p>
1031
+ device_type = DynamicModelChoiceField(
1032
+ queryset=DeviceType.objects.all(),
1033
+ )
1034
+
1035
+
1036
+ class ModularComponentTemplateForm(ComponentTemplateForm):
1037
+ device_type = DynamicModelChoiceField(
1038
+ queryset=DeviceType.objects.all(),
1039
+ required=False,
1040
+ )
1041
+ module_type = DynamicModelChoiceField(
1042
+ queryset=ModuleType.objects.all(),
1043
+ required=False,
1044
+ )
1045
+
1046
+
905
1047
  class ComponentTemplateCreateForm(ComponentForm):
906
1048
  """
907
1049
  Base form for the creation of device component templates (subclassed from ComponentTemplateModel).
908
1050
  """
909
1051
 
910
- manufacturer = DynamicModelChoiceField(
911
- queryset=Manufacturer.objects.all(),
912
- required=False,
913
- initial_params={"device_types": "device_type"},
1052
+ device_type = DynamicModelChoiceField(
1053
+ queryset=DeviceType.objects.all(),
914
1054
  )
1055
+ description = forms.CharField(required=False)
1056
+
1057
+
1058
+ class ModularComponentTemplateCreateForm(ModularComponentForm):
1059
+ """
1060
+ Base form for the creation of modular device component templates (subclassed from ModularComponentTemplateModel).
1061
+ """
1062
+
915
1063
  device_type = DynamicModelChoiceField(
916
1064
  queryset=DeviceType.objects.all(),
917
- query_params={"manufacturer": "$manufacturer"},
1065
+ required=False,
1066
+ )
1067
+ module_type = DynamicModelChoiceField(
1068
+ queryset=ModuleType.objects.all(),
1069
+ required=False,
918
1070
  )
919
1071
  description = forms.CharField(required=False)
920
1072
 
921
1073
 
922
- class ConsolePortTemplateForm(NautobotModelForm):
1074
+ class ConsolePortTemplateForm(ModularComponentTemplateForm):
923
1075
  class Meta:
924
1076
  model = ConsolePortTemplate
925
1077
  fields = [
926
1078
  "device_type",
1079
+ "module_type",
927
1080
  "name",
928
1081
  "label",
929
1082
  "type",
930
1083
  "description",
931
1084
  ]
932
- widgets = {
933
- "device_type": forms.HiddenInput(),
934
- }
935
1085
 
936
1086
 
937
- class ConsolePortTemplateCreateForm(ComponentTemplateCreateForm):
1087
+ class ConsolePortTemplateCreateForm(ModularComponentTemplateCreateForm):
938
1088
  type = forms.ChoiceField(choices=add_blank_choice(ConsolePortTypeChoices), widget=StaticSelect2())
939
1089
  field_order = (
940
- "manufacturer",
941
1090
  "device_type",
1091
+ "module_type",
942
1092
  "name_pattern",
943
1093
  "label_pattern",
944
1094
  "type",
@@ -959,26 +1109,24 @@ class ConsolePortTemplateBulkEditForm(NautobotBulkEditForm):
959
1109
  nullable_fields = ["label", "type", "description"]
960
1110
 
961
1111
 
962
- class ConsoleServerPortTemplateForm(NautobotModelForm):
1112
+ class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
963
1113
  class Meta:
964
1114
  model = ConsoleServerPortTemplate
965
1115
  fields = [
966
1116
  "device_type",
1117
+ "module_type",
967
1118
  "name",
968
1119
  "label",
969
1120
  "type",
970
1121
  "description",
971
1122
  ]
972
- widgets = {
973
- "device_type": forms.HiddenInput(),
974
- }
975
1123
 
976
1124
 
977
- class ConsoleServerPortTemplateCreateForm(ComponentTemplateCreateForm):
1125
+ class ConsoleServerPortTemplateCreateForm(ModularComponentTemplateCreateForm):
978
1126
  type = forms.ChoiceField(choices=add_blank_choice(ConsolePortTypeChoices), widget=StaticSelect2())
979
1127
  field_order = (
980
- "manufacturer",
981
1128
  "device_type",
1129
+ "module_type",
982
1130
  "name_pattern",
983
1131
  "label_pattern",
984
1132
  "type",
@@ -1003,11 +1151,12 @@ class ConsoleServerPortTemplateBulkEditForm(NautobotBulkEditForm):
1003
1151
  nullable_fields = ["label", "type", "description"]
1004
1152
 
1005
1153
 
1006
- class PowerPortTemplateForm(NautobotModelForm):
1154
+ class PowerPortTemplateForm(ModularComponentTemplateForm):
1007
1155
  class Meta:
1008
1156
  model = PowerPortTemplate
1009
1157
  fields = [
1010
1158
  "device_type",
1159
+ "module_type",
1011
1160
  "name",
1012
1161
  "label",
1013
1162
  "type",
@@ -1015,18 +1164,15 @@ class PowerPortTemplateForm(NautobotModelForm):
1015
1164
  "allocated_draw",
1016
1165
  "description",
1017
1166
  ]
1018
- widgets = {
1019
- "device_type": forms.HiddenInput(),
1020
- }
1021
1167
 
1022
1168
 
1023
- class PowerPortTemplateCreateForm(ComponentTemplateCreateForm):
1169
+ class PowerPortTemplateCreateForm(ModularComponentTemplateCreateForm):
1024
1170
  type = forms.ChoiceField(choices=add_blank_choice(PowerPortTypeChoices), required=False)
1025
1171
  maximum_draw = forms.IntegerField(min_value=1, required=False, help_text="Maximum power draw (watts)")
1026
1172
  allocated_draw = forms.IntegerField(min_value=1, required=False, help_text="Allocated power draw (watts)")
1027
1173
  field_order = (
1028
- "manufacturer",
1029
1174
  "device_type",
1175
+ "module_type",
1030
1176
  "name_pattern",
1031
1177
  "label_pattern",
1032
1178
  "type",
@@ -1058,11 +1204,12 @@ class PowerPortTemplateBulkEditForm(NautobotBulkEditForm):
1058
1204
  ]
1059
1205
 
1060
1206
 
1061
- class PowerOutletTemplateForm(NautobotModelForm):
1207
+ class PowerOutletTemplateForm(ModularComponentTemplateForm):
1062
1208
  class Meta:
1063
1209
  model = PowerOutletTemplate
1064
1210
  fields = [
1065
1211
  "device_type",
1212
+ "module_type",
1066
1213
  "name",
1067
1214
  "label",
1068
1215
  "type",
@@ -1070,21 +1217,22 @@ class PowerOutletTemplateForm(NautobotModelForm):
1070
1217
  "feed_leg",
1071
1218
  "description",
1072
1219
  ]
1073
- widgets = {
1074
- "device_type": forms.HiddenInput(),
1075
- }
1076
1220
 
1077
1221
  def __init__(self, *args, **kwargs):
1078
1222
  super().__init__(*args, **kwargs)
1079
1223
 
1080
- # Limit power_port_template choices to current DeviceType
1081
- if hasattr(self.instance, "device_type"):
1224
+ # Limit power_port_template choices to current DeviceType or ModuleType
1225
+ if getattr(self.instance, "device_type", None):
1082
1226
  self.fields["power_port_template"].queryset = PowerPortTemplate.objects.filter(
1083
1227
  device_type=self.instance.device_type
1084
1228
  )
1229
+ elif getattr(self.instance, "module_type", None):
1230
+ self.fields["power_port_template"].queryset = PowerPortTemplate.objects.filter(
1231
+ module_type=self.instance.module_type
1232
+ )
1085
1233
 
1086
1234
 
1087
- class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm):
1235
+ class PowerOutletTemplateCreateForm(ModularComponentTemplateCreateForm):
1088
1236
  type = forms.ChoiceField(choices=add_blank_choice(PowerOutletTypeChoices), required=False)
1089
1237
  power_port_template = DynamicModelChoiceField(
1090
1238
  queryset=PowerPortTemplate.objects.all(),
@@ -1097,8 +1245,8 @@ class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm):
1097
1245
  widget=StaticSelect2(),
1098
1246
  )
1099
1247
  field_order = (
1100
- "manufacturer",
1101
1248
  "device_type",
1249
+ "module_type",
1102
1250
  "name_pattern",
1103
1251
  "label_pattern",
1104
1252
  "type",
@@ -1113,7 +1261,6 @@ class PowerOutletTemplateBulkEditForm(NautobotBulkEditForm):
1113
1261
  device_type = forms.ModelChoiceField(
1114
1262
  queryset=DeviceType.objects.all(),
1115
1263
  required=False,
1116
- disabled=True,
1117
1264
  widget=forms.HiddenInput(),
1118
1265
  )
1119
1266
  label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
@@ -1145,11 +1292,12 @@ class PowerOutletTemplateBulkEditForm(NautobotBulkEditForm):
1145
1292
  self.fields["power_port_template"].widget.attrs["disabled"] = True
1146
1293
 
1147
1294
 
1148
- class InterfaceTemplateForm(NautobotModelForm):
1295
+ class InterfaceTemplateForm(ModularComponentTemplateForm):
1149
1296
  class Meta:
1150
1297
  model = InterfaceTemplate
1151
1298
  fields = [
1152
1299
  "device_type",
1300
+ "module_type",
1153
1301
  "name",
1154
1302
  "label",
1155
1303
  "type",
@@ -1157,17 +1305,16 @@ class InterfaceTemplateForm(NautobotModelForm):
1157
1305
  "description",
1158
1306
  ]
1159
1307
  widgets = {
1160
- "device_type": forms.HiddenInput(),
1161
1308
  "type": StaticSelect2(),
1162
1309
  }
1163
1310
 
1164
1311
 
1165
- class InterfaceTemplateCreateForm(ComponentTemplateCreateForm):
1312
+ class InterfaceTemplateCreateForm(ModularComponentTemplateCreateForm):
1166
1313
  type = forms.ChoiceField(choices=InterfaceTypeChoices, widget=StaticSelect2())
1167
1314
  mgmt_only = forms.BooleanField(required=False, label="Management only")
1168
1315
  field_order = (
1169
- "manufacturer",
1170
1316
  "device_type",
1317
+ "module_type",
1171
1318
  "name_pattern",
1172
1319
  "label_pattern",
1173
1320
  "type",
@@ -1191,11 +1338,12 @@ class InterfaceTemplateBulkEditForm(NautobotBulkEditForm):
1191
1338
  nullable_fields = ["label", "description"]
1192
1339
 
1193
1340
 
1194
- class FrontPortTemplateForm(NautobotModelForm):
1341
+ class FrontPortTemplateForm(ModularComponentTemplateForm):
1195
1342
  class Meta:
1196
1343
  model = FrontPortTemplate
1197
1344
  fields = [
1198
1345
  "device_type",
1346
+ "module_type",
1199
1347
  "name",
1200
1348
  "label",
1201
1349
  "type",
@@ -1204,21 +1352,24 @@ class FrontPortTemplateForm(NautobotModelForm):
1204
1352
  "description",
1205
1353
  ]
1206
1354
  widgets = {
1207
- "device_type": forms.HiddenInput(),
1208
1355
  "rear_port_template": StaticSelect2(),
1209
1356
  }
1210
1357
 
1211
1358
  def __init__(self, *args, **kwargs):
1212
1359
  super().__init__(*args, **kwargs)
1213
1360
 
1214
- # Limit rear_port_template choices to current DeviceType
1215
- if hasattr(self.instance, "device_type"):
1361
+ # Limit rear_port_template choices to current DeviceType or ModuleType
1362
+ if getattr(self.instance, "device_type", None):
1216
1363
  self.fields["rear_port_template"].queryset = RearPortTemplate.objects.filter(
1217
1364
  device_type=self.instance.device_type
1218
1365
  )
1366
+ elif getattr(self.instance, "module_type", None):
1367
+ self.fields["rear_port_template"].queryset = RearPortTemplate.objects.filter(
1368
+ module_type=self.instance.module_type
1369
+ )
1219
1370
 
1220
1371
 
1221
- class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
1372
+ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
1222
1373
  type = forms.ChoiceField(choices=PortTypeChoices, widget=StaticSelect2())
1223
1374
  rear_port_template_set = forms.MultipleChoiceField(
1224
1375
  choices=[],
@@ -1226,8 +1377,8 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
1226
1377
  help_text="Select one rear port assignment for each front port being created.",
1227
1378
  )
1228
1379
  field_order = (
1229
- "manufacturer",
1230
1380
  "device_type",
1381
+ "module_type",
1231
1382
  "name_pattern",
1232
1383
  "label_pattern",
1233
1384
  "type",
@@ -1238,17 +1389,25 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
1238
1389
  def __init__(self, *args, **kwargs):
1239
1390
  super().__init__(*args, **kwargs)
1240
1391
 
1241
- device_type = DeviceType.objects.get(pk=self.initial.get("device_type") or self.data.get("device_type"))
1392
+ device_type = self.initial.get("device_type") or self.data.get("device_type")
1393
+ module_type = self.initial.get("module_type") or self.data.get("module_type")
1394
+ if device_type:
1395
+ parent = DeviceType.objects.get(pk=device_type)
1396
+ elif module_type:
1397
+ parent = ModuleType.objects.get(pk=module_type)
1398
+ else:
1399
+ return
1242
1400
 
1243
1401
  # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
1244
1402
  occupied_port_positions = [
1245
1403
  (front_port_template.rear_port_template_id, front_port_template.rear_port_position)
1246
- for front_port_template in device_type.front_port_templates.all()
1404
+ for front_port_template in parent.front_port_templates.all()
1247
1405
  ]
1248
1406
 
1249
1407
  # Populate rear port choices
1250
1408
  choices = []
1251
- rear_port_templates = RearPortTemplate.objects.filter(device_type=device_type)
1409
+ parent_field_name = parent._meta.verbose_name.replace(" ", "_")
1410
+ rear_port_templates = RearPortTemplate.objects.filter(**{parent_field_name: parent})
1252
1411
  for rear_port_template in rear_port_templates:
1253
1412
  for i in range(1, rear_port_template.positions + 1):
1254
1413
  if (rear_port_template.pk, i) not in occupied_port_positions:
@@ -1300,11 +1459,12 @@ class FrontPortTemplateBulkEditForm(NautobotBulkEditForm):
1300
1459
  nullable_fields = ["description"]
1301
1460
 
1302
1461
 
1303
- class RearPortTemplateForm(NautobotModelForm):
1462
+ class RearPortTemplateForm(ModularComponentTemplateForm):
1304
1463
  class Meta:
1305
1464
  model = RearPortTemplate
1306
1465
  fields = [
1307
1466
  "device_type",
1467
+ "module_type",
1308
1468
  "name",
1309
1469
  "label",
1310
1470
  "type",
@@ -1312,12 +1472,11 @@ class RearPortTemplateForm(NautobotModelForm):
1312
1472
  "description",
1313
1473
  ]
1314
1474
  widgets = {
1315
- "device_type": forms.HiddenInput(),
1316
1475
  "type": StaticSelect2(),
1317
1476
  }
1318
1477
 
1319
1478
 
1320
- class RearPortTemplateCreateForm(ComponentTemplateCreateForm):
1479
+ class RearPortTemplateCreateForm(ModularComponentTemplateCreateForm):
1321
1480
  type = forms.ChoiceField(
1322
1481
  choices=PortTypeChoices,
1323
1482
  widget=StaticSelect2(),
@@ -1329,8 +1488,8 @@ class RearPortTemplateCreateForm(ComponentTemplateCreateForm):
1329
1488
  help_text="The number of front ports which may be mapped to each rear port",
1330
1489
  )
1331
1490
  field_order = (
1332
- "manufacturer",
1333
1491
  "device_type",
1492
+ "module_type",
1334
1493
  "name_pattern",
1335
1494
  "label_pattern",
1336
1495
  "type",
@@ -1353,7 +1512,7 @@ class RearPortTemplateBulkEditForm(NautobotBulkEditForm):
1353
1512
  nullable_fields = ["description"]
1354
1513
 
1355
1514
 
1356
- class DeviceBayTemplateForm(NautobotModelForm):
1515
+ class DeviceBayTemplateForm(ComponentTemplateForm):
1357
1516
  class Meta:
1358
1517
  model = DeviceBayTemplate
1359
1518
  fields = [
@@ -1362,14 +1521,10 @@ class DeviceBayTemplateForm(NautobotModelForm):
1362
1521
  "label",
1363
1522
  "description",
1364
1523
  ]
1365
- widgets = {
1366
- "device_type": forms.HiddenInput(),
1367
- }
1368
1524
 
1369
1525
 
1370
1526
  class DeviceBayTemplateCreateForm(ComponentTemplateCreateForm):
1371
1527
  field_order = (
1372
- "manufacturer",
1373
1528
  "device_type",
1374
1529
  "name_pattern",
1375
1530
  "label_pattern",
@@ -1386,6 +1541,92 @@ class DeviceBayTemplateBulkEditForm(NautobotBulkEditForm):
1386
1541
  nullable_fields = ("label", "description")
1387
1542
 
1388
1543
 
1544
+ class ModuleBayTemplateForm(ModularComponentTemplateForm):
1545
+ class Meta:
1546
+ model = ModuleBayTemplate
1547
+ fields = [
1548
+ "device_type",
1549
+ "module_type",
1550
+ "name",
1551
+ "position",
1552
+ "label",
1553
+ "description",
1554
+ ]
1555
+
1556
+
1557
+ class ModuleBayBaseCreateForm(BootstrapMixin, forms.Form):
1558
+ name_pattern = ExpandableNameField(label="Name")
1559
+ label_pattern = ExpandableNameField(
1560
+ label="Label",
1561
+ required=False,
1562
+ help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
1563
+ )
1564
+ position_pattern = ExpandableNameField(
1565
+ label="Position",
1566
+ required=False,
1567
+ help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
1568
+ )
1569
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1570
+
1571
+ def clean(self):
1572
+ super().clean()
1573
+
1574
+ # Validate that the number of components being created from both the name_pattern, position_pattern and label_pattern are equal
1575
+ if self.cleaned_data["label_pattern"]:
1576
+ name_pattern_count = len(self.cleaned_data["name_pattern"])
1577
+ label_pattern_count = len(self.cleaned_data["label_pattern"])
1578
+ if name_pattern_count != label_pattern_count:
1579
+ raise forms.ValidationError(
1580
+ {
1581
+ "label_pattern": f"The provided name pattern will create {name_pattern_count} components, however "
1582
+ f"{label_pattern_count} labels will be generated. These counts must match."
1583
+ },
1584
+ code="label_pattern_mismatch",
1585
+ )
1586
+
1587
+ if self.cleaned_data["position_pattern"]:
1588
+ name_pattern_count = len(self.cleaned_data["name_pattern"])
1589
+ position_pattern_count = len(self.cleaned_data["position_pattern"])
1590
+ if name_pattern_count != position_pattern_count:
1591
+ raise forms.ValidationError(
1592
+ {
1593
+ "position_pattern": f"The provided name pattern will create {name_pattern_count} components, however "
1594
+ f"{position_pattern_count} positions will be generated. These counts must match."
1595
+ },
1596
+ code="position_pattern_mismatch",
1597
+ )
1598
+
1599
+
1600
+ class ModuleBayTemplateCreateForm(ModuleBayBaseCreateForm):
1601
+ device_type = DynamicModelChoiceField(
1602
+ queryset=DeviceType.objects.all(),
1603
+ required=False,
1604
+ )
1605
+ module_type = DynamicModelChoiceField(
1606
+ queryset=ModuleType.objects.all(),
1607
+ required=False,
1608
+ )
1609
+
1610
+ field_order = (
1611
+ "device_type",
1612
+ "module_type",
1613
+ "name_pattern",
1614
+ "label_pattern",
1615
+ "position_pattern",
1616
+ "description",
1617
+ )
1618
+
1619
+
1620
+ class ModuleBayTemplateBulkEditForm(NautobotBulkEditForm):
1621
+ pk = forms.ModelMultipleChoiceField(queryset=ModuleBayTemplate.objects.all(), widget=forms.MultipleHiddenInput())
1622
+ label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1623
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1624
+ position = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1625
+
1626
+ class Meta:
1627
+ nullable_fields = ("label", "description")
1628
+
1629
+
1389
1630
  #
1390
1631
  # Component template import forms
1391
1632
  #
@@ -1400,14 +1641,7 @@ class ComponentTemplateImportForm(BootstrapMixin, CustomFieldModelCSVForm):
1400
1641
  netbox-community/devicetype-library repository.
1401
1642
  """
1402
1643
 
1403
- def __init__(self, device_type, data=None, *args, **kwargs):
1404
- # Must pass the parent DeviceType on form initialization
1405
- data.update(
1406
- {
1407
- "device_type": device_type.pk,
1408
- }
1409
- )
1410
-
1644
+ def __init__(self, data=None, *args, **kwargs):
1411
1645
  super().__init__(data, *args, **kwargs)
1412
1646
 
1413
1647
  if "type" in self.fields:
@@ -1419,9 +1653,21 @@ class ComponentTemplateImportForm(BootstrapMixin, CustomFieldModelCSVForm):
1419
1653
  data = self.cleaned_data["device_type"]
1420
1654
 
1421
1655
  # Limit fields referencing other components to the parent DeviceType
1422
- for field_name, field in self.fields.items():
1423
- if isinstance(field, forms.ModelChoiceField) and field_name != "device_type":
1424
- field.queryset = field.queryset.filter(device_type=data)
1656
+ if data:
1657
+ for field_name, field in self.fields.items():
1658
+ if isinstance(field, forms.ModelChoiceField) and field_name not in ["device_type", "module_type"]:
1659
+ field.queryset = field.queryset.filter(device_type=data)
1660
+
1661
+ return data
1662
+
1663
+ def clean_module_type(self):
1664
+ data = self.cleaned_data["module_type"]
1665
+
1666
+ # Limit fields referencing other components to the parent ModuleType
1667
+ if data:
1668
+ for field_name, field in self.fields.items():
1669
+ if isinstance(field, forms.ModelChoiceField) and field_name not in ["device_type", "module_type"]:
1670
+ field.queryset = field.queryset.filter(module_type=data)
1425
1671
 
1426
1672
  return data
1427
1673
 
@@ -1454,6 +1700,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
1454
1700
  model = ConsolePortTemplate
1455
1701
  fields = [
1456
1702
  "device_type",
1703
+ "module_type",
1457
1704
  "name",
1458
1705
  "label",
1459
1706
  "type",
@@ -1465,6 +1712,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
1465
1712
  model = ConsoleServerPortTemplate
1466
1713
  fields = [
1467
1714
  "device_type",
1715
+ "module_type",
1468
1716
  "name",
1469
1717
  "label",
1470
1718
  "type",
@@ -1476,6 +1724,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
1476
1724
  model = PowerPortTemplate
1477
1725
  fields = [
1478
1726
  "device_type",
1727
+ "module_type",
1479
1728
  "name",
1480
1729
  "label",
1481
1730
  "type",
@@ -1495,6 +1744,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
1495
1744
  model = PowerOutletTemplate
1496
1745
  fields = [
1497
1746
  "device_type",
1747
+ "module_type",
1498
1748
  "name",
1499
1749
  "label",
1500
1750
  "type",
@@ -1517,6 +1767,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
1517
1767
  model = InterfaceTemplate
1518
1768
  fields = [
1519
1769
  "device_type",
1770
+ "module_type",
1520
1771
  "name",
1521
1772
  "label",
1522
1773
  "type",
@@ -1535,6 +1786,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
1535
1786
  model = FrontPortTemplate
1536
1787
  fields = [
1537
1788
  "device_type",
1789
+ "module_type",
1538
1790
  "name",
1539
1791
  "type",
1540
1792
  "rear_port_template",
@@ -1556,6 +1808,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
1556
1808
  model = RearPortTemplate
1557
1809
  fields = [
1558
1810
  "device_type",
1811
+ "module_type",
1559
1812
  "name",
1560
1813
  "type",
1561
1814
  "positions",
@@ -1571,6 +1824,17 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
1571
1824
  ]
1572
1825
 
1573
1826
 
1827
+ class ModuleBayTemplateImportForm(ComponentTemplateImportForm):
1828
+ class Meta:
1829
+ model = ModuleBayTemplate
1830
+ fields = [
1831
+ "device_type",
1832
+ "module_type",
1833
+ "name",
1834
+ "position",
1835
+ ]
1836
+
1837
+
1574
1838
  #
1575
1839
  # Platforms
1576
1840
  #
@@ -1734,7 +1998,7 @@ class DeviceForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm, LocalC
1734
1998
  ip_choices = [(None, "---------")]
1735
1999
 
1736
2000
  # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
1737
- interface_ids = self.instance.vc_interfaces.values_list("pk", flat=True)
2001
+ interface_ids = self.instance.all_interfaces.values_list("pk", flat=True)
1738
2002
 
1739
2003
  # Collect interface IPs
1740
2004
  interface_ip_assignments = IPAddressToInterface.objects.filter(
@@ -2001,17 +2265,211 @@ class DeviceFilterForm(
2001
2265
 
2002
2266
 
2003
2267
  #
2004
- # Device components
2268
+ # Modules
2005
2269
  #
2006
2270
 
2007
2271
 
2008
- class ComponentCreateForm(ComponentForm):
2009
- """
2010
- Base form for the creation of device components (models subclassed from ComponentModel).
2011
- """
2272
+ class ModuleForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm):
2273
+ manufacturer = DynamicModelChoiceField(
2274
+ queryset=Manufacturer.objects.all(),
2275
+ required=False,
2276
+ initial_params={"module_types": "$module_type"},
2277
+ )
2278
+ module_type = DynamicModelChoiceField(
2279
+ queryset=ModuleType.objects.all(),
2280
+ query_params={"manufacturer": "$manufacturer"},
2281
+ )
2282
+ parent_module_bay_device_filter = DynamicModelChoiceField(
2283
+ queryset=Device.objects.all(),
2284
+ required=False,
2285
+ label="Parent Device",
2286
+ query_params={"has_empty_module_bays": True},
2287
+ initial_params={"module_bays": "$parent_module_bay"},
2288
+ )
2289
+ parent_module_bay_device = DynamicModelChoiceField(
2290
+ queryset=ModuleBay.objects.all(),
2291
+ label="Parent Module Bay",
2292
+ required=False,
2293
+ query_params={"parent_device": "$parent_module_bay_device_filter", "has_installed_module": False},
2294
+ initial_params={"pk": "$parent_module_bay", "parent_device__module_bays": "$parent_module_bay"},
2295
+ )
2296
+ parent_module_bay_module_filter = DynamicModelChoiceField(
2297
+ queryset=Module.objects.all(),
2298
+ required=False,
2299
+ label="Parent Module",
2300
+ query_params={"has_empty_module_bays": True},
2301
+ initial_params={"module_bays": "$parent_module_bay"},
2302
+ )
2303
+ parent_module_bay_module = DynamicModelChoiceField(
2304
+ queryset=ModuleBay.objects.all(),
2305
+ label="Parent Module Bay",
2306
+ required=False,
2307
+ query_params={"parent_module": "$parent_module_bay_module_filter", "has_installed_module": False},
2308
+ initial_params={"pk": "$parent_module_bay", "parent_module__module_bays": "$parent_module_bay"},
2309
+ )
2310
+ location = DynamicModelChoiceField(
2311
+ queryset=Location.objects.all(),
2312
+ required=False,
2313
+ label="Location",
2314
+ query_params={"content_type": Module._meta.label_lower},
2315
+ )
2316
+ role = DynamicModelChoiceField(
2317
+ queryset=Role.objects.all(),
2318
+ required=False,
2319
+ query_params={"content_types": Module._meta.label_lower},
2320
+ )
2012
2321
 
2013
- device = DynamicModelChoiceField(queryset=Device.objects.all())
2014
- description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
2322
+ class Meta:
2323
+ model = Module
2324
+ fields = [
2325
+ "manufacturer",
2326
+ "module_type",
2327
+ "parent_module_bay",
2328
+ "location",
2329
+ "serial",
2330
+ "asset_tag",
2331
+ "role",
2332
+ "status",
2333
+ "tenant_group",
2334
+ "tenant",
2335
+ "tags",
2336
+ ]
2337
+ help_texts = {
2338
+ "serial": "Module serial number",
2339
+ }
2340
+
2341
+ def clean(self):
2342
+ cleaned_data = self.cleaned_data
2343
+ if cleaned_data["parent_module_bay_device"] and cleaned_data["parent_module_bay_module"]:
2344
+ raise forms.ValidationError("Multiple parent module bays selected.")
2345
+ elif cleaned_data["parent_module_bay_device"]:
2346
+ cleaned_data["parent_module_bay"] = cleaned_data.pop("parent_module_bay_device")
2347
+ elif cleaned_data["parent_module_bay_module"]:
2348
+ cleaned_data["parent_module_bay"] = cleaned_data.pop("parent_module_bay_module")
2349
+
2350
+ return cleaned_data
2351
+
2352
+
2353
+ class ModuleBulkEditForm(
2354
+ TagsBulkEditFormMixin,
2355
+ LocatableModelBulkEditFormMixin,
2356
+ StatusModelBulkEditFormMixin,
2357
+ RoleModelBulkEditFormMixin,
2358
+ NautobotBulkEditForm,
2359
+ LocalContextModelBulkEditForm,
2360
+ ):
2361
+ pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
2362
+ manufacturer = DynamicModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
2363
+ module_type = DynamicModelChoiceField(
2364
+ queryset=ModuleType.objects.all(),
2365
+ required=False,
2366
+ query_params={"manufacturer": "$manufacturer"},
2367
+ )
2368
+ tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
2369
+ serial = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False, label="Serial Number")
2370
+
2371
+ class Meta:
2372
+ model = Module
2373
+ nullable_fields = [
2374
+ "location",
2375
+ "tenant",
2376
+ "serial",
2377
+ ]
2378
+
2379
+
2380
+ class ModuleFilterForm(
2381
+ NautobotFilterForm,
2382
+ LocalContextFilterForm,
2383
+ LocatableModelFilterFormMixin,
2384
+ TenancyFilterForm,
2385
+ StatusModelFilterFormMixin,
2386
+ RoleModelFilterFormMixin,
2387
+ ):
2388
+ model = Module
2389
+ field_order = [
2390
+ "q",
2391
+ "location",
2392
+ "status",
2393
+ "role",
2394
+ "tenant_group",
2395
+ "tenant",
2396
+ "manufacturer",
2397
+ "module_type",
2398
+ "mac_address",
2399
+ ]
2400
+ q = forms.CharField(required=False, label="Search")
2401
+ manufacturer = DynamicModelMultipleChoiceField(
2402
+ queryset=Manufacturer.objects.all(),
2403
+ to_field_name="name",
2404
+ required=False,
2405
+ label="Manufacturer",
2406
+ )
2407
+ module_type = DynamicModelMultipleChoiceField(
2408
+ queryset=ModuleType.objects.all(),
2409
+ required=False,
2410
+ label="Model",
2411
+ query_params={"manufacturer": "$manufacturer"},
2412
+ )
2413
+ mac_address = forms.CharField(required=False, label="MAC address")
2414
+ has_console_ports = forms.NullBooleanField(
2415
+ required=False,
2416
+ label="Has console ports",
2417
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2418
+ )
2419
+ has_console_server_ports = forms.NullBooleanField(
2420
+ required=False,
2421
+ label="Has console server ports",
2422
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2423
+ )
2424
+ has_power_ports = forms.NullBooleanField(
2425
+ required=False,
2426
+ label="Has power ports",
2427
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2428
+ )
2429
+ has_power_outlets = forms.NullBooleanField(
2430
+ required=False,
2431
+ label="Has power outlets",
2432
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2433
+ )
2434
+ has_interfaces = forms.NullBooleanField(
2435
+ required=False,
2436
+ label="Has interfaces",
2437
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2438
+ )
2439
+ has_front_ports = forms.NullBooleanField(
2440
+ required=False,
2441
+ label="Has front ports",
2442
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2443
+ )
2444
+ has_rear_ports = forms.NullBooleanField(
2445
+ required=False,
2446
+ label="Has rear ports",
2447
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
2448
+ )
2449
+ tags = TagFilterField(model)
2450
+
2451
+
2452
+ #
2453
+ # Device components
2454
+ #
2455
+
2456
+
2457
+ class ComponentCreateForm(ComponentForm):
2458
+ """
2459
+ Base form for the creation of device components (models subclassed from ComponentModel).
2460
+ """
2461
+
2462
+ device = DynamicModelChoiceField(queryset=Device.objects.all())
2463
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
2464
+
2465
+
2466
+ class ModularComponentCreateForm(ModularComponentForm):
2467
+ """
2468
+ Base form for the creation of modular device components (models subclassed from ModularComponentModel).
2469
+ """
2470
+
2471
+ device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
2472
+ module = DynamicModelChoiceField(queryset=Module.objects.all(), required=False)
2015
2473
 
2016
2474
 
2017
2475
  class ComponentEditForm(NautobotModelForm):
@@ -2026,9 +2484,26 @@ class ComponentEditForm(NautobotModelForm):
2026
2484
  def __init__(self, *args, **kwargs):
2027
2485
  super().__init__(*args, **kwargs)
2028
2486
 
2029
- # Disallow changing the device of an existing component
2030
- if self.instance is not None and self.instance.present_in_database:
2487
+ # Disable the device field if an initial value is provided
2488
+ if "device" in self.initial:
2489
+ self.fields["device"].disabled = True
2490
+
2491
+
2492
+ class ModularComponentEditForm(ComponentEditForm):
2493
+ """
2494
+ Base class for editing modular device components (models subclassed from ModularComponentModel).
2495
+ """
2496
+
2497
+ device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
2498
+ module = DynamicModelChoiceField(queryset=Module.objects.all(), required=False)
2499
+
2500
+ def __init__(self, *args, **kwargs):
2501
+ super().__init__(*args, **kwargs)
2502
+
2503
+ # Disable the device and module fields if an initial value is provided for either
2504
+ if "device" in self.initial or "module" in self.initial:
2031
2505
  self.fields["device"].disabled = True
2506
+ self.fields["module"].disabled = True
2032
2507
 
2033
2508
 
2034
2509
  class DeviceBulkAddComponentForm(ComponentForm, CustomFieldModelBulkEditFormMixin):
@@ -2039,22 +2514,31 @@ class DeviceBulkAddComponentForm(ComponentForm, CustomFieldModelBulkEditFormMixi
2039
2514
  nullable_fields = []
2040
2515
 
2041
2516
 
2517
+ class ModuleBulkAddComponentForm(ModularComponentForm, CustomFieldModelBulkEditFormMixin):
2518
+ pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
2519
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
2520
+
2521
+ class Meta:
2522
+ nullable_fields = []
2523
+
2524
+
2042
2525
  #
2043
2526
  # Console ports
2044
2527
  #
2045
2528
 
2046
2529
 
2047
- class ConsolePortFilterForm(DeviceComponentFilterForm):
2530
+ class ConsolePortFilterForm(ModularDeviceComponentFilterForm):
2048
2531
  model = ConsolePort
2049
2532
  type = forms.MultipleChoiceField(choices=ConsolePortTypeChoices, required=False, widget=StaticSelect2Multiple())
2050
2533
  tags = TagFilterField(model)
2051
2534
 
2052
2535
 
2053
- class ConsolePortForm(ComponentEditForm):
2536
+ class ConsolePortForm(ModularComponentEditForm):
2054
2537
  class Meta:
2055
2538
  model = ConsolePort
2056
2539
  fields = [
2057
2540
  "device",
2541
+ "module",
2058
2542
  "name",
2059
2543
  "label",
2060
2544
  "type",
@@ -2063,7 +2547,7 @@ class ConsolePortForm(ComponentEditForm):
2063
2547
  ]
2064
2548
 
2065
2549
 
2066
- class ConsolePortCreateForm(ComponentCreateForm):
2550
+ class ConsolePortCreateForm(ModularComponentCreateForm):
2067
2551
  type = forms.ChoiceField(
2068
2552
  choices=add_blank_choice(ConsolePortTypeChoices),
2069
2553
  required=False,
@@ -2071,6 +2555,7 @@ class ConsolePortCreateForm(ComponentCreateForm):
2071
2555
  )
2072
2556
  field_order = (
2073
2557
  "device",
2558
+ "module",
2074
2559
  "name_pattern",
2075
2560
  "label_pattern",
2076
2561
  "type",
@@ -2083,6 +2568,10 @@ class ConsolePortBulkCreateForm(form_from_model(ConsolePort, ["type", "tags"]),
2083
2568
  field_order = ("name_pattern", "label_pattern", "type", "description", "tags")
2084
2569
 
2085
2570
 
2571
+ class ModuleConsolePortBulkCreateForm(form_from_model(ConsolePort, ["type", "tags"]), ModuleBulkAddComponentForm):
2572
+ field_order = ("name_pattern", "label_pattern", "type", "description", "tags")
2573
+
2574
+
2086
2575
  class ConsolePortBulkEditForm(
2087
2576
  form_from_model(ConsolePort, ["label", "type", "description"]),
2088
2577
  TagsBulkEditFormMixin,
@@ -2099,17 +2588,18 @@ class ConsolePortBulkEditForm(
2099
2588
  #
2100
2589
 
2101
2590
 
2102
- class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
2591
+ class ConsoleServerPortFilterForm(ModularDeviceComponentFilterForm):
2103
2592
  model = ConsoleServerPort
2104
2593
  type = forms.MultipleChoiceField(choices=ConsolePortTypeChoices, required=False, widget=StaticSelect2Multiple())
2105
2594
  tags = TagFilterField(model)
2106
2595
 
2107
2596
 
2108
- class ConsoleServerPortForm(ComponentEditForm):
2597
+ class ConsoleServerPortForm(ModularComponentEditForm):
2109
2598
  class Meta:
2110
2599
  model = ConsoleServerPort
2111
2600
  fields = [
2112
2601
  "device",
2602
+ "module",
2113
2603
  "name",
2114
2604
  "label",
2115
2605
  "type",
@@ -2118,7 +2608,7 @@ class ConsoleServerPortForm(ComponentEditForm):
2118
2608
  ]
2119
2609
 
2120
2610
 
2121
- class ConsoleServerPortCreateForm(ComponentCreateForm):
2611
+ class ConsoleServerPortCreateForm(ModularComponentCreateForm):
2122
2612
  type = forms.ChoiceField(
2123
2613
  choices=add_blank_choice(ConsolePortTypeChoices),
2124
2614
  required=False,
@@ -2126,6 +2616,7 @@ class ConsoleServerPortCreateForm(ComponentCreateForm):
2126
2616
  )
2127
2617
  field_order = (
2128
2618
  "device",
2619
+ "module",
2129
2620
  "name_pattern",
2130
2621
  "label_pattern",
2131
2622
  "type",
@@ -2138,6 +2629,12 @@ class ConsoleServerPortBulkCreateForm(form_from_model(ConsoleServerPort, ["type"
2138
2629
  field_order = ("name_pattern", "label_pattern", "type", "description", "tags")
2139
2630
 
2140
2631
 
2632
+ class ModuleConsoleServerPortBulkCreateForm(
2633
+ form_from_model(ConsoleServerPort, ["type", "tags"]), ModuleBulkAddComponentForm
2634
+ ):
2635
+ field_order = ("name_pattern", "label_pattern", "type", "description", "tags")
2636
+
2637
+
2141
2638
  class ConsoleServerPortBulkEditForm(
2142
2639
  form_from_model(ConsoleServerPort, ["label", "type", "description"]),
2143
2640
  TagsBulkEditFormMixin,
@@ -2154,17 +2651,18 @@ class ConsoleServerPortBulkEditForm(
2154
2651
  #
2155
2652
 
2156
2653
 
2157
- class PowerPortFilterForm(DeviceComponentFilterForm):
2654
+ class PowerPortFilterForm(ModularDeviceComponentFilterForm):
2158
2655
  model = PowerPort
2159
2656
  type = forms.MultipleChoiceField(choices=PowerPortTypeChoices, required=False, widget=StaticSelect2Multiple())
2160
2657
  tags = TagFilterField(model)
2161
2658
 
2162
2659
 
2163
- class PowerPortForm(ComponentEditForm):
2660
+ class PowerPortForm(ModularComponentEditForm):
2164
2661
  class Meta:
2165
2662
  model = PowerPort
2166
2663
  fields = [
2167
2664
  "device",
2665
+ "module",
2168
2666
  "name",
2169
2667
  "label",
2170
2668
  "type",
@@ -2175,7 +2673,7 @@ class PowerPortForm(ComponentEditForm):
2175
2673
  ]
2176
2674
 
2177
2675
 
2178
- class PowerPortCreateForm(ComponentCreateForm):
2676
+ class PowerPortCreateForm(ModularComponentCreateForm):
2179
2677
  type = forms.ChoiceField(
2180
2678
  choices=add_blank_choice(PowerPortTypeChoices),
2181
2679
  required=False,
@@ -2185,6 +2683,7 @@ class PowerPortCreateForm(ComponentCreateForm):
2185
2683
  allocated_draw = forms.IntegerField(min_value=1, required=False, help_text="Allocated draw in watts")
2186
2684
  field_order = (
2187
2685
  "device",
2686
+ "module",
2188
2687
  "name_pattern",
2189
2688
  "label_pattern",
2190
2689
  "type",
@@ -2210,6 +2709,21 @@ class PowerPortBulkCreateForm(
2210
2709
  )
2211
2710
 
2212
2711
 
2712
+ class ModulePowerPortBulkCreateForm(
2713
+ form_from_model(PowerPort, ["type", "maximum_draw", "allocated_draw", "tags"]),
2714
+ ModuleBulkAddComponentForm,
2715
+ ):
2716
+ field_order = (
2717
+ "name_pattern",
2718
+ "label_pattern",
2719
+ "type",
2720
+ "maximum_draw",
2721
+ "allocated_draw",
2722
+ "description",
2723
+ "tags",
2724
+ )
2725
+
2726
+
2213
2727
  class PowerPortBulkEditForm(
2214
2728
  form_from_model(PowerPort, ["label", "type", "maximum_draw", "allocated_draw", "description"]),
2215
2729
  TagsBulkEditFormMixin,
@@ -2226,13 +2740,13 @@ class PowerPortBulkEditForm(
2226
2740
  #
2227
2741
 
2228
2742
 
2229
- class PowerOutletFilterForm(DeviceComponentFilterForm):
2743
+ class PowerOutletFilterForm(ModularDeviceComponentFilterForm):
2230
2744
  model = PowerOutlet
2231
2745
  type = forms.MultipleChoiceField(choices=PowerOutletTypeChoices, required=False, widget=StaticSelect2Multiple())
2232
2746
  tags = TagFilterField(model)
2233
2747
 
2234
2748
 
2235
- class PowerOutletForm(ComponentEditForm):
2749
+ class PowerOutletForm(ModularComponentEditForm):
2236
2750
  power_port = DynamicModelChoiceField(
2237
2751
  queryset=PowerPort.objects.all(),
2238
2752
  required=False,
@@ -2243,6 +2757,7 @@ class PowerOutletForm(ComponentEditForm):
2243
2757
  model = PowerOutlet
2244
2758
  fields = [
2245
2759
  "device",
2760
+ "module",
2246
2761
  "name",
2247
2762
  "label",
2248
2763
  "type",
@@ -2253,7 +2768,7 @@ class PowerOutletForm(ComponentEditForm):
2253
2768
  ]
2254
2769
 
2255
2770
 
2256
- class PowerOutletCreateForm(ComponentCreateForm):
2771
+ class PowerOutletCreateForm(ModularComponentCreateForm):
2257
2772
  type = forms.ChoiceField(
2258
2773
  choices=add_blank_choice(PowerOutletTypeChoices),
2259
2774
  required=False,
@@ -2267,6 +2782,7 @@ class PowerOutletCreateForm(ComponentCreateForm):
2267
2782
  feed_leg = forms.ChoiceField(choices=add_blank_choice(PowerOutletFeedLegChoices), required=False)
2268
2783
  field_order = (
2269
2784
  "device",
2785
+ "module",
2270
2786
  "name_pattern",
2271
2787
  "label_pattern",
2272
2788
  "type",
@@ -2288,6 +2804,19 @@ class PowerOutletBulkCreateForm(form_from_model(PowerOutlet, ["type", "feed_leg"
2288
2804
  )
2289
2805
 
2290
2806
 
2807
+ class ModulePowerOutletBulkCreateForm(
2808
+ form_from_model(PowerOutlet, ["type", "feed_leg", "tags"]), ModuleBulkAddComponentForm
2809
+ ):
2810
+ field_order = (
2811
+ "name_pattern",
2812
+ "label_pattern",
2813
+ "type",
2814
+ "feed_leg",
2815
+ "description",
2816
+ "tags",
2817
+ )
2818
+
2819
+
2291
2820
  class PowerOutletBulkEditForm(
2292
2821
  form_from_model(PowerOutlet, ["label", "type", "feed_leg", "power_port", "description"]),
2293
2822
  TagsBulkEditFormMixin,
@@ -2297,7 +2826,6 @@ class PowerOutletBulkEditForm(
2297
2826
  device = forms.ModelChoiceField(
2298
2827
  queryset=Device.objects.all(),
2299
2828
  required=False,
2300
- disabled=True,
2301
2829
  widget=forms.HiddenInput(),
2302
2830
  )
2303
2831
 
@@ -2321,7 +2849,7 @@ class PowerOutletBulkEditForm(
2321
2849
  #
2322
2850
 
2323
2851
 
2324
- class InterfaceFilterForm(DeviceComponentFilterForm, StatusModelFilterFormMixin):
2852
+ class InterfaceFilterForm(ModularDeviceComponentFilterForm, RoleModelFilterFormMixin, StatusModelFilterFormMixin):
2325
2853
  model = Interface
2326
2854
  type = forms.MultipleChoiceField(choices=InterfaceTypeChoices, required=False, widget=StaticSelect2Multiple())
2327
2855
  enabled = forms.NullBooleanField(required=False, widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES))
@@ -2330,7 +2858,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm, StatusModelFilterFormMixin)
2330
2858
  tags = TagFilterField(model)
2331
2859
 
2332
2860
 
2333
- class InterfaceForm(InterfaceCommonForm, ComponentEditForm):
2861
+ class InterfaceForm(InterfaceCommonForm, ModularComponentEditForm):
2334
2862
  parent_interface = DynamicModelChoiceField(
2335
2863
  queryset=Interface.objects.all(),
2336
2864
  required=False,
@@ -2389,7 +2917,9 @@ class InterfaceForm(InterfaceCommonForm, ComponentEditForm):
2389
2917
  model = Interface
2390
2918
  fields = [
2391
2919
  "device",
2920
+ "module",
2392
2921
  "name",
2922
+ "role",
2393
2923
  "label",
2394
2924
  "type",
2395
2925
  "enabled",
@@ -2420,7 +2950,8 @@ class InterfaceForm(InterfaceCommonForm, ComponentEditForm):
2420
2950
  }
2421
2951
 
2422
2952
 
2423
- class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
2953
+ class InterfaceCreateForm(ModularComponentCreateForm, InterfaceCommonForm, RoleNotRequiredModelFormMixin):
2954
+ model = Interface
2424
2955
  type = forms.ChoiceField(
2425
2956
  choices=InterfaceTypeChoices,
2426
2957
  widget=StaticSelect2(),
@@ -2503,9 +3034,11 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
2503
3034
  )
2504
3035
  field_order = (
2505
3036
  "device",
3037
+ "module",
2506
3038
  "name_pattern",
2507
3039
  "label_pattern",
2508
3040
  "status",
3041
+ "role",
2509
3042
  "type",
2510
3043
  "enabled",
2511
3044
  "parent_interface",
@@ -2527,7 +3060,9 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm):
2527
3060
  class InterfaceBulkCreateForm(
2528
3061
  form_from_model(Interface, ["enabled", "mtu", "vrf", "mgmt_only", "mode", "tags"]),
2529
3062
  DeviceBulkAddComponentForm,
3063
+ RoleNotRequiredModelFormMixin,
2530
3064
  ):
3065
+ model = Interface
2531
3066
  type = forms.ChoiceField(
2532
3067
  choices=InterfaceTypeChoices,
2533
3068
  widget=StaticSelect2(),
@@ -2542,6 +3077,39 @@ class InterfaceBulkCreateForm(
2542
3077
  "name_pattern",
2543
3078
  "label_pattern",
2544
3079
  "status",
3080
+ "role",
3081
+ "type",
3082
+ "enabled",
3083
+ "mtu",
3084
+ "vrf",
3085
+ "mgmt_only",
3086
+ "description",
3087
+ "mode",
3088
+ "tags",
3089
+ )
3090
+
3091
+
3092
+ class ModuleInterfaceBulkCreateForm(
3093
+ form_from_model(Interface, ["enabled", "mtu", "vrf", "mgmt_only", "mode", "tags"]),
3094
+ ModuleBulkAddComponentForm,
3095
+ RoleNotRequiredModelFormMixin,
3096
+ ):
3097
+ model = Interface
3098
+ type = forms.ChoiceField(
3099
+ choices=InterfaceTypeChoices,
3100
+ widget=StaticSelect2(),
3101
+ )
3102
+ status = DynamicModelChoiceField(
3103
+ required=True,
3104
+ queryset=Status.objects.all(),
3105
+ query_params={"content_types": Interface._meta.label_lower},
3106
+ )
3107
+
3108
+ field_order = (
3109
+ "name_pattern",
3110
+ "label_pattern",
3111
+ "status",
3112
+ "role",
2545
3113
  "type",
2546
3114
  "enabled",
2547
3115
  "mtu",
@@ -2559,6 +3127,7 @@ class InterfaceBulkEditForm(
2559
3127
  ),
2560
3128
  TagsBulkEditFormMixin,
2561
3129
  StatusModelBulkEditFormMixin,
3130
+ RoleModelBulkEditFormMixin,
2562
3131
  NautobotBulkEditForm,
2563
3132
  ):
2564
3133
  pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput())
@@ -2670,13 +3239,13 @@ class InterfaceBulkEditForm(
2670
3239
  #
2671
3240
 
2672
3241
 
2673
- class FrontPortFilterForm(DeviceComponentFilterForm):
3242
+ class FrontPortFilterForm(ModularDeviceComponentFilterForm):
2674
3243
  model = FrontPort
2675
3244
  type = forms.MultipleChoiceField(choices=PortTypeChoices, required=False, widget=StaticSelect2Multiple())
2676
3245
  tags = TagFilterField(model)
2677
3246
 
2678
3247
 
2679
- class FrontPortForm(ComponentEditForm):
3248
+ class FrontPortForm(ModularComponentEditForm):
2680
3249
  rear_port = DynamicModelChoiceField(
2681
3250
  queryset=RearPort.objects.all(),
2682
3251
  query_params={"device": "$device"},
@@ -2686,6 +3255,7 @@ class FrontPortForm(ComponentEditForm):
2686
3255
  model = FrontPort
2687
3256
  fields = [
2688
3257
  "device",
3258
+ "module",
2689
3259
  "name",
2690
3260
  "label",
2691
3261
  "type",
@@ -2700,7 +3270,7 @@ class FrontPortForm(ComponentEditForm):
2700
3270
 
2701
3271
 
2702
3272
  # TODO: Merge with FrontPortTemplateCreateForm to remove duplicate logic
2703
- class FrontPortCreateForm(ComponentCreateForm):
3273
+ class FrontPortCreateForm(ModularComponentCreateForm):
2704
3274
  type = forms.ChoiceField(
2705
3275
  choices=PortTypeChoices,
2706
3276
  widget=StaticSelect2(),
@@ -2712,6 +3282,7 @@ class FrontPortCreateForm(ComponentCreateForm):
2712
3282
  )
2713
3283
  field_order = (
2714
3284
  "device",
3285
+ "module",
2715
3286
  "name_pattern",
2716
3287
  "label_pattern",
2717
3288
  "type",
@@ -2723,17 +3294,24 @@ class FrontPortCreateForm(ComponentCreateForm):
2723
3294
  def __init__(self, *args, **kwargs):
2724
3295
  super().__init__(*args, **kwargs)
2725
3296
 
2726
- device = Device.objects.get(pk=self.initial.get("device") or self.data.get("device"))
3297
+ device = self.initial.get("device") or self.data.get("device")
3298
+ module = self.initial.get("module") or self.data.get("module")
3299
+ if device:
3300
+ parent = Device.objects.get(pk=device)
3301
+ elif module:
3302
+ parent = Module.objects.get(pk=module)
3303
+ else:
3304
+ return
2727
3305
 
2728
3306
  # Determine which rear port positions are occupied. These will be excluded from the list of available
2729
3307
  # mappings.
2730
3308
  occupied_port_positions = [
2731
- (front_port.rear_port_id, front_port.rear_port_position) for front_port in device.front_ports.all()
3309
+ (front_port.rear_port_id, front_port.rear_port_position) for front_port in parent.front_ports.all()
2732
3310
  ]
2733
3311
 
2734
3312
  # Populate rear port choices
2735
3313
  choices = []
2736
- rear_ports = RearPort.objects.filter(device=device)
3314
+ rear_ports = RearPort.objects.filter(**{parent._meta.model_name: parent})
2737
3315
  for rear_port in rear_ports:
2738
3316
  for i in range(1, rear_port.positions + 1):
2739
3317
  if (rear_port.pk, i) not in occupied_port_positions:
@@ -2794,17 +3372,18 @@ class FrontPortBulkEditForm(
2794
3372
  #
2795
3373
 
2796
3374
 
2797
- class RearPortFilterForm(DeviceComponentFilterForm):
3375
+ class RearPortFilterForm(ModularDeviceComponentFilterForm):
2798
3376
  model = RearPort
2799
3377
  type = forms.MultipleChoiceField(choices=PortTypeChoices, required=False, widget=StaticSelect2Multiple())
2800
3378
  tags = TagFilterField(model)
2801
3379
 
2802
3380
 
2803
- class RearPortForm(ComponentEditForm):
3381
+ class RearPortForm(ModularComponentEditForm):
2804
3382
  class Meta:
2805
3383
  model = RearPort
2806
3384
  fields = [
2807
3385
  "device",
3386
+ "module",
2808
3387
  "name",
2809
3388
  "label",
2810
3389
  "type",
@@ -2817,7 +3396,7 @@ class RearPortForm(ComponentEditForm):
2817
3396
  }
2818
3397
 
2819
3398
 
2820
- class RearPortCreateForm(ComponentCreateForm):
3399
+ class RearPortCreateForm(ModularComponentCreateForm):
2821
3400
  type = forms.ChoiceField(
2822
3401
  choices=PortTypeChoices,
2823
3402
  widget=StaticSelect2(),
@@ -2830,6 +3409,7 @@ class RearPortCreateForm(ComponentCreateForm):
2830
3409
  )
2831
3410
  field_order = (
2832
3411
  "device",
3412
+ "module",
2833
3413
  "name_pattern",
2834
3414
  "label_pattern",
2835
3415
  "type",
@@ -2850,6 +3430,19 @@ class RearPortBulkCreateForm(form_from_model(RearPort, ["type", "positions", "ta
2850
3430
  )
2851
3431
 
2852
3432
 
3433
+ class ModuleRearPortBulkCreateForm(
3434
+ form_from_model(RearPort, ["type", "positions", "tags"]), ModuleBulkAddComponentForm
3435
+ ):
3436
+ field_order = (
3437
+ "name_pattern",
3438
+ "label_pattern",
3439
+ "type",
3440
+ "positions",
3441
+ "description",
3442
+ "tags",
3443
+ )
3444
+
3445
+
2853
3446
  class RearPortBulkEditForm(
2854
3447
  form_from_model(RearPort, ["label", "type", "description"]),
2855
3448
  TagsBulkEditFormMixin,
@@ -2922,13 +3515,119 @@ class DeviceBayBulkEditForm(
2922
3515
  nullable_fields = ["label", "description"]
2923
3516
 
2924
3517
 
3518
+ #
3519
+ # Module bays
3520
+ #
3521
+
3522
+
3523
+ class ModuleBayFilterForm(NautobotFilterForm):
3524
+ model = ModuleBay
3525
+
3526
+ field_order = ["q", "parent_device", "parent_module"]
3527
+ q = forms.CharField(required=False, label="Search")
3528
+ parent_device = DynamicModelMultipleChoiceField(
3529
+ queryset=Device.objects.all(),
3530
+ required=False,
3531
+ label="Parent device",
3532
+ )
3533
+ parent_module = DynamicModelMultipleChoiceField(
3534
+ queryset=Module.objects.all(),
3535
+ required=False,
3536
+ label="Parent module",
3537
+ )
3538
+
3539
+
3540
+ class ModuleBayForm(NautobotModelForm):
3541
+ parent_device = DynamicModelChoiceField(
3542
+ queryset=Device.objects.all(),
3543
+ required=False,
3544
+ label="Parent Device",
3545
+ )
3546
+ parent_module = DynamicModelChoiceField(
3547
+ queryset=Module.objects.all(),
3548
+ required=False,
3549
+ label="Parent Module",
3550
+ )
3551
+ # TODO: Installed module field
3552
+
3553
+ class Meta:
3554
+ model = ModuleBay
3555
+ fields = [
3556
+ "parent_device",
3557
+ "parent_module",
3558
+ "name",
3559
+ "position",
3560
+ "label",
3561
+ "description",
3562
+ "tags",
3563
+ ]
3564
+
3565
+ def __init__(self, *args, **kwargs):
3566
+ super().__init__(*args, **kwargs)
3567
+
3568
+ # Disable the parent_device and parent_module fields if an initial value is provided for either
3569
+ if "parent_device" in self.initial or "parent_module" in self.initial:
3570
+ self.fields["parent_device"].disabled = True
3571
+ self.fields["parent_module"].disabled = True
3572
+
3573
+
3574
+ class ModuleBayCreateForm(ModuleBayBaseCreateForm):
3575
+ parent_device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
3576
+ parent_module = DynamicModelChoiceField(queryset=Module.objects.all(), required=False)
3577
+ tags = DynamicModelMultipleChoiceField(
3578
+ queryset=Tag.objects.all(),
3579
+ required=False,
3580
+ query_params={"content_types": ModuleBay._meta.label_lower},
3581
+ )
3582
+ field_order = (
3583
+ "parent_device",
3584
+ "parent_module",
3585
+ "name_pattern",
3586
+ "label_pattern",
3587
+ "position_pattern",
3588
+ "description",
3589
+ "tags",
3590
+ )
3591
+
3592
+
3593
+ class ModuleBayBulkCreateForm(
3594
+ form_from_model(ModuleBay, ["tags"]),
3595
+ ModuleBayBaseCreateForm,
3596
+ CustomFieldModelBulkEditFormMixin,
3597
+ ):
3598
+ pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput())
3599
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
3600
+
3601
+ field_order = ("name_pattern", "label_pattern", "position_pattern", "description", "tags")
3602
+
3603
+ class Meta:
3604
+ nullable_fields = []
3605
+
3606
+
3607
+ class ModuleModuleBayBulkCreateForm(ModuleBayBulkCreateForm):
3608
+ pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
3609
+
3610
+ class Meta(ModuleBayBulkCreateForm.Meta):
3611
+ pass
3612
+
3613
+
3614
+ class ModuleBayBulkEditForm(
3615
+ form_from_model(ModuleBay, ["label", "description", "position"]),
3616
+ TagsBulkEditFormMixin,
3617
+ NautobotBulkEditForm,
3618
+ ):
3619
+ pk = forms.ModelMultipleChoiceField(queryset=ModuleBay.objects.all(), widget=forms.MultipleHiddenInput())
3620
+
3621
+ class Meta:
3622
+ nullable_fields = ["label", "description"]
3623
+
3624
+
2925
3625
  #
2926
3626
  # Inventory items
2927
3627
  #
2928
3628
 
2929
3629
 
2930
- class InventoryItemForm(NautobotModelForm):
2931
- device = DynamicModelChoiceField(queryset=Device.objects.all())
3630
+ class InventoryItemForm(ComponentEditForm):
2932
3631
  parent = DynamicModelChoiceField(
2933
3632
  queryset=InventoryItem.objects.all(),
2934
3633
  required=False,
@@ -3101,6 +3800,14 @@ class ConnectCableToDeviceForm(ConnectCableExcludeIDMixin, NautobotModelForm):
3101
3800
  "rack": "$termination_b_rack",
3102
3801
  },
3103
3802
  )
3803
+ termination_b_module = DynamicModelChoiceField(
3804
+ queryset=Module.objects.all(),
3805
+ label="Module",
3806
+ required=False,
3807
+ query_params={
3808
+ "device": "$termination_b_device",
3809
+ },
3810
+ )
3104
3811
 
3105
3812
  class Meta:
3106
3813
  model = Cable
@@ -3135,7 +3842,7 @@ class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
3135
3842
  queryset=ConsolePort.objects.all(),
3136
3843
  label="Name",
3137
3844
  disabled_indicator="cable",
3138
- query_params={"device": "$termination_b_device"},
3845
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3139
3846
  )
3140
3847
 
3141
3848
 
@@ -3144,7 +3851,7 @@ class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
3144
3851
  queryset=ConsoleServerPort.objects.all(),
3145
3852
  label="Name",
3146
3853
  disabled_indicator="cable",
3147
- query_params={"device": "$termination_b_device"},
3854
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3148
3855
  )
3149
3856
 
3150
3857
 
@@ -3153,7 +3860,7 @@ class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
3153
3860
  queryset=PowerPort.objects.all(),
3154
3861
  label="Name",
3155
3862
  disabled_indicator="cable",
3156
- query_params={"device": "$termination_b_device"},
3863
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3157
3864
  )
3158
3865
 
3159
3866
 
@@ -3162,7 +3869,7 @@ class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
3162
3869
  queryset=PowerOutlet.objects.all(),
3163
3870
  label="Name",
3164
3871
  disabled_indicator="cable",
3165
- query_params={"device": "$termination_b_device"},
3872
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3166
3873
  )
3167
3874
 
3168
3875
 
@@ -3173,6 +3880,7 @@ class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
3173
3880
  disabled_indicator="cable",
3174
3881
  query_params={
3175
3882
  "device_id": "$termination_b_device",
3883
+ "module": "$termination_b_module",
3176
3884
  "kind": "physical",
3177
3885
  },
3178
3886
  )
@@ -3183,7 +3891,7 @@ class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
3183
3891
  queryset=FrontPort.objects.all(),
3184
3892
  label="Name",
3185
3893
  disabled_indicator="cable",
3186
- query_params={"device": "$termination_b_device"},
3894
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3187
3895
  )
3188
3896
 
3189
3897
 
@@ -3192,7 +3900,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
3192
3900
  queryset=RearPort.objects.all(),
3193
3901
  label="Name",
3194
3902
  disabled_indicator="cable",
3195
- query_params={"device": "$termination_b_device"},
3903
+ query_params={"device": "$termination_b_device", "module": "$termination_b_module"},
3196
3904
  )
3197
3905
 
3198
3906