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
@@ -1,22 +1,26 @@
1
+ from datetime import timedelta
1
2
  from pathlib import Path
2
3
 
3
4
  from django.contrib.contenttypes.models import ContentType
4
- from django.core.files.base import ContentFile
5
+ from django.core.cache import cache
6
+ from django.utils import timezone
5
7
  import yaml
6
8
 
9
+ from nautobot.core.jobs.cleanup import CleanupTypes
7
10
  from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
8
- from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
11
+ from nautobot.dcim.models import DeviceType, Location, LocationType, Manufacturer
9
12
  from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
13
+ from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
10
14
  from nautobot.extras.models import (
11
15
  Contact,
12
16
  ContactAssociation,
13
17
  ExportTemplate,
14
- FileProxy,
15
18
  JobLogEntry,
19
+ JobResult,
20
+ ObjectChange,
16
21
  Role,
17
22
  Status,
18
23
  )
19
- from nautobot.ipam.models import Prefix
20
24
  from nautobot.users.models import ObjectPermission
21
25
 
22
26
 
@@ -214,76 +218,6 @@ class ImportObjectsTestCase(TransactionTestCase):
214
218
  )
215
219
  self.assertEqual(4, Status.objects.filter(name__startswith="test_status").count())
216
220
 
217
- def test_csv_import_with_utf_8_with_bom_encoding(self):
218
- """
219
- A superuser running the job with a .csv file with utf_8 with bom encoding should successfully create all specified objects.
220
- Test for bug fix https://github.com/nautobot/nautobot/issues/5812 and https://github.com/nautobot/nautobot/issues/5985
221
- """
222
-
223
- status = Status.objects.get(name="Active").pk
224
- content = f"prefix,status\n192.168.1.1/32,{status}"
225
- content = content.encode("utf-8-sig")
226
- filename = "test.csv"
227
- csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
228
- job_result = create_job_result_and_run_job(
229
- "nautobot.core.jobs",
230
- "ImportObjects",
231
- content_type=ContentType.objects.get_for_model(Prefix).pk,
232
- csv_file=csv_file.id,
233
- )
234
- self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
235
- self.assertFalse(
236
- JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
237
- )
238
- self.assertFalse(
239
- JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
240
- )
241
- self.assertEqual(
242
- 1, Prefix.objects.filter(status=Status.objects.get(name="Active"), prefix="192.168.1.1/32").count()
243
- )
244
- mfr = Manufacturer.objects.create(name="Test Cisco Manufacturer")
245
- device_type = DeviceType.objects.create(
246
- manufacturer=mfr,
247
- model="Cisco CSR1000v",
248
- u_height=0,
249
- )
250
- location_type = LocationType.objects.create(name="Test Location Type")
251
- location_type.content_types.set([ContentType.objects.get_for_model(Device)])
252
- location = Location.objects.create(
253
- name="Device Location",
254
- location_type=location_type,
255
- status=Status.objects.get_for_model(Location).first(),
256
- )
257
- role = Role.objects.create(name="Device Status")
258
- role.content_types.set([ContentType.objects.get_for_model(Device)])
259
- content = "\n".join(
260
- [
261
- "serial,asset_tag,device_type,location,status,name,role",
262
- f"1021C4,CA211,{device_type.pk},{location.pk},{status},Test-AC-01,{role}",
263
- f"1021C5,CA212,{device_type.pk},{location.pk},{status},Test-AC-02,{role}",
264
- ]
265
- )
266
- content = content.encode("utf-8-sig")
267
- filename = "test.csv"
268
- csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
269
- job_result = create_job_result_and_run_job(
270
- "nautobot.core.jobs",
271
- "ImportObjects",
272
- content_type=ContentType.objects.get_for_model(Device).pk,
273
- csv_file=csv_file.id,
274
- )
275
- self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
276
- self.assertFalse(
277
- JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
278
- )
279
- self.assertFalse(
280
- JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
281
- )
282
- device_1 = Device.objects.get(name="Test-AC-01")
283
- device_2 = Device.objects.get(name="Test-AC-02")
284
- self.assertEqual(device_1.serial, "1021C4")
285
- self.assertEqual(device_2.serial, "1021C5")
286
-
287
221
  def test_csv_import_bad_row(self):
288
222
  """A row of incorrect data should fail validation for that object but import all others successfully if `roll_back_if_error` is False."""
289
223
  csv_data = self.csv_data.split("\n")
@@ -413,3 +347,118 @@ class ImportObjectsTestCase(TransactionTestCase):
413
347
  )
414
348
 
415
349
  self.assertEqual(associations_job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
350
+
351
+
352
+ class LogsCleanupTestCase(TransactionTestCase):
353
+ """
354
+ Test the LogsCleanup system job.
355
+ """
356
+
357
+ databases = ("default", "job_logs")
358
+
359
+ def setUp(self):
360
+ super().setUp()
361
+ # Recall that TransactionTestCase truncates the DB after each test case...
362
+ cache.delete("nautobot.extras.utils.change_logged_models_queryset")
363
+ if ObjectChange.objects.count() < 2:
364
+ ObjectChangeFactory.create_batch(40)
365
+ if JobResult.objects.count() < 2:
366
+ JobResultFactory.create_batch(20)
367
+
368
+ def test_cleanup_without_permission(self):
369
+ """Job should enforce user permissions on the content-types being deleted."""
370
+ job_result_count = JobResult.objects.count()
371
+ job_log_entry_count = JobLogEntry.objects.count()
372
+ object_change_count = ObjectChange.objects.count()
373
+
374
+ job_result = create_job_result_and_run_job(
375
+ "nautobot.core.jobs.cleanup",
376
+ "LogsCleanup",
377
+ username=self.user.username,
378
+ cleanup_types=[CleanupTypes.JOB_RESULT],
379
+ max_age=0,
380
+ )
381
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
382
+ log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
383
+ self.assertEqual(log_error.message, f'User "{self.user}" does not have permission to delete JobResult records')
384
+ self.assertEqual(JobResult.objects.count(), job_result_count + 1)
385
+ self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
386
+ self.assertEqual(ObjectChange.objects.count(), object_change_count)
387
+
388
+ job_result = create_job_result_and_run_job(
389
+ "nautobot.core.jobs.cleanup",
390
+ "LogsCleanup",
391
+ username=self.user.username,
392
+ cleanup_types=[CleanupTypes.OBJECT_CHANGE],
393
+ max_age=0,
394
+ )
395
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
396
+ log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
397
+ self.assertEqual(
398
+ log_error.message, f'User "{self.user}" does not have permission to delete ObjectChange records'
399
+ )
400
+ self.assertEqual(JobResult.objects.count(), job_result_count + 2)
401
+ self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
402
+ self.assertEqual(ObjectChange.objects.count(), object_change_count)
403
+
404
+ def test_cleanup_with_constrained_permission(self):
405
+ """Job should only allow the user to cleanup records they have permission to delete."""
406
+ job_result_1 = JobResult.objects.last()
407
+ job_result_2 = JobResult.objects.first()
408
+ self.assertNotEqual(job_result_1.pk, job_result_2.pk)
409
+ object_change_1 = ObjectChange.objects.first()
410
+ object_change_2 = ObjectChange.objects.last()
411
+ self.assertNotEqual(object_change_1.pk, object_change_2.pk)
412
+ obj_perm = ObjectPermission(
413
+ name="Test permission",
414
+ constraints={"pk__in": [str(job_result_1.pk), str(object_change_1.pk)]},
415
+ actions=["delete"],
416
+ )
417
+ obj_perm.save()
418
+ obj_perm.users.add(self.user)
419
+ obj_perm.object_types.add(ContentType.objects.get_for_model(JobResult))
420
+ obj_perm.object_types.add(ContentType.objects.get_for_model(ObjectChange))
421
+ job_result = create_job_result_and_run_job(
422
+ "nautobot.core.jobs.cleanup",
423
+ "LogsCleanup",
424
+ username=self.user.username,
425
+ cleanup_types=[CleanupTypes.JOB_RESULT, CleanupTypes.OBJECT_CHANGE],
426
+ max_age=0,
427
+ )
428
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
429
+ self.assertEqual(job_result.result["extras.JobResult"], 1)
430
+ self.assertEqual(job_result.result["extras.ObjectChange"], 1)
431
+ with self.assertRaises(JobResult.DoesNotExist):
432
+ JobResult.objects.get(pk=job_result_1.pk)
433
+ JobResult.objects.get(pk=job_result_2.pk)
434
+ with self.assertRaises(ObjectChange.DoesNotExist):
435
+ ObjectChange.objects.get(pk=object_change_1.pk)
436
+ ObjectChange.objects.get(pk=object_change_2.pk)
437
+
438
+ def test_cleanup_job_results(self):
439
+ """With unconstrained permissions, all JobResults before the cutoff should be deleted."""
440
+ cutoff = timezone.now() - timedelta(days=60)
441
+ create_job_result_and_run_job(
442
+ "nautobot.core.jobs.cleanup",
443
+ "LogsCleanup",
444
+ cleanup_types=[CleanupTypes.JOB_RESULT],
445
+ max_age=60,
446
+ )
447
+ self.assertFalse(JobResult.objects.filter(date_done__lt=cutoff).exists())
448
+ self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
449
+ self.assertTrue(ObjectChange.objects.filter(time__lt=cutoff).exists())
450
+ self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
451
+
452
+ def test_cleanup_object_changes(self):
453
+ """With unconstrained permissions, all ObjectChanges before the cutoff should be deleted."""
454
+ cutoff = timezone.now() - timedelta(days=60)
455
+ create_job_result_and_run_job(
456
+ "nautobot.core.jobs.cleanup",
457
+ "LogsCleanup",
458
+ cleanup_types=[CleanupTypes.OBJECT_CHANGE],
459
+ max_age=60,
460
+ )
461
+ self.assertTrue(JobResult.objects.filter(date_done__lt=cutoff).exists())
462
+ self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
463
+ self.assertFalse(ObjectChange.objects.filter(time__lt=cutoff).exists())
464
+ self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
@@ -7,9 +7,11 @@ from nautobot.tenancy.tables import TenantGroupTable
7
7
 
8
8
 
9
9
  class TableTestCase(TestCase):
10
+ maxDiff = None
11
+
10
12
  def _validate_sorted_tree_queryset_same_with_table_queryset(self, queryset, table_class, field_name):
11
13
  with self.subTest(f"Assert sorting {table_class.__name__} on '{field_name}'"):
12
- table = table_class(queryset, order_by=field_name)
14
+ table = table_class(queryset.with_tree_fields(), order_by=field_name)
13
15
  table_queryset_data = table.data.data.values_list("pk", flat=True)
14
16
  sorted_queryset = queryset.with_tree_fields().extra(order_by=[field_name]).values_list("pk", flat=True)
15
17
  self.assertEqual(list(table_queryset_data), list(sorted_queryset))
@@ -22,14 +22,14 @@ class NautobotTemplatetagsHelperTest(TestCase):
22
22
  location.description = ""
23
23
  location.save()
24
24
  self.assertEqual(
25
- helpers.hyperlinked_object(location), f'<a href="/dcim/locations/{location.pk}/">{location.name}</a>'
25
+ helpers.hyperlinked_object(location), f'<a href="/dcim/locations/{location.pk}/">{location.display}</a>'
26
26
  )
27
27
  # An object with get_absolute_url and a description gives a titled hyperlink
28
28
  location.description = "An important location"
29
29
  location.save()
30
30
  self.assertEqual(
31
31
  helpers.hyperlinked_object(location),
32
- f'<a href="/dcim/locations/{location.pk}/" title="An important location">{location.name}</a>',
32
+ f'<a href="/dcim/locations/{location.pk}/" title="An important location">{location.display}</a>',
33
33
  )
34
34
  # Optionally you can request a field other than the object's display string
35
35
  self.assertEqual(
@@ -114,6 +114,13 @@ class NautobotTemplatetagsHelperTest(TestCase):
114
114
  )
115
115
  self.assertEqual('"I am UTF-8! 😀"', helpers.render_json("I am UTF-8! 😀", False))
116
116
 
117
+ def test_render_uptime(self):
118
+ self.assertEqual(helpers.render_uptime(1024768), "11 days 20 hours 39 minutes")
119
+ self.assertEqual(helpers.render_uptime(""), helpers.placeholder(""))
120
+ self.assertEqual(helpers.render_uptime("123456"), "1 day 10 hours 17 minutes")
121
+ self.assertEqual(helpers.render_uptime(0), "0 days 0 hours 0 minutes")
122
+ self.assertEqual(helpers.render_uptime("foo bar"), helpers.placeholder("foo bar"))
123
+
117
124
  def test_render_yaml(self):
118
125
  self.assertEqual(
119
126
  helpers.render_yaml({"syntax": "highlight"}), '<code class="language-yaml">syntax: highlight\n</code>'
@@ -251,7 +258,7 @@ class NautobotTemplatetagsHelperTest(TestCase):
251
258
  f'<span class="label" style="color: {fbcolor}; background-color: #{color}">{display}</span>',
252
259
  )
253
260
  # Assert when obj is None
254
- self.assertEqual(helpers.hyperlinked_object_with_color(obj=None), "")
261
+ self.assertEqual(helpers.hyperlinked_object_with_color(obj=None), '<span class="text-muted">&mdash;</span>')
255
262
 
256
263
  @override_settings(BANNER_TOP="¡Hola, mundo!")
257
264
  @override_config(example_app__SAMPLE_VARIABLE="Testing")
@@ -296,14 +303,14 @@ class NautobotTemplatetagsHelperTest(TestCase):
296
303
  location.save()
297
304
  self.assertEqual(
298
305
  helpers.hyperlinked_object_target_new_tab(location),
299
- f'<a href="/dcim/locations/{location.pk}/" target="_blank" rel="noreferrer">{location.name}</a>',
306
+ f'<a href="/dcim/locations/{location.pk}/" target="_blank" rel="noreferrer">{location.display}</a>',
300
307
  )
301
308
  # An object with get_absolute_url and a description gives a titled hyperlink
302
309
  location.description = "An important location"
303
310
  location.save()
304
311
  self.assertEqual(
305
312
  helpers.hyperlinked_object_target_new_tab(location),
306
- f'<a href="/dcim/locations/{location.pk}/" title="An important location" target="_blank" rel="noreferrer">{location.name}</a>',
313
+ f'<a href="/dcim/locations/{location.pk}/" title="An important location" target="_blank" rel="noreferrer">{location.display}</a>',
307
314
  )
308
315
  # Optionally you can request a field other than the object's display string
309
316
  self.assertEqual(
@@ -7,12 +7,12 @@ from django.contrib.contenttypes.models import ContentType
7
7
  from django.core.exceptions import ValidationError
8
8
  from django.db.models import Q
9
9
  from django.http import QueryDict
10
- from django.test import TestCase
11
10
 
12
11
  from nautobot.circuits import models as circuits_models
13
12
  from nautobot.core import exceptions, forms, settings_funcs
14
13
  from nautobot.core.api import utils as api_utils
15
14
  from nautobot.core.models import fields as core_fields, utils as models_utils, validators
15
+ from nautobot.core.testing import TestCase
16
16
  from nautobot.core.utils import data as data_utils, filtering, lookup, requests
17
17
  from nautobot.core.utils.migrations import update_object_change_ct_for_replaced_models
18
18
  from nautobot.dcim import filters as dcim_filters, forms as dcim_forms, models as dcim_models, tables
@@ -485,61 +485,61 @@ class LookupRelatedFunctionTest(TestCase):
485
485
  location_fields = ["comments", "name", "contact_email", "physical_address", "shipping_address"]
486
486
  for field_name in location_fields:
487
487
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, field_name)
488
- self.assertIs(type(form_field), forms.MultiValueCharField)
488
+ self.assertIsInstance(form_field, forms.MultiValueCharField)
489
489
 
490
490
  device_fields = ["serial", "name"]
491
491
  for field_name in device_fields:
492
492
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, field_name)
493
- self.assertIs(type(form_field), forms.MultiValueCharField)
493
+ self.assertIsInstance(form_field, forms.MultiValueCharField)
494
494
 
495
495
  with self.subTest("Test IntegerField"):
496
496
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, "asn")
497
- self.assertIs(type(form_field), django_forms.IntegerField)
497
+ self.assertIsInstance(form_field, django_forms.IntegerField)
498
498
 
499
499
  device_fields = ["vc_position", "vc_priority"]
500
500
  for field_name in device_fields:
501
501
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, field_name)
502
- self.assertIs(type(form_field), django_forms.IntegerField)
502
+ self.assertIsInstance(form_field, django_forms.IntegerField)
503
503
 
504
504
  with self.subTest("Test DynamicModelMultipleChoiceField"):
505
505
  location_fields = ["tenant", "status"]
506
506
  for field_name in location_fields:
507
507
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, field_name)
508
- self.assertIs(type(form_field), forms.DynamicModelMultipleChoiceField)
508
+ self.assertIsInstance(form_field, forms.DynamicModelMultipleChoiceField)
509
509
 
510
510
  device_fields = ["cluster", "device_type", "location"]
511
511
  for field_name in device_fields:
512
512
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, field_name)
513
- self.assertIs(type(form_field), forms.DynamicModelMultipleChoiceField)
513
+ self.assertIsInstance(form_field, forms.DynamicModelMultipleChoiceField)
514
514
 
515
515
  location_fields = ["has_circuit_terminations", "has_devices"]
516
516
  for field_name in location_fields:
517
517
  with self.subTest("Test ChoiceField", model=dcim_models.Location, field_name=field_name):
518
518
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, field_name)
519
- self.assertIs(type(form_field), django_forms.ChoiceField)
520
- self.assertIs(type(form_field.widget), forms.StaticSelect2)
519
+ self.assertIsInstance(form_field, django_forms.ChoiceField)
520
+ self.assertIsInstance(form_field.widget, forms.StaticSelect2)
521
521
 
522
522
  device_fields = ["has_console_ports", "has_interfaces", "local_config_context_data"]
523
523
  for field_name in device_fields:
524
524
  with self.subTest("Test ChoiceField", model=dcim_models.Device, field_name=field_name):
525
525
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, field_name)
526
- self.assertIs(type(form_field), django_forms.ChoiceField)
527
- self.assertIs(type(form_field.widget), forms.StaticSelect2)
526
+ self.assertIsInstance(form_field, django_forms.ChoiceField)
527
+ self.assertIsInstance(form_field.widget, forms.StaticSelect2)
528
528
 
529
529
  with self.subTest("Test MultipleChoiceField"):
530
530
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, "face")
531
- self.assertIs(type(form_field), django_forms.MultipleChoiceField)
531
+ self.assertIsInstance(form_field, django_forms.MultipleChoiceField)
532
532
 
533
533
  with self.subTest("Test DateTimePicker"):
534
534
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, "last_updated")
535
- self.assertIs(type(form_field.widget), forms.DateTimePicker)
535
+ self.assertIsInstance(form_field.widget, forms.DateTimePicker)
536
536
 
537
537
  form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, "last_updated")
538
- self.assertIs(type(form_field.widget), forms.DateTimePicker)
538
+ self.assertIsInstance(form_field.widget, forms.DateTimePicker)
539
539
 
540
540
  with self.subTest("Test DatePicker"):
541
541
  form_field = filtering.get_filterset_parameter_form_field(circuits_models.Circuit, "install_date")
542
- self.assertIs(type(form_field.widget), forms.DatePicker)
542
+ self.assertIsInstance(form_field.widget, forms.DatePicker)
543
543
 
544
544
  with self.subTest("Test Invalid parameter"):
545
545
  with self.assertRaises(exceptions.FilterSetFieldNotFound) as err:
@@ -548,16 +548,27 @@ class LookupRelatedFunctionTest(TestCase):
548
548
 
549
549
  with self.subTest("Test Content types"):
550
550
  form_field = filtering.get_filterset_parameter_form_field(extras_models.Status, "content_types")
551
- self.assertIs(type(form_field), forms.MultipleContentTypeField)
551
+ self.assertIsInstance(form_field, forms.MultipleContentTypeField)
552
552
 
553
553
  # Assert total ContentTypes generated by form_field is == total `content_types` generated by TaggableClassesQuery
554
554
  form_field = filtering.get_filterset_parameter_form_field(extras_models.Tag, "content_types")
555
- self.assertIs(type(form_field), forms.MultipleContentTypeField)
556
- self.assertEqual(form_field.queryset.count(), extras_utils.TaggableClassesQuery().as_queryset().count())
555
+ self.assertIsInstance(form_field, forms.MultipleContentTypeField)
556
+ self.assertQuerysetEqualAndNotEmpty(form_field.queryset, extras_utils.TaggableClassesQuery().as_queryset())
557
557
 
558
558
  form_field = filtering.get_filterset_parameter_form_field(extras_models.JobHook, "content_types")
559
- self.assertIs(type(form_field), forms.MultipleContentTypeField)
560
- self.assertEqual(form_field.queryset.count(), extras_utils.ChangeLoggedModelsQuery().as_queryset().count())
559
+ self.assertIsInstance(form_field, forms.MultipleContentTypeField)
560
+ self.assertQuerysetEqualAndNotEmpty(
561
+ form_field.queryset, extras_utils.ChangeLoggedModelsQuery().as_queryset()
562
+ )
563
+
564
+ with self.subTest("Test prefers_id"):
565
+ form_field = filtering.get_filterset_parameter_form_field(dcim_models.Device, "location")
566
+ self.assertEqual("id", form_field.to_field_name)
567
+ form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, "vlans")
568
+ self.assertEqual("id", form_field.to_field_name)
569
+ # Test prefers_id=False (default)
570
+ form_field = filtering.get_filterset_parameter_form_field(dcim_models.Location, "racks")
571
+ self.assertEqual("name", form_field.to_field_name)
561
572
 
562
573
  def test_convert_querydict_to_factory_formset_dict(self):
563
574
  location_filter_set = dcim_filters.LocationFilterSet()
@@ -480,9 +480,9 @@ class ErrorPagesTestCase(TestCase):
480
480
  def test_500_default_support_message(self, mock_get):
481
481
  """Nautobot's custom 500 page should be used and should include a default support message."""
482
482
  url = reverse("home")
483
- with self.assertTemplateUsed("500.html"):
484
- self.client.raise_request_exception = False
485
- response = self.client.get(url)
483
+ self.client.raise_request_exception = False
484
+ response = self.client.get(url)
485
+ self.assertTemplateUsed(response, "500.html")
486
486
  self.assertContains(response, "Network to Code", status_code=500)
487
487
  response_content = response.content.decode(response.charset)
488
488
  self.assertInHTML(
@@ -497,9 +497,9 @@ class ErrorPagesTestCase(TestCase):
497
497
  def test_500_custom_support_message(self, mock_get):
498
498
  """Nautobot's custom 500 page should be used and should include a custom support message if defined."""
499
499
  url = reverse("home")
500
- with self.assertTemplateUsed("500.html"):
501
- self.client.raise_request_exception = False
502
- response = self.client.get(url)
500
+ self.client.raise_request_exception = False
501
+ response = self.client.get(url)
502
+ self.assertTemplateUsed(response, "500.html")
503
503
  self.assertNotContains(response, "Network to Code", status_code=500)
504
504
  response_content = response.content.decode(response.charset)
505
505
  self.assertInHTML("Hello world!", response_content)
nautobot/core/urls.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from django.conf import settings
2
- from django.conf.urls import include, url
3
- from django.urls import path
2
+ from django.urls import include, path
4
3
  from django.views.generic import TemplateView
5
4
  from django.views.static import serve
6
5
 
@@ -13,6 +12,7 @@ from nautobot.core.views import (
13
12
  SearchView,
14
13
  StaticMediaFailureView,
15
14
  ThemePreviewView,
15
+ WorkerStatusView,
16
16
  )
17
17
  from nautobot.extras.plugins.urls import (
18
18
  apps_patterns,
@@ -32,11 +32,14 @@ urlpatterns = [
32
32
  path("logout/", LogoutView.as_view(), name="logout"),
33
33
  # Apps
34
34
  path("circuits/", include("nautobot.circuits.urls")),
35
+ path("cloud/", include("nautobot.cloud.urls")),
35
36
  path("dcim/", include("nautobot.dcim.urls")),
36
37
  path("extras/", include("nautobot.extras.urls")),
37
38
  path("ipam/", include("nautobot.ipam.urls")),
38
39
  path("tenancy/", include("nautobot.tenancy.urls")),
40
+ # TODO: deprecate this url and use users
39
41
  path("user/", include("nautobot.users.urls")),
42
+ path("users/", include("nautobot.users.urls", "users")),
40
43
  path("virtualization/", include("nautobot.virtualization.urls")),
41
44
  # API
42
45
  path("api/", include("nautobot.core.api.urls")),
@@ -57,12 +60,14 @@ urlpatterns = [
57
60
  # django-health-check
58
61
  path(r"health/", include("health_check.urls")),
59
62
  # FileProxy attachments download/get URLs used in admin views only
60
- url(
63
+ path(
61
64
  "files/download/",
62
65
  get_file_with_authorization,
63
66
  {"add_attachment_headers": True},
64
67
  name="db_file_storage.download_file",
65
68
  ),
69
+ # Celery worker status page
70
+ path("worker-status/", WorkerStatusView.as_view(), name="worker_status"),
66
71
  # Templated css file
67
72
  path(
68
73
  "template.css", TemplateView.as_view(template_name="template.css", content_type="text/css"), name="template_css"
@@ -45,3 +45,32 @@ def class_deprecated(message):
45
45
  def class_deprecated_in_favor_of(replacement_class):
46
46
  """Decorator to mark a class as deprecated and suggest a replacement class if it is subclassed from."""
47
47
  return class_deprecated(f"please migrate your code to inherit from class {replacement_class.__name__} instead")
48
+
49
+
50
+ def method_deprecated(message):
51
+ """Decorator to mark a method as deprecated with a custom message about what to call instead."""
52
+
53
+ def decorate(method):
54
+ def decorated_method(*args, **kwargs):
55
+ warnings.warn(
56
+ f"Method `{method.__name__}` is deprecated, and will be removed in a future Nautobot release. "
57
+ f"{message}",
58
+ DeprecationWarning,
59
+ stacklevel=2,
60
+ )
61
+ if LOG_DEPRECATION_WARNINGS:
62
+ logger.warning(
63
+ f"Method `{method.__name__}` is deprecated, and will be removed in a future Nautobot release. "
64
+ f"{message}",
65
+ stacklevel=2,
66
+ )
67
+ return method(*args, **kwargs)
68
+
69
+ return decorated_method
70
+
71
+ return decorate
72
+
73
+
74
+ def method_deprecated_in_favor_of(replacement_method):
75
+ """Decorator to mark a method as deprecated and suggest a replacement method instead."""
76
+ return method_deprecated(f"Please migrate your code to call `{replacement_method.__name__}` instead.")
@@ -96,8 +96,8 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
96
96
  StaticSelect2Multiple,
97
97
  )
98
98
  from nautobot.dcim.models import Device
99
- from nautobot.extras.filters import ContentTypeMultipleChoiceFilter, CustomFieldFilterMixin, StatusFilter
100
- from nautobot.extras.models import ConfigContext, Role, Status, Tag
99
+ from nautobot.extras.filters import ContentTypeMultipleChoiceFilter, CustomFieldFilterMixin
100
+ from nautobot.extras.models import ConfigContext
101
101
  from nautobot.extras.utils import ChangeLoggedModelsQuery, RoleModelsQuery, TaggableClassesQuery
102
102
  from nautobot.virtualization.models import VirtualMachine
103
103
 
@@ -114,17 +114,18 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
114
114
  elif isinstance(field, NumberFilter):
115
115
  form_field = forms.IntegerField()
116
116
  elif isinstance(field, ModelMultipleChoiceFilter):
117
- related_model = Status if isinstance(field, StatusFilter) else field.extra["queryset"].model
117
+ if getattr(field, "prefers_id", False):
118
+ to_field_name = "id"
119
+ else:
120
+ to_field_name = field.extra.get("to_field_name", "id")
118
121
  form_attr = {
119
- "queryset": related_model.objects.all(),
120
- "to_field_name": field.extra.get("to_field_name", "id"),
122
+ "queryset": field.extra["queryset"],
123
+ "to_field_name": to_field_name,
124
+ "query_params": field.extra.get("query_params", {}),
121
125
  }
122
126
  # ConfigContext requires content_type set to Device and VirtualMachine
123
127
  if model == ConfigContext:
124
128
  form_attr["query_params"] = {"content_types": [Device._meta.label_lower, VirtualMachine._meta.label_lower]}
125
- # Status and Tag api requires content_type, to limit result to only related content_types
126
- elif related_model in [Role, Status, Tag]:
127
- form_attr["query_params"] = {"content_types": model._meta.label_lower}
128
129
 
129
130
  form_field = DynamicModelMultipleChoiceField(**form_attr)
130
131
  elif isinstance(
@@ -136,7 +137,9 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
136
137
  plural_name = slugify_dashes_to_underscores(model._meta.verbose_name_plural)
137
138
  # Cable-connectable models use "cable_terminations", not "cables", as the feature name
138
139
  if plural_name == "cables":
139
- plural_name == "cable_terminations"
140
+ plural_name = "cable_terminations"
141
+ elif plural_name == "metadata_types":
142
+ plural_name = "metadata"
140
143
  try:
141
144
  form_field = MultipleContentTypeField(choices_as_strings=True, feature=plural_name)
142
145
  except KeyError:
@@ -208,6 +208,40 @@ def get_view_for_model(model, view_type=""):
208
208
  return result
209
209
 
210
210
 
211
+ def get_model_for_view_name(view_name):
212
+ """
213
+ Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
214
+ If the app_label or model_name contained by the given view_name is invalid, this will return `None`.
215
+ """
216
+ app_label, model_name = view_name.split(":") # dcim, device_list
217
+ model_name = model_name.split("_")[0] # device
218
+
219
+ try:
220
+ model = apps.get_model(app_label=app_label, model_name=model_name)
221
+ return model
222
+ except LookupError:
223
+ return None
224
+
225
+
226
+ def get_table_class_string_from_view_name(view_name):
227
+ """Return the name of the TableClass name associated with the view_name
228
+
229
+ e.g. returns `LocationTable` for view_name `dcim:location_list`
230
+
231
+ Args:
232
+ view_name (String): The name of the view e.g. dcim:location_list, circuits:circuit_list
233
+
234
+ Returns:
235
+ table_class_name (String): The name of the model table class or None e.g. LocationTable, CircuitTable
236
+ """
237
+ model = get_model_for_view_name(view_name)
238
+ if model:
239
+ table_class = get_table_for_model(model)
240
+ if table_class:
241
+ return table_class.__name__
242
+ return None
243
+
244
+
211
245
  def get_created_and_last_updated_usernames_for_model(instance):
212
246
  """
213
247
  Args:
@@ -224,8 +258,9 @@ def get_created_and_last_updated_usernames_for_model(instance):
224
258
  created_by = None
225
259
  last_updated_by = None
226
260
  try:
227
- created_by_record = object_change_records.get(action=ObjectChangeActionChoices.ACTION_CREATE)
228
- created_by = created_by_record.user_name
261
+ created_by_record = object_change_records.filter(action=ObjectChangeActionChoices.ACTION_CREATE).first()
262
+ if created_by_record is not None:
263
+ created_by = created_by_record.user_name
229
264
  except ObjectChange.DoesNotExist:
230
265
  pass
231
266
 
@@ -65,7 +65,10 @@ def convert_querydict_to_factory_formset_acceptable_querydict(request_querydict,
65
65
  # the filterset can handle the QueryDict conversion and we can just pass the QueryDict to the filterset
66
66
  # then use the FilterSet to de-dupe the field names
67
67
  lookup_field = re.sub(r"__\w+", "", filter_field_name)
68
- lookup_value = request_querydict.getlist(filter_field_name)
68
+ if isinstance(request_querydict, QueryDict):
69
+ lookup_value = request_querydict.getlist(filter_field_name)
70
+ else:
71
+ lookup_value = request_querydict.get(filter_field_name)
69
72
 
70
73
  query_dict.setlistdefault(lookup_field_placeholder % num, [lookup_field])
71
74
  query_dict.setlistdefault(lookup_type_placeholder % num, [filter_field_name])