nautobot 2.2.8__py3-none-any.whl → 2.3.0__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 (704) 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 +113 -0
  26. nautobot/cloud/filters.py +187 -0
  27. nautobot/cloud/forms.py +339 -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 +246 -0
  32. nautobot/cloud/navigation.py +85 -0
  33. nautobot/cloud/tables.py +157 -0
  34. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
  35. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
  36. nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
  37. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
  38. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -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 +125 -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 +110 -14
  57. nautobot/core/forms/fields.py +10 -4
  58. nautobot/core/forms/forms.py +11 -3
  59. nautobot/core/forms/widgets.py +18 -1
  60. nautobot/core/graphql/generators.py +2 -2
  61. nautobot/core/graphql/schema.py +28 -7
  62. nautobot/core/jobs/__init__.py +20 -3
  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 +15 -19
  73. nautobot/core/settings.yaml +48 -13
  74. nautobot/core/settings_funcs.py +103 -0
  75. nautobot/core/tables.py +130 -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 +28 -8
  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 +63 -14
  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 +204 -2
  120. nautobot/core/tests/test_tables.py +3 -1
  121. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  122. nautobot/core/tests/test_templatetags_netutils.py +3 -3
  123. nautobot/core/tests/test_utils.py +31 -20
  124. nautobot/core/tests/test_views.py +6 -6
  125. nautobot/core/urls.py +8 -3
  126. nautobot/core/utils/deprecation.py +29 -0
  127. nautobot/core/utils/filtering.py +12 -9
  128. nautobot/core/utils/lookup.py +37 -2
  129. nautobot/core/utils/requests.py +4 -1
  130. nautobot/core/views/__init__.py +137 -24
  131. nautobot/core/views/generic.py +119 -67
  132. nautobot/core/views/mixins.py +105 -36
  133. nautobot/core/views/paginator.py +9 -3
  134. nautobot/core/views/renderers.py +121 -56
  135. nautobot/core/views/utils.py +81 -1
  136. nautobot/dcim/__init__.py +0 -1
  137. nautobot/dcim/api/serializers.py +180 -44
  138. nautobot/dcim/api/urls.py +7 -3
  139. nautobot/dcim/api/views.py +53 -7
  140. nautobot/dcim/apps.py +3 -0
  141. nautobot/dcim/choices.py +25 -0
  142. nautobot/dcim/constants.py +7 -0
  143. nautobot/dcim/factory.py +252 -18
  144. nautobot/dcim/filters/__init__.py +373 -193
  145. nautobot/dcim/filters/mixins.py +274 -1
  146. nautobot/dcim/forms.py +834 -121
  147. nautobot/dcim/graphql/types.py +2 -2
  148. nautobot/dcim/homepage.py +1 -1
  149. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  150. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  151. nautobot/dcim/migrations/0061_module_models.py +862 -0
  152. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  153. nautobot/dcim/models/__init__.py +8 -0
  154. nautobot/dcim/models/cables.py +15 -0
  155. nautobot/dcim/models/device_component_templates.py +207 -53
  156. nautobot/dcim/models/device_components.py +282 -99
  157. nautobot/dcim/models/devices.py +472 -13
  158. nautobot/dcim/models/racks.py +0 -1
  159. nautobot/dcim/navigation.py +47 -0
  160. nautobot/dcim/signals.py +3 -3
  161. nautobot/dcim/tables/__init__.py +35 -23
  162. nautobot/dcim/tables/devices.py +248 -47
  163. nautobot/dcim/tables/devicetypes.py +65 -9
  164. nautobot/dcim/tables/racks.py +5 -1
  165. nautobot/dcim/tables/template_code.py +46 -26
  166. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  167. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  168. nautobot/dcim/templates/dcim/device/base.html +14 -6
  169. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  170. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  171. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  172. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  173. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  174. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  175. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  176. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  177. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  178. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  179. nautobot/dcim/templates/dcim/device.html +45 -1
  180. nautobot/dcim/templates/dcim/device_component.html +13 -5
  181. nautobot/dcim/templates/dcim/device_list.html +2 -1
  182. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  183. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  184. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  185. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  186. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  187. nautobot/dcim/templates/dcim/interface.html +17 -2
  188. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  189. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  190. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  191. nautobot/dcim/templates/dcim/module/base.html +97 -0
  192. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  193. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  194. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  195. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  196. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  197. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  198. nautobot/dcim/templates/dcim/module_list.html +20 -0
  199. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  200. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  201. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  202. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  203. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  204. nautobot/dcim/templates/dcim/module_update.html +71 -0
  205. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  206. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  207. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  208. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  209. nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
  210. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  211. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  212. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  213. nautobot/dcim/tests/test_api.py +693 -208
  214. nautobot/dcim/tests/test_filters.py +843 -217
  215. nautobot/dcim/tests/test_models.py +1103 -8
  216. nautobot/dcim/tests/test_views.py +1525 -343
  217. nautobot/dcim/urls.py +17 -2
  218. nautobot/dcim/utils.py +2 -3
  219. nautobot/dcim/views.py +1109 -113
  220. nautobot/extras/__init__.py +0 -1
  221. nautobot/extras/api/serializers.py +115 -3
  222. nautobot/extras/api/urls.py +12 -0
  223. nautobot/extras/api/views.py +73 -59
  224. nautobot/extras/apps.py +2 -2
  225. nautobot/extras/choices.py +43 -0
  226. nautobot/extras/context_managers.py +13 -8
  227. nautobot/extras/datasources/git.py +2 -0
  228. nautobot/extras/factory.py +460 -9
  229. nautobot/extras/filters/__init__.py +174 -3
  230. nautobot/extras/filters/mixins.py +46 -43
  231. nautobot/extras/forms/base.py +24 -5
  232. nautobot/extras/forms/forms.py +227 -8
  233. nautobot/extras/forms/mixins.py +93 -0
  234. nautobot/extras/graphql/types.py +23 -10
  235. nautobot/extras/homepage.py +26 -3
  236. nautobot/extras/jobs.py +2 -2
  237. nautobot/extras/management/__init__.py +1 -0
  238. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  239. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  240. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  241. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  242. nautobot/extras/migrations/0111_metadata.py +162 -0
  243. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  244. nautobot/extras/migrations/0113_saved_views.py +77 -0
  245. nautobot/extras/models/__init__.py +15 -1
  246. nautobot/extras/models/change_logging.py +3 -3
  247. nautobot/extras/models/contacts.py +4 -0
  248. nautobot/extras/models/customfields.py +18 -3
  249. nautobot/extras/models/groups.py +389 -225
  250. nautobot/extras/models/jobs.py +87 -3
  251. nautobot/extras/models/metadata.py +441 -0
  252. nautobot/extras/models/mixins.py +72 -62
  253. nautobot/extras/models/models.py +118 -9
  254. nautobot/extras/models/relationships.py +9 -2
  255. nautobot/extras/models/tags.py +13 -2
  256. nautobot/extras/navigation.py +57 -0
  257. nautobot/extras/plugins/__init__.py +3 -1
  258. nautobot/extras/querysets.py +30 -66
  259. nautobot/extras/signals.py +109 -101
  260. nautobot/extras/tables.py +201 -17
  261. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  262. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  263. nautobot/extras/templates/extras/job.html +1 -1
  264. nautobot/extras/templates/extras/job_detail.html +11 -0
  265. nautobot/extras/templates/extras/jobresult.html +61 -74
  266. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  267. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  268. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  269. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  270. nautobot/extras/templates/extras/plugins_list.html +1 -3
  271. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  272. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  273. nautobot/extras/tests/integration/test_customfields.py +1 -0
  274. nautobot/extras/tests/test_api.py +509 -23
  275. nautobot/extras/tests/test_changelog.py +20 -9
  276. nautobot/extras/tests/test_context_managers.py +22 -15
  277. nautobot/extras/tests/test_datasources.py +13 -1
  278. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  279. nautobot/extras/tests/test_filters.py +211 -12
  280. nautobot/extras/tests/test_jobs.py +6 -6
  281. nautobot/extras/tests/test_models.py +501 -4
  282. nautobot/extras/tests/test_relationships.py +1 -0
  283. nautobot/extras/tests/test_views.py +586 -8
  284. nautobot/extras/tests/test_webhooks.py +1 -1
  285. nautobot/extras/urls.py +5 -0
  286. nautobot/extras/utils.py +85 -16
  287. nautobot/extras/views.py +562 -122
  288. nautobot/ipam/__init__.py +0 -1
  289. nautobot/ipam/apps.py +1 -0
  290. nautobot/ipam/factory.py +17 -19
  291. nautobot/ipam/filters.py +13 -0
  292. nautobot/ipam/forms.py +8 -4
  293. nautobot/ipam/graphql/types.py +2 -2
  294. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  295. nautobot/ipam/models.py +20 -20
  296. nautobot/ipam/querysets.py +1 -1
  297. nautobot/ipam/signals.py +4 -2
  298. nautobot/ipam/tables.py +5 -0
  299. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  300. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  301. nautobot/ipam/templates/ipam/prefix.html +1 -0
  302. nautobot/ipam/tests/test_api.py +37 -18
  303. nautobot/ipam/tests/test_filters.py +26 -2
  304. nautobot/ipam/tests/test_models.py +9 -2
  305. nautobot/ipam/tests/test_querysets.py +1 -1
  306. nautobot/ipam/tests/test_views.py +3 -2
  307. nautobot/ipam/urls.py +2 -2
  308. nautobot/ipam/views.py +20 -34
  309. nautobot/project-static/css/base.css +21 -0
  310. nautobot/project-static/css/dark.css +11 -0
  311. nautobot/project-static/docs/404.html +894 -90
  312. nautobot/project-static/docs/apps/index.html +894 -90
  313. nautobot/project-static/docs/apps/nautobot-apps.html +894 -90
  314. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  315. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  316. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  317. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +921 -122
  318. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +906 -103
  319. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1620 -905
  320. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +937 -146
  321. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +979 -190
  322. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +903 -101
  323. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +899 -95
  324. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +993 -195
  325. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +976 -133
  326. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1080 -274
  327. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1244 -336
  328. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1729 -877
  329. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1166 -383
  330. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2090 -1376
  331. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2248 -1424
  332. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +914 -113
  333. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +965 -165
  334. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1012 -225
  335. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1915 -1279
  336. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1848 -1104
  337. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +906 -103
  338. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2335 -1701
  339. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1804 -1026
  340. nautobot/project-static/docs/development/apps/api/configuration-view.html +894 -90
  341. nautobot/project-static/docs/development/apps/api/database-backend-config.html +894 -90
  342. nautobot/project-static/docs/development/apps/api/models/django-admin.html +894 -90
  343. nautobot/project-static/docs/development/apps/api/models/global-search.html +894 -90
  344. nautobot/project-static/docs/development/apps/api/models/graphql.html +894 -90
  345. nautobot/project-static/docs/development/apps/api/models/index.html +944 -92
  346. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +894 -90
  347. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +894 -90
  348. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +894 -90
  349. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +894 -90
  350. nautobot/project-static/docs/development/apps/api/platform-features/index.html +894 -90
  351. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +894 -90
  352. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +894 -90
  353. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +894 -90
  354. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +894 -90
  355. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +894 -90
  356. nautobot/project-static/docs/development/apps/api/prometheus.html +894 -90
  357. nautobot/project-static/docs/development/apps/api/setup.html +894 -90
  358. nautobot/project-static/docs/development/apps/api/testing.html +894 -90
  359. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +894 -90
  360. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +894 -90
  361. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +894 -90
  362. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +894 -90
  363. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +894 -90
  364. nautobot/project-static/docs/development/apps/api/views/base-template.html +894 -90
  365. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +894 -90
  366. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +894 -90
  367. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +894 -90
  368. nautobot/project-static/docs/development/apps/api/views/index.html +894 -90
  369. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +894 -90
  370. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +894 -90
  371. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +894 -90
  372. nautobot/project-static/docs/development/apps/api/views/notes.html +894 -90
  373. nautobot/project-static/docs/development/apps/api/views/rest-api.html +894 -90
  374. nautobot/project-static/docs/development/apps/api/views/urls.html +894 -90
  375. nautobot/project-static/docs/development/apps/index.html +894 -90
  376. nautobot/project-static/docs/development/apps/migration/code-updates.html +894 -90
  377. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +894 -90
  378. nautobot/project-static/docs/development/apps/migration/from-v1.html +894 -90
  379. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +894 -90
  380. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +894 -90
  381. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +894 -90
  382. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +894 -90
  383. nautobot/project-static/docs/development/apps/porting-from-netbox.html +894 -90
  384. nautobot/project-static/docs/development/core/application-registry.html +894 -90
  385. nautobot/project-static/docs/development/core/best-practices.html +895 -90
  386. nautobot/project-static/docs/development/core/bootstrap-ui.html +894 -90
  387. nautobot/project-static/docs/development/core/caching.html +894 -90
  388. nautobot/project-static/docs/development/core/controllers.html +894 -90
  389. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +894 -90
  390. nautobot/project-static/docs/development/core/generic-views.html +894 -90
  391. nautobot/project-static/docs/development/core/getting-started.html +894 -90
  392. nautobot/project-static/docs/development/core/homepage.html +894 -90
  393. nautobot/project-static/docs/development/core/index.html +905 -90
  394. nautobot/project-static/docs/development/core/model-checklist.html +903 -91
  395. nautobot/project-static/docs/development/core/model-features.html +894 -90
  396. nautobot/project-static/docs/development/core/natural-keys.html +894 -90
  397. nautobot/project-static/docs/development/core/navigation-menu.html +894 -90
  398. nautobot/project-static/docs/development/core/release-checklist.html +897 -93
  399. nautobot/project-static/docs/development/core/role-internals.html +894 -90
  400. nautobot/project-static/docs/development/core/settings.html +894 -90
  401. nautobot/project-static/docs/development/core/style-guide.html +895 -91
  402. nautobot/project-static/docs/development/core/templates.html +906 -91
  403. nautobot/project-static/docs/development/core/testing.html +894 -90
  404. nautobot/project-static/docs/development/core/user-preferences.html +894 -90
  405. nautobot/project-static/docs/development/index.html +894 -90
  406. nautobot/project-static/docs/development/jobs/index.html +1271 -453
  407. nautobot/project-static/docs/development/jobs/migration/from-v1.html +894 -90
  408. nautobot/project-static/docs/index.html +9032 -13
  409. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  410. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  411. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  412. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  413. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  414. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  415. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  416. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  417. nautobot/project-static/docs/models/dcim/module.html +15 -0
  418. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  419. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  420. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  421. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  422. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  423. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  424. nautobot/project-static/docs/models/extras/role.html +15 -0
  425. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  426. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  427. nautobot/project-static/docs/models/extras/status.html +15 -0
  428. nautobot/project-static/docs/objects.inv +0 -0
  429. nautobot/project-static/docs/overview/application_stack.html +902 -91
  430. nautobot/project-static/docs/overview/design_philosophy.html +896 -92
  431. nautobot/project-static/docs/overview/index.html +13 -8228
  432. nautobot/project-static/docs/release-notes/index.html +1131 -94
  433. nautobot/project-static/docs/release-notes/version-1.0.html +894 -90
  434. nautobot/project-static/docs/release-notes/version-1.1.html +894 -90
  435. nautobot/project-static/docs/release-notes/version-1.2.html +894 -90
  436. nautobot/project-static/docs/release-notes/version-1.3.html +894 -90
  437. nautobot/project-static/docs/release-notes/version-1.4.html +894 -90
  438. nautobot/project-static/docs/release-notes/version-1.5.html +895 -91
  439. nautobot/project-static/docs/release-notes/version-1.6.html +895 -91
  440. nautobot/project-static/docs/release-notes/version-2.0.html +894 -90
  441. nautobot/project-static/docs/release-notes/version-2.1.html +894 -90
  442. nautobot/project-static/docs/release-notes/version-2.2.html +1137 -196
  443. nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
  444. nautobot/project-static/docs/requirements.txt +5 -5
  445. nautobot/project-static/docs/search/search_index.json +1 -1
  446. nautobot/project-static/docs/sitemap.xml +335 -260
  447. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +894 -90
  449. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +894 -90
  450. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +894 -90
  451. nautobot/project-static/docs/user-guide/administration/configuration/index.html +894 -90
  452. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +1025 -175
  453. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +894 -90
  454. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +894 -90
  455. nautobot/project-static/docs/user-guide/administration/guides/caching.html +894 -90
  456. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +902 -90
  457. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +894 -90
  458. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +894 -90
  459. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +894 -90
  460. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +894 -90
  461. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +894 -90
  462. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +894 -90
  463. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +894 -90
  464. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +894 -90
  465. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +946 -155
  466. nautobot/project-static/docs/user-guide/administration/installation/index.html +903 -95
  467. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +936 -124
  468. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +956 -159
  469. nautobot/project-static/docs/user-guide/administration/installation/services.html +915 -114
  470. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +910 -101
  471. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +894 -90
  472. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +894 -90
  473. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +894 -90
  474. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +894 -90
  475. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +977 -121
  476. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +894 -90
  477. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +894 -90
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +894 -90
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +894 -90
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +894 -90
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +894 -90
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +894 -90
  483. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +894 -90
  484. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +894 -90
  485. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +894 -90
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +894 -90
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +895 -91
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +894 -90
  489. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +898 -90
  490. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +897 -93
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  496. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  497. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +908 -104
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +925 -107
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +925 -107
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +920 -102
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +925 -107
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +908 -104
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +908 -104
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +915 -107
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +922 -118
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +923 -119
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +920 -116
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +908 -104
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +916 -107
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +928 -110
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +938 -120
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +930 -108
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +908 -104
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +939 -121
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +930 -112
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +920 -116
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +923 -119
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +925 -117
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +918 -114
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +908 -104
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +942 -85
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +926 -108
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +908 -104
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +945 -88
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +923 -105
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +931 -127
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +920 -116
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +908 -104
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +924 -106
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +926 -108
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +908 -104
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +908 -104
  538. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +908 -104
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +938 -90
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +894 -90
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +899 -91
  542. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +899 -91
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +894 -90
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +894 -90
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +894 -90
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +894 -90
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +894 -90
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +894 -90
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +894 -90
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +894 -90
  551. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +894 -90
  552. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +894 -90
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +903 -98
  554. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +894 -90
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +894 -90
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +894 -90
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +894 -90
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +894 -90
  559. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +899 -91
  560. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +894 -90
  561. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +894 -90
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +894 -90
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +894 -90
  564. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +894 -90
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +894 -90
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +894 -90
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +894 -90
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +894 -90
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +894 -90
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +894 -90
  571. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +894 -90
  572. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +894 -90
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  592. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  593. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  594. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  595. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  596. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  597. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  598. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +894 -90
  599. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +894 -90
  600. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +894 -90
  601. nautobot/project-static/docs/user-guide/index.html +894 -90
  602. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +894 -90
  603. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +894 -90
  604. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +894 -90
  605. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +894 -90
  606. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1260 -787
  607. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +897 -93
  608. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +894 -90
  609. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +894 -90
  610. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +894 -90
  611. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +894 -90
  612. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +894 -90
  613. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +894 -90
  614. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +894 -90
  615. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +894 -90
  616. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +894 -90
  617. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +898 -90
  618. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +894 -90
  619. nautobot/project-static/docs/user-guide/platform-functionality/note.html +897 -93
  620. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
  621. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +897 -93
  622. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +894 -90
  623. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +894 -90
  624. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +894 -90
  625. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +894 -90
  626. nautobot/project-static/docs/user-guide/platform-functionality/role.html +897 -93
  627. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  628. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +897 -93
  629. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  630. nautobot/project-static/docs/user-guide/platform-functionality/status.html +894 -90
  631. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +894 -90
  632. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +952 -123
  633. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +894 -90
  634. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +894 -90
  635. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +894 -90
  636. nautobot/project-static/js/forms.js +71 -0
  637. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  638. nautobot/project-static/js/tableconfig.js +6 -1
  639. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  640. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  641. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  642. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  643. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  644. nautobot/tenancy/__init__.py +0 -1
  645. nautobot/tenancy/apps.py +1 -0
  646. nautobot/tenancy/factory.py +3 -2
  647. nautobot/tenancy/filters/__init__.py +1 -0
  648. nautobot/tenancy/forms.py +1 -1
  649. nautobot/tenancy/templates/tenancy/tenant.html +24 -20
  650. nautobot/tenancy/views.py +11 -10
  651. nautobot/users/__init__.py +0 -1
  652. nautobot/users/api/serializers.py +1 -1
  653. nautobot/users/api/views.py +4 -2
  654. nautobot/users/apps.py +3 -2
  655. nautobot/users/factory.py +3 -3
  656. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  657. nautobot/users/models.py +12 -0
  658. nautobot/users/tests/test_filters.py +6 -3
  659. nautobot/users/urls.py +8 -0
  660. nautobot/virtualization/__init__.py +0 -1
  661. nautobot/virtualization/apps.py +1 -0
  662. nautobot/virtualization/filters.py +6 -1
  663. nautobot/virtualization/forms.py +11 -3
  664. nautobot/virtualization/graphql/types.py +2 -2
  665. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  666. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  667. nautobot/virtualization/models.py +0 -2
  668. nautobot/virtualization/tables.py +12 -8
  669. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  670. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  671. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  672. nautobot/virtualization/tests/test_api.py +9 -4
  673. nautobot/virtualization/tests/test_filters.py +22 -0
  674. nautobot/virtualization/tests/test_models.py +7 -3
  675. nautobot/virtualization/tests/test_views.py +19 -3
  676. nautobot/virtualization/urls.py +2 -2
  677. nautobot/virtualization/views.py +10 -32
  678. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/METADATA +21 -19
  679. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/RECORD +684 -564
  680. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  681. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  682. nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
  683. nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
  684. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
  685. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
  686. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
  687. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
  688. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
  689. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
  690. nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
  691. nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
  692. nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
  693. nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
  694. nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
  695. nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
  696. nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
  697. nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
  698. nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
  699. nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
  700. /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
  701. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
  702. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
  703. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
  704. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
2
4
  from django.contrib.contenttypes.models import ContentType
3
5
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -24,6 +26,7 @@ from nautobot.dcim.choices import (
24
26
  PowerOutletFeedLegChoices,
25
27
  PowerOutletTypeChoices,
26
28
  PowerPortTypeChoices,
29
+ SubdeviceRoleChoices,
27
30
  )
28
31
  from nautobot.dcim.constants import (
29
32
  NONCONNECTABLE_IFACE_TYPES,
@@ -35,6 +38,7 @@ from nautobot.dcim.constants import (
35
38
  from nautobot.extras.models import (
36
39
  ChangeLoggedModel,
37
40
  RelationshipModel,
41
+ RoleField,
38
42
  Status,
39
43
  StatusField,
40
44
  )
@@ -51,6 +55,7 @@ __all__ = (
51
55
  "InterfaceRedundancyGroup",
52
56
  "InterfaceRedundancyGroupAssociation",
53
57
  "InventoryItem",
58
+ "ModuleBay",
54
59
  "PathEndpoint",
55
60
  "PowerOutlet",
56
61
  "PowerPort",
@@ -69,7 +74,7 @@ class ComponentModel(PrimaryModel):
69
74
  label = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Physical label")
70
75
  description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
71
76
 
72
- natural_key_field_names = ["name", "device"]
77
+ natural_key_field_names = ["device", "name"]
73
78
 
74
79
  class Meta:
75
80
  abstract = True
@@ -83,6 +88,9 @@ class ComponentModel(PrimaryModel):
83
88
  """
84
89
  Return a new ObjectChange with the `related_object` pinned to the `device` by default.
85
90
  """
91
+ if "related_object" in kwargs:
92
+ return super().to_objectchange(action, **kwargs)
93
+
86
94
  # Annotate the parent Device
87
95
  try:
88
96
  device = self.device
@@ -92,6 +100,114 @@ class ComponentModel(PrimaryModel):
92
100
 
93
101
  return super().to_objectchange(action, related_object=device, **kwargs)
94
102
 
103
+ @property
104
+ def parent(self):
105
+ return self.device
106
+
107
+
108
+ class ModularComponentModel(ComponentModel):
109
+ device = ForeignKeyWithAutoRelatedName(
110
+ to="dcim.Device",
111
+ on_delete=models.CASCADE,
112
+ blank=True,
113
+ null=True,
114
+ )
115
+ module = ForeignKeyWithAutoRelatedName(
116
+ to="dcim.Module",
117
+ on_delete=models.CASCADE,
118
+ blank=True,
119
+ null=True,
120
+ )
121
+
122
+ natural_key_field_names = ["device", "module", "name"]
123
+
124
+ class Meta:
125
+ abstract = True
126
+ ordering = ("device", "module", "_name")
127
+ constraints = [
128
+ models.UniqueConstraint(
129
+ fields=("device", "name"),
130
+ name="%(app_label)s_%(class)s_device_name_unique",
131
+ ),
132
+ models.UniqueConstraint(
133
+ fields=("module", "name"),
134
+ name="%(app_label)s_%(class)s_module_name_unique",
135
+ ),
136
+ ]
137
+
138
+ @property
139
+ def parent(self):
140
+ """Device that this component belongs to, walking up module inheritance if necessary."""
141
+ return self.module.device if self.module else self.device
142
+
143
+ def render_name_template(self, save=False):
144
+ """
145
+ Replace the {module}, {module.parent}, {module.parent.parent}, etc. variables in the name
146
+ field with the actual module bay positions.
147
+
148
+ Args:
149
+ save (bool, optional): If True, save the object after updating the name field. Defaults to False.
150
+
151
+ If a module bay position is blank, it will be skipped and the parents will be checked until a non-blank
152
+ position is found. If all parent module bays are exhausted, the variable is left as-is.
153
+
154
+ Example:
155
+ - Device (name="Device 1")
156
+ - ModuleBay (position="A")
157
+ - Module
158
+ - ModuleBay (position="")
159
+ - Module
160
+ - Interface (name="{module}{module.parent}")
161
+
162
+ The deeply nested interface would be named "A{module.parent}" after calling this method.
163
+ """
164
+ if self.module and self.module.parent_module_bay and "{module" in self.name:
165
+ name = ""
166
+ module_bay = self.module.parent_module_bay
167
+ positions = []
168
+ while module_bay is not None:
169
+ position = getattr(module_bay, "position", None)
170
+ if position:
171
+ positions.append(position)
172
+ module_bay = getattr(getattr(module_bay, "parent_module", None), "parent_module_bay", None)
173
+ for part in re.split(r"({module(?:\.parent)*})", self.name):
174
+ if re.fullmatch(r"{module(\.parent)*}", part):
175
+ depth = part.count(".parent")
176
+ if depth < len(positions):
177
+ name += positions[depth]
178
+ continue
179
+ name += part
180
+ if self.name != name:
181
+ self.name = name
182
+ if save:
183
+ self.save(update_fields=["_name", "name"])
184
+
185
+ def to_objectchange(self, action, **kwargs):
186
+ """
187
+ Return a new ObjectChange with the `related_object` pinned to the parent `device` or `module`.
188
+ """
189
+ if "related_object" in kwargs:
190
+ return super().to_objectchange(action, **kwargs)
191
+
192
+ # Annotate the parent
193
+ try:
194
+ parent = self.device if self.device else self.module
195
+ except ObjectDoesNotExist:
196
+ # The parent may have already been deleted
197
+ parent = None
198
+
199
+ return super().to_objectchange(action, related_object=parent, **kwargs)
200
+
201
+ def clean(self):
202
+ super().clean()
203
+
204
+ # Validate that a Device or Module is set, but not both
205
+ if self.device and self.module:
206
+ raise ValidationError("Only one of device or module must be set")
207
+
208
+ if not (self.device or self.module):
209
+ raise ValidationError("Either device or module must be set")
210
+
95
211
 
96
212
  class CableTermination(models.Model):
97
213
  """
@@ -211,9 +327,9 @@ class PathEndpoint(models.Model):
211
327
  "graphql",
212
328
  "webhooks",
213
329
  )
214
- class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
330
+ class ConsolePort(ModularComponentModel, CableTermination, PathEndpoint):
215
331
  """
216
- A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
332
+ A physical console port within a Device or Module. ConsolePorts connect to ConsoleServerPorts.
217
333
  """
218
334
 
219
335
  type = models.CharField(
@@ -223,14 +339,6 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
223
339
  help_text="Physical port type",
224
340
  )
225
341
 
226
- class Meta:
227
- ordering = ("device", "_name")
228
- unique_together = ("device", "name")
229
-
230
- @property
231
- def parent(self):
232
- return self.device
233
-
234
342
 
235
343
  #
236
344
  # Console server ports
@@ -238,9 +346,9 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
238
346
 
239
347
 
240
348
  @extras_features("custom_links", "cable_terminations", "custom_validators", "graphql", "webhooks")
241
- class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
349
+ class ConsoleServerPort(ModularComponentModel, CableTermination, PathEndpoint):
242
350
  """
243
- A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
351
+ A physical port within a Device or Module (typically a designated console server) which provides access to ConsolePorts.
244
352
  """
245
353
 
246
354
  type = models.CharField(
@@ -250,14 +358,6 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
250
358
  help_text="Physical port type",
251
359
  )
252
360
 
253
- class Meta:
254
- ordering = ("device", "_name")
255
- unique_together = ("device", "name")
256
-
257
- @property
258
- def parent(self):
259
- return self.device
260
-
261
361
 
262
362
  #
263
363
  # Power ports
@@ -272,9 +372,9 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
272
372
  "graphql",
273
373
  "webhooks",
274
374
  )
275
- class PowerPort(CableTermination, PathEndpoint, ComponentModel):
375
+ class PowerPort(ModularComponentModel, CableTermination, PathEndpoint):
276
376
  """
277
- A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
377
+ A physical power supply (intake) port within a Device or Module. PowerPorts connect to PowerOutlets.
278
378
  """
279
379
 
280
380
  type = models.CharField(
@@ -296,14 +396,6 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
296
396
  help_text="Allocated power draw (watts)",
297
397
  )
298
398
 
299
- class Meta:
300
- ordering = ("device", "_name")
301
- unique_together = ("device", "name")
302
-
303
- @property
304
- def parent(self):
305
- return self.device
306
-
307
399
  def clean(self):
308
400
  super().clean()
309
401
 
@@ -383,9 +475,9 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
383
475
 
384
476
 
385
477
  @extras_features("cable_terminations", "custom_links", "custom_validators", "graphql", "webhooks")
386
- class PowerOutlet(CableTermination, PathEndpoint, ComponentModel):
478
+ class PowerOutlet(ModularComponentModel, CableTermination, PathEndpoint):
387
479
  """
388
- A physical power outlet (output) within a Device which provides power to a PowerPort.
480
+ A physical power outlet (output) within a Device or Module which provides power to a PowerPort.
389
481
  """
390
482
 
391
483
  type = models.CharField(
@@ -409,19 +501,11 @@ class PowerOutlet(CableTermination, PathEndpoint, ComponentModel):
409
501
  help_text="Phase (for three-phase feeds)",
410
502
  )
411
503
 
412
- class Meta:
413
- ordering = ("device", "_name")
414
- unique_together = ("device", "name")
415
-
416
- @property
417
- def parent(self):
418
- return self.device
419
-
420
504
  def clean(self):
421
505
  super().clean()
422
506
 
423
507
  # Validate power port assignment
424
- if self.power_port and self.power_port.device != self.device:
508
+ if self.power_port and self.parent and self.power_port.parent != self.parent:
425
509
  raise ValidationError(f"Parent power port ({self.power_port}) must belong to the same device")
426
510
 
427
511
 
@@ -445,6 +529,7 @@ class BaseInterface(RelationshipModel):
445
529
  validators=[MinValueValidator(1), MaxValueValidator(65536)],
446
530
  verbose_name="MTU",
447
531
  )
532
+ role = RoleField(blank=True, null=True)
448
533
  mode = models.CharField(max_length=50, choices=InterfaceModeChoices, blank=True)
449
534
  parent_interface = models.ForeignKey(
450
535
  to="self",
@@ -499,9 +584,9 @@ class BaseInterface(RelationshipModel):
499
584
  "statuses",
500
585
  "webhooks",
501
586
  )
502
- class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
587
+ class Interface(ModularComponentModel, CableTermination, PathEndpoint, BaseInterface):
503
588
  """
504
- A network interface within a Device. A physical Interface can connect to exactly one other Interface.
589
+ A network interface within a Device or Module. A physical Interface can connect to exactly one other Interface.
505
590
  """
506
591
 
507
592
  # Override ComponentModel._name to specify naturalize_interface function
@@ -558,15 +643,14 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
558
643
  verbose_name="IP Addresses",
559
644
  )
560
645
 
561
- class Meta:
562
- ordering = ("device", CollateAsChar("_name"))
563
- unique_together = ("device", "name")
646
+ class Meta(ModularComponentModel.Meta):
647
+ ordering = ("device", "module", CollateAsChar("_name"))
564
648
 
565
649
  def clean(self):
566
650
  super().clean()
567
651
 
568
652
  # VRF validation
569
- if self.vrf and self.vrf not in self.device.vrfs.all():
653
+ if self.vrf and self.parent and self.vrf not in self.parent.vrfs.all():
570
654
  # TODO(jathan): Or maybe we automatically add the VRF to the device?
571
655
  raise ValidationError({"vrf": "VRF must be assigned to same Device."})
572
656
 
@@ -577,19 +661,23 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
577
661
  raise ValidationError({"lag": "A LAG interface cannot be its own parent."})
578
662
 
579
663
  # An interface's LAG must belong to the same device or virtual chassis
580
- if self.lag.device_id != self.device_id:
581
- if self.device.virtual_chassis is None:
664
+ if self.parent and self.lag.parent != self.parent:
665
+ if self.lag.parent is None:
666
+ raise ValidationError(
667
+ {"lag": f"The selected LAG interface ({self.lag}) does not belong to a device."}
668
+ )
669
+ elif self.parent.virtual_chassis is None:
582
670
  raise ValidationError(
583
671
  {
584
- "lag": f"The selected LAG interface ({self.lag}) belongs to a different device ({self.lag.device})."
672
+ "lag": f"The selected LAG interface ({self.lag}) belongs to a different device ({self.lag.parent})."
585
673
  }
586
674
  )
587
- elif self.lag.device.virtual_chassis_id != self.device.virtual_chassis_id:
675
+ elif self.lag.parent.virtual_chassis_id != self.parent.virtual_chassis_id:
588
676
  raise ValidationError(
589
677
  {
590
678
  "lag": (
591
- f"The selected LAG interface ({self.lag}) belongs to {self.lag.device}, which is not part "
592
- f"of virtual chassis {self.device.virtual_chassis}."
679
+ f"The selected LAG interface ({self.lag}) belongs to {self.lag.parent}, which is not part "
680
+ f"of virtual chassis {self.parent.virtual_chassis}."
593
681
  )
594
682
  }
595
683
  )
@@ -622,19 +710,19 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
622
710
  )
623
711
 
624
712
  # An interface's parent must belong to the same device or virtual chassis
625
- if self.parent_interface.device != self.device:
626
- if getattr(self.device, "virtual_chassis", None) is None:
713
+ if self.parent and self.parent_interface.parent != self.parent:
714
+ if getattr(self.parent, "virtual_chassis", None) is None:
627
715
  raise ValidationError(
628
716
  {
629
717
  "parent_interface": f"The selected parent interface ({self.parent_interface}) belongs to a different device "
630
- f"({self.parent_interface.device})."
718
+ f"({self.parent_interface.parent})."
631
719
  }
632
720
  )
633
- elif self.parent_interface.device.virtual_chassis != self.device.virtual_chassis:
721
+ elif self.parent_interface.parent.virtual_chassis != self.parent.virtual_chassis:
634
722
  raise ValidationError(
635
723
  {
636
- "parent_interface": f"The selected parent interface ({self.parent_interface}) belongs to {self.parent_interface.device}, which "
637
- f"is not part of virtual chassis {self.device.virtual_chassis}."
724
+ "parent_interface": f"The selected parent interface ({self.parent_interface}) belongs to {self.parent_interface.parent}, which "
725
+ f"is not part of virtual chassis {self.parent.virtual_chassis}."
638
726
  }
639
727
  )
640
728
 
@@ -644,7 +732,8 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
644
732
  if (
645
733
  self.untagged_vlan
646
734
  and self.untagged_vlan.locations.exists()
647
- and not self.untagged_vlan.locations.filter(id=self.device.location_id).exists()
735
+ and self.parent
736
+ and not self.untagged_vlan.locations.filter(id=self.parent.location_id).exists()
648
737
  ):
649
738
  raise ValidationError(
650
739
  {
@@ -662,22 +751,22 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
662
751
  raise ValidationError({"bridge": "An interface cannot be bridged to itself."})
663
752
 
664
753
  # A bridged interface belong to the same device or virtual chassis
665
- if self.bridge.device.id != self.device.id:
666
- if getattr(self.device, "virtual_chassis", None) is None:
754
+ if self.parent and self.bridge.parent != self.parent:
755
+ if getattr(self.parent, "virtual_chassis", None) is None:
667
756
  raise ValidationError(
668
757
  {
669
758
  "bridge": (
670
759
  f"The selected bridge interface ({self.bridge}) belongs to a different device "
671
- f"({self.bridge.device})."
760
+ f"({self.bridge.parent})."
672
761
  )
673
762
  }
674
763
  )
675
- elif self.bridge.device.virtual_chassis_id != self.device.virtual_chassis_id:
764
+ elif self.bridge.parent.virtual_chassis_id != self.parent.virtual_chassis_id:
676
765
  raise ValidationError(
677
766
  {
678
767
  "bridge": (
679
- f"The selected bridge interface ({self.bridge}) belongs to {self.bridge.device}, which "
680
- f"is not part of virtual chassis {self.device.virtual_chassis}."
768
+ f"The selected bridge interface ({self.bridge}) belongs to {self.bridge.parent}, which "
769
+ f"is not part of virtual chassis {self.parent.virtual_chassis}."
681
770
  )
682
771
  }
683
772
  )
@@ -765,10 +854,6 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
765
854
  def ip_address_count(self):
766
855
  return self.ip_addresses.count()
767
856
 
768
- @property
769
- def parent(self):
770
- return self.device
771
-
772
857
 
773
858
  @extras_features(
774
859
  "custom_fields",
@@ -878,6 +963,7 @@ class InterfaceRedundancyGroupAssociation(BaseModel, ChangeLoggedModel):
878
963
  related_name="interface_redundancy_group_associations",
879
964
  )
880
965
  priority = models.PositiveSmallIntegerField()
966
+ is_metadata_associable_model = False
881
967
 
882
968
  class Meta:
883
969
  """Meta class."""
@@ -887,7 +973,7 @@ class InterfaceRedundancyGroupAssociation(BaseModel, ChangeLoggedModel):
887
973
 
888
974
  def __str__(self):
889
975
  """Return a string representation of the instance."""
890
- return f"{self.interface_redundancy_group}: {self.interface.device} {self.interface}: {self.priority}"
976
+ return f"{self.interface_redundancy_group}: {self.interface.device or self.interface.module} {self.interface}: {self.priority}"
891
977
 
892
978
 
893
979
  #
@@ -896,9 +982,9 @@ class InterfaceRedundancyGroupAssociation(BaseModel, ChangeLoggedModel):
896
982
 
897
983
 
898
984
  @extras_features("cable_terminations", "custom_links", "custom_validators", "graphql", "webhooks")
899
- class FrontPort(CableTermination, ComponentModel):
985
+ class FrontPort(ModularComponentModel, CableTermination):
900
986
  """
901
- A pass-through port on the front of a Device.
987
+ A pass-through port on the front of a Device or Module.
902
988
  """
903
989
 
904
990
  type = models.CharField(max_length=50, choices=PortTypeChoices)
@@ -911,22 +997,22 @@ class FrontPort(CableTermination, ComponentModel):
911
997
  ],
912
998
  )
913
999
 
914
- class Meta:
915
- ordering = ("device", "_name")
916
- unique_together = (
917
- ("device", "name"),
918
- ("rear_port", "rear_port_position"),
919
- )
1000
+ natural_key_field_names = ["device", "module", "name", "rear_port", "rear_port_position"]
920
1001
 
921
- @property
922
- def parent(self):
923
- return self.device
1002
+ class Meta(ModularComponentModel.Meta):
1003
+ constraints = [
1004
+ *ModularComponentModel.Meta.constraints,
1005
+ models.UniqueConstraint(
1006
+ fields=("rear_port", "rear_port_position"),
1007
+ name="dcim_frontport_rear_port_position_unique",
1008
+ ),
1009
+ ]
924
1010
 
925
1011
  def clean(self):
926
1012
  super().clean()
927
1013
 
928
1014
  # Validate rear port assignment
929
- if self.rear_port.device != self.device:
1015
+ if self.parent and self.rear_port.parent != self.parent:
930
1016
  raise ValidationError({"rear_port": f"Rear port ({self.rear_port}) must belong to the same device"})
931
1017
 
932
1018
  # Validate rear port position assignment
@@ -940,9 +1026,9 @@ class FrontPort(CableTermination, ComponentModel):
940
1026
 
941
1027
 
942
1028
  @extras_features("cable_terminations", "custom_links", "custom_validators", "graphql", "webhooks")
943
- class RearPort(CableTermination, ComponentModel):
1029
+ class RearPort(ModularComponentModel, CableTermination):
944
1030
  """
945
- A pass-through port on the rear of a Device.
1031
+ A pass-through port on the rear of a Device or Module.
946
1032
  """
947
1033
 
948
1034
  type = models.CharField(max_length=50, choices=PortTypeChoices)
@@ -954,10 +1040,6 @@ class RearPort(CableTermination, ComponentModel):
954
1040
  ],
955
1041
  )
956
1042
 
957
- class Meta:
958
- ordering = ("device", "_name")
959
- unique_together = ("device", "name")
960
-
961
1043
  def clean(self):
962
1044
  super().clean()
963
1045
 
@@ -971,10 +1053,6 @@ class RearPort(CableTermination, ComponentModel):
971
1053
  }
972
1054
  )
973
1055
 
974
- @property
975
- def parent(self):
976
- return self.device
977
-
978
1056
 
979
1057
  #
980
1058
  # Device bays
@@ -1019,10 +1097,12 @@ class DeviceBay(ComponentModel):
1019
1097
  "installed_device": f"Cannot install the specified device; device is already installed in {current_bay}"
1020
1098
  }
1021
1099
  )
1022
-
1023
- @property
1024
- def parent(self):
1025
- return self.device
1100
+ if self.installed_device.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_CHILD:
1101
+ raise ValidationError(
1102
+ {
1103
+ "installed_device": f'Cannot install device "{self.installed_device}"; device-type "{self.installed_device.device_type}" subdevice_role is not "child".'
1104
+ }
1105
+ )
1026
1106
 
1027
1107
 
1028
1108
  #
@@ -1072,7 +1152,8 @@ class InventoryItem(TreeModel, ComponentModel):
1072
1152
  related_name="inventory_items",
1073
1153
  blank=True,
1074
1154
  null=True,
1075
- verbose_name="The software version installed on this inventory item",
1155
+ verbose_name="Software Version",
1156
+ help_text="The software version installed on this inventory item",
1076
1157
  )
1077
1158
  software_image_files = models.ManyToManyField(
1078
1159
  to="dcim.SoftwareImageFile",
@@ -1094,3 +1175,105 @@ class InventoryItem(TreeModel, ComponentModel):
1094
1175
  For the time being we just use the PK as a natural key.
1095
1176
  """
1096
1177
  return ["pk"]
1178
+
1179
+
1180
+ @extras_features(
1181
+ "custom_links",
1182
+ "custom_validators",
1183
+ "export_templates",
1184
+ "graphql",
1185
+ "webhooks",
1186
+ )
1187
+ class ModuleBay(PrimaryModel):
1188
+ """
1189
+ A slot in a Device or Module which can contain Modules.
1190
+ """
1191
+
1192
+ parent_device = models.ForeignKey(
1193
+ to="dcim.Device",
1194
+ on_delete=models.CASCADE,
1195
+ related_name="module_bays",
1196
+ blank=True,
1197
+ null=True,
1198
+ )
1199
+ parent_module = models.ForeignKey(
1200
+ to="dcim.Module",
1201
+ on_delete=models.CASCADE,
1202
+ related_name="module_bays",
1203
+ blank=True,
1204
+ null=True,
1205
+ )
1206
+ name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, db_index=True)
1207
+ _name = NaturalOrderingField(target_field="name", max_length=CHARFIELD_MAX_LENGTH, blank=True, db_index=True)
1208
+ position = models.CharField(
1209
+ blank=True,
1210
+ max_length=CHARFIELD_MAX_LENGTH,
1211
+ help_text="The position of the module bay within the parent device/module",
1212
+ )
1213
+ label = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Physical label")
1214
+ description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
1215
+
1216
+ clone_fields = ["parent_device", "parent_module"]
1217
+
1218
+ # The recursive nature of this model combined with the fact that it can be a child of a
1219
+ # device or location makes our natural key implementation unusable, so just use the pk
1220
+ natural_key_field_names = ["pk"]
1221
+
1222
+ class Meta:
1223
+ # TODO: Ordering by parent_module.id is not correct but prevents an infinite loop
1224
+ ordering = (
1225
+ "parent_device",
1226
+ "parent_module__id",
1227
+ "_name",
1228
+ )
1229
+ constraints = [
1230
+ models.UniqueConstraint(
1231
+ fields=["parent_device", "name"],
1232
+ name="dcim_modulebay_parent_device_name_unique",
1233
+ ),
1234
+ models.UniqueConstraint(
1235
+ fields=["parent_module", "name"],
1236
+ name="dcim_modulebay_parent_module_name_unique",
1237
+ ),
1238
+ ]
1239
+
1240
+ @property
1241
+ def parent(self):
1242
+ """Walk up parent chain to find the Device that this ModuleBay is installed in, if one exists."""
1243
+ return self.parent_module.device if self.parent_module else self.parent_device
1244
+
1245
+ def __str__(self):
1246
+ if self.parent_device is not None:
1247
+ return f"{self.parent_device} ({self.name})"
1248
+ else:
1249
+ return f"{self.parent_module} ({self.name})"
1250
+
1251
+ @property
1252
+ def display(self):
1253
+ if self.parent_device is not None:
1254
+ return f"{self.parent_device.display} ({self.name})"
1255
+ else:
1256
+ return f"{self.parent_module.display} ({self.name})"
1257
+
1258
+ def to_objectchange(self, action, **kwargs):
1259
+ """
1260
+ Return a new ObjectChange with the `related_object` pinned to the parent `device` or `module`.
1261
+ """
1262
+ # Annotate the parent
1263
+ try:
1264
+ parent = self.parent
1265
+ except ObjectDoesNotExist:
1266
+ # The parent has already been deleted
1267
+ parent = None
1268
+
1269
+ return super().to_objectchange(action, related_object=parent, **kwargs)
1270
+
1271
+ def clean(self):
1272
+ super().clean()
1273
+
1274
+ # Validate that a Device or Module is set, but not both
1275
+ if self.parent_device and self.parent_module:
1276
+ raise ValidationError("Only one of parent_device or parent_module must be set")
1277
+
1278
+ if not (self.parent_device or self.parent_module):
1279
+ raise ValidationError("Either parent_device or parent_module must be set")