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
nautobot/core/filters.py CHANGED
@@ -1,4 +1,3 @@
1
- from collections import OrderedDict
2
1
  from copy import deepcopy
3
2
  import logging
4
3
  import uuid
@@ -7,9 +6,11 @@ from django import forms as django_forms
7
6
  from django.conf import settings
8
7
  from django.db import models
9
8
  from django.forms.utils import ErrorDict, ErrorList
9
+ from django.utils.encoding import force_str
10
+ from django.utils.text import capfirst
10
11
  import django_filters
11
12
  from django_filters.constants import EMPTY_VALUES
12
- from django_filters.utils import get_model_field, resolve_field
13
+ from django_filters.utils import get_model_field, label_for_filter, resolve_field, verbose_lookup_expr
13
14
  from drf_spectacular.types import OpenApiTypes
14
15
  from drf_spectacular.utils import extend_schema_field
15
16
 
@@ -123,8 +124,8 @@ class MultiValueUUIDFilter(django_filters.UUIDFilter, django_filters.MultipleCho
123
124
 
124
125
  class RelatedMembershipBooleanFilter(django_filters.BooleanFilter):
125
126
  """
126
- BooleanFilter for related objects that will explicitly perform `exclude=True` and `isnull`
127
- lookups. The `field_name` argument is required and must be set to the related field on the
127
+ BooleanFilter for related objects that will explicitly perform `isnull` lookups.
128
+ The `field_name` argument is required and must be set to the related field on the
128
129
  model.
129
130
 
130
131
  This should be used instead of a default `BooleanFilter` paired `method=`
@@ -132,15 +133,33 @@ class RelatedMembershipBooleanFilter(django_filters.BooleanFilter):
132
133
 
133
134
  Example:
134
135
 
135
- has_interfaces = RelatedMembershipBooleanFilter(
136
- field_name="interfaces",
137
- label="Has interfaces",
136
+ has_modules = RelatedMembershipBooleanFilter(
137
+ field_name="module_bays__installed_module",
138
+ label="Has modules",
138
139
  )
140
+
141
+ This would generate a filter that returns instances that have at least one module
142
+ bay with an installed module. The `has_modules=False` filter would exclude instances
143
+ with at least one module bay with an installed module.
144
+
145
+ Set `exclude=True` to reverse the behavior of the filter. This __may__ be useful
146
+ for filtering on null directly related fields but this filter is not smart enough
147
+ to differentiate between `fieldA__fieldB__isnull` and `fieldA__isnull` so it's not
148
+ suitable for cases like the `has_empty_module_bays` filter where an instance may
149
+ not have any module bays.
150
+
151
+ See the below table for more information:
152
+
153
+ | value | exclude | Result
154
+ |-------------|------------------|-------
155
+ | True | False (default) | Return instances with at least one non-null match -- qs.filter(field_name__isnull=False)
156
+ | False | False (default) | Exclude instances with at least one non-null match -- qs.exclude(field_name__isnull=False)
157
+ | True | True | Return instances with at least one null match -- qs.filter(field_name__isnull=True)
158
+ | False | True | Exclude instances with at least one null match -- qs.exclude(field_name__isnull=True)
159
+
139
160
  """
140
161
 
141
- def __init__(
142
- self, field_name=None, lookup_expr="isnull", *, label=None, method=None, distinct=False, exclude=True, **kwargs
143
- ):
162
+ def __init__(self, field_name=None, lookup_expr="isnull", *, label=None, method=None, distinct=True, **kwargs):
144
163
  if field_name is None:
145
164
  raise ValueError(f"Field name is required for {self.__class__.__name__}")
146
165
 
@@ -150,11 +169,23 @@ class RelatedMembershipBooleanFilter(django_filters.BooleanFilter):
150
169
  label=label,
151
170
  method=method,
152
171
  distinct=distinct,
153
- exclude=exclude,
154
172
  widget=forms.StaticSelect2(choices=forms.BOOLEAN_CHOICES),
155
173
  **kwargs,
156
174
  )
157
175
 
176
+ def filter(self, qs, value):
177
+ if value in EMPTY_VALUES:
178
+ return qs
179
+ if self.distinct:
180
+ qs = qs.distinct()
181
+ lookup = f"{self.field_name}__{self.lookup_expr}"
182
+ if bool(value):
183
+ # if self.exclude=False, return instances with field populated
184
+ return qs.filter(**{lookup: self.exclude})
185
+ else:
186
+ # if self.exclude=False, exclude instances with field populated
187
+ return qs.exclude(**{lookup: self.exclude})
188
+
158
189
 
159
190
  class NumericArrayFilter(django_filters.NumberFilter):
160
191
  """
@@ -401,12 +432,22 @@ class NaturalKeyOrPKMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilte
401
432
  Filter that supports filtering on values matching the `pk` field and another
402
433
  field of a foreign-key related object. The desired field is set using the `to_field_name`
403
434
  keyword argument on filter initialization (defaults to `name`).
435
+
436
+ NOTE that the `to_field_name` field does not have to be a "true" natural key (ie. unique), it
437
+ was just the best name we could come up with for this filter
438
+
404
439
  """
405
440
 
406
441
  field_class = forms.MultiMatchModelMultipleChoiceField
407
442
 
408
- def __init__(self, *args, **kwargs):
443
+ def __init__(self, *args, prefers_id=False, **kwargs):
444
+ """Initialize the NaturalKeyOrPKMultipleChoiceFilter.
445
+
446
+ Args:
447
+ prefers_id (bool, optional): Prefer PK (ID) over the 'to_field_name'. Defaults to False.
448
+ """
409
449
  self.natural_key = kwargs.setdefault("to_field_name", "name")
450
+ self.prefers_id = prefers_id
410
451
  super().__init__(*args, **kwargs)
411
452
 
412
453
  def get_filter_predicate(self, v):
@@ -681,6 +722,18 @@ class BaseFilterSet(django_filters.FilterSet):
681
722
  # Of course setting the negation of the existing filter's exclude attribute handles both cases
682
723
  new_filter.exclude = not filter_field.exclude
683
724
 
725
+ # If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
726
+ # so we have to do it.
727
+ if filter_field.label and filter_field.label != label_for_filter(
728
+ cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
729
+ ):
730
+ # Lightly adjusted from label_for_filter() implementation:
731
+ verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
732
+ if isinstance(lookup_expr, str):
733
+ verbose_expression.append(verbose_lookup_expr(lookup_expr))
734
+ verbose_expression = [force_str(part) for part in verbose_expression if part]
735
+ new_filter.label = capfirst(" ".join(verbose_expression))
736
+
684
737
  magic_filters[new_filter_name] = new_filter
685
738
 
686
739
  return magic_filters
@@ -711,8 +764,8 @@ class BaseFilterSet(django_filters.FilterSet):
711
764
  def get_fields(cls):
712
765
  fields = super().get_fields()
713
766
  if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude):
714
- # Add "id" as the first key in the `fields` OrderedDict
715
- fields = OrderedDict(id=[django_filters.conf.settings.DEFAULT_LOOKUP_EXPR], **fields)
767
+ # Add "id" as the first key in the `fields` dict
768
+ fields = {"id": [django_filters.conf.settings.DEFAULT_LOOKUP_EXPR], **fields}
716
769
  return fields
717
770
 
718
771
  @classmethod
@@ -727,8 +780,51 @@ class BaseFilterSet(django_filters.FilterSet):
727
780
  if filter_name.startswith("_"):
728
781
  del filters[filter_name]
729
782
 
783
+ if getattr(cls._meta.model, "is_contact_associable_model", False):
784
+ # Add "contacts" and "teams" filters
785
+ from nautobot.extras.models import Contact, Team
786
+
787
+ if "contacts" not in filters:
788
+ filters["contacts"] = NaturalKeyOrPKMultipleChoiceFilter(
789
+ queryset=Contact.objects.all(),
790
+ field_name="associated_contacts__contact",
791
+ to_field_name="name",
792
+ label="Contacts (name or ID)",
793
+ )
794
+
795
+ if "teams" not in filters:
796
+ filters["teams"] = NaturalKeyOrPKMultipleChoiceFilter(
797
+ queryset=Team.objects.all(),
798
+ field_name="associated_contacts__team",
799
+ to_field_name="name",
800
+ label="Teams (name or ID)",
801
+ )
802
+
803
+ if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False):
804
+ if not hasattr(cls._meta.model, "static_group_association_set"):
805
+ logger.warning(
806
+ "Model %s has 'is_dynamic_group_associable_model = True' but lacks "
807
+ "a 'static_group_association_set' attribute. Perhaps this is due to it inheriting from "
808
+ "the deprecated DynamicGroupMixin class instead of the preferred DynamicGroupsModelMixin?",
809
+ cls._meta.model,
810
+ )
811
+ else:
812
+ # Add "dynamic_groups" field as the last key
813
+ from nautobot.extras.models import DynamicGroup
814
+
815
+ filters["dynamic_groups"] = NaturalKeyOrPKMultipleChoiceFilter(
816
+ queryset=DynamicGroup.objects.all(),
817
+ field_name="static_group_association_set__dynamic_group",
818
+ to_field_name="name",
819
+ query_params={"content_type": cls._meta.model._meta.label_lower},
820
+ label="Dynamic groups (name or ID)",
821
+ )
822
+
730
823
  # django-filters has no concept of "abstract" filtersets, so we have to fake it
731
824
  if cls._meta.model is not None:
825
+ if "tags" in filters and isinstance(filters["tags"], TagFilter):
826
+ filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]}
827
+
732
828
  new_filters = {}
733
829
  for existing_filter_name, existing_filter in filters.items():
734
830
  new_filters.update(
@@ -228,6 +228,10 @@ class MultipleContentTypeField(django_forms.ModelMultipleChoiceField):
228
228
 
229
229
  if "queryset" not in kwargs:
230
230
  if feature is not None:
231
+ from nautobot.extras.registry import registry
232
+
233
+ if feature not in registry["model_features"]:
234
+ raise KeyError
231
235
  kwargs["queryset"] = ContentType.objects.filter(
232
236
  extras_utils.FeatureQuery(feature).get_query()
233
237
  ).order_by("app_label", "model")
@@ -453,6 +457,7 @@ class DynamicModelChoiceMixin:
453
457
 
454
458
  filter = django_filters.ModelChoiceFilter # 2.0 TODO(Glenn): can we rename this? pylint: disable=redefined-builtin
455
459
  widget = widgets.APISelect
460
+ iterator = widgets.MinimalModelChoiceIterator
456
461
 
457
462
  def __init__(
458
463
  self,
@@ -475,6 +480,7 @@ class DynamicModelChoiceMixin:
475
480
  # to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
476
481
  # by widget_attrs()
477
482
  self.to_field_name = kwargs.get("to_field_name")
483
+ self.data_queryset = kwargs.get("queryset") # may be updated in get_bound_field()
478
484
 
479
485
  super().__init__(*args, **kwargs)
480
486
 
@@ -539,12 +545,12 @@ class DynamicModelChoiceMixin:
539
545
  field_name = getattr(self, "to_field_name") or "pk"
540
546
  filter_ = self.filter(field_name=field_name)
541
547
  try:
542
- self.queryset = filter_.filter(self.queryset, data)
543
- except (TypeError, ValidationError):
548
+ self.data_queryset = filter_.filter(self.queryset, data)
549
+ except (TypeError, ValueError, ValidationError):
544
550
  # Catch any error caused by invalid initial data passed from the user
545
- self.queryset = self.queryset.none()
551
+ self.data_queryset = self.queryset.none()
546
552
  else:
547
- self.queryset = self.queryset.none()
553
+ self.data_queryset = self.queryset.none()
548
554
 
549
555
  # Set the data URL on the APISelect widget (if not already set)
550
556
  widget = bound_field.field.widget
@@ -59,6 +59,13 @@ class AddressFieldMixin(forms.ModelForm):
59
59
  class BootstrapMixin(forms.BaseForm):
60
60
  """
61
61
  Add the base Bootstrap CSS classes to form elements.
62
+
63
+ Note that this only applies to form fields that are:
64
+
65
+ 1. statically defined on the form class at declaration time, or
66
+ 2. dynamically added to the form at init time by a class **later in the MRO than this mixin**.
67
+
68
+ If a class earlier in the MRO adds its own fields, it will have to ensure that the widgets are correctly configured.
62
69
  """
63
70
 
64
71
  def __init__(self, *args, **kwargs):
@@ -73,8 +80,9 @@ class BootstrapMixin(forms.BaseForm):
73
80
 
74
81
  for field in self.fields.values():
75
82
  if field.widget.__class__ not in exempt_widgets:
76
- css = field.widget.attrs.get("class", "")
77
- field.widget.attrs["class"] = " ".join([css, "form-control"]).strip()
83
+ css_classes = field.widget.attrs.get("class", "")
84
+ if "form-control" not in css_classes:
85
+ field.widget.attrs["class"] = " ".join([css_classes, "form-control"]).strip()
78
86
  if field.required and not isinstance(field.widget, forms.FileInput):
79
87
  field.widget.attrs["required"] = "required"
80
88
  if "placeholder" not in field.widget.attrs:
@@ -121,7 +129,7 @@ class BulkRenameForm(forms.Form):
121
129
  """
122
130
 
123
131
  find = forms.CharField()
124
- replace = forms.CharField()
132
+ replace = forms.CharField(required=False, strip=False)
125
133
  use_regex = forms.BooleanField(required=False, initial=True, label="Use regular expressions")
126
134
 
127
135
  def clean(self):
@@ -116,6 +116,23 @@ class ContentTypeSelect(StaticSelect2):
116
116
  option_template_name = "widgets/select_contenttype.html"
117
117
 
118
118
 
119
+ class MinimalModelChoiceIterator(ModelChoiceIterator):
120
+ """
121
+ Helper class for APISelect and APISelectMultiple.
122
+
123
+ Allows the widget to keep a full `queryset` for data validation, but, for performance reasons, returns a minimal
124
+ subset of choices at render time derived from the widget's `data_queryset`.
125
+ """
126
+
127
+ @property
128
+ def queryset(self):
129
+ return self.field.data_queryset
130
+
131
+ @queryset.setter
132
+ def queryset(self, value):
133
+ return self.field.data_queryset
134
+
135
+
119
136
  class APISelect(SelectWithDisabled):
120
137
  """
121
138
  A select widget populated via an API call
@@ -173,7 +190,7 @@ class APISelect(SelectWithDisabled):
173
190
  and isinstance(self.choices, Iterable)
174
191
  ):
175
192
 
176
- class ModelChoiceIteratorWithNullOption(ModelChoiceIterator):
193
+ class ModelChoiceIteratorWithNullOption(MinimalModelChoiceIterator):
177
194
  def __init__(self, *args, **kwargs):
178
195
  self.null_options = kwargs.pop("null_option", None)
179
196
  super().__init__(*args, **kwargs)
@@ -64,8 +64,8 @@ def generate_filter_resolver(schema_type, resolver_name, field_name):
64
64
  """
65
65
  filterset_class = schema_type._meta.filterset_class
66
66
 
67
- def resolve_filter(self, *args, **kwargs):
68
- if not filterset_class:
67
+ def resolve_filter(self, info, **kwargs):
68
+ if not filterset_class or not kwargs:
69
69
  return getattr(self, field_name).all()
70
70
 
71
71
  # Inverse of substitution logic from get_filtering_args_from_filterset() - transform "_type" back to "type"
@@ -4,6 +4,7 @@ from collections import OrderedDict
4
4
  import logging
5
5
 
6
6
  from django.conf import settings
7
+ from django.contrib.contenttypes.fields import GenericRelation
7
8
  from django.contrib.contenttypes.models import ContentType
8
9
  from django.core.validators import ValidationError
9
10
  from django.db.models import ManyToManyField
@@ -42,7 +43,7 @@ from nautobot.dcim.graphql.types import (
42
43
  RearPortType,
43
44
  )
44
45
  from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipSideChoices
45
- from nautobot.extras.graphql.types import DynamicGroupType, TagType
46
+ from nautobot.extras.graphql.types import ContactAssociationType, DynamicGroupType, TagType
46
47
  from nautobot.extras.models import ComputedField, CustomField, Relationship
47
48
  from nautobot.extras.registry import registry
48
49
  from nautobot.extras.utils import check_if_key_is_graphql_safe
@@ -68,8 +69,9 @@ registry["graphql_types"]["dcim.powerport"] = PowerPortType
68
69
  registry["graphql_types"]["dcim.rack"] = RackType
69
70
  registry["graphql_types"]["dcim.rearport"] = RearPortType
70
71
  registry["graphql_types"]["dcim.location"] = LocationType
71
- registry["graphql_types"]["extras.tag"] = TagType
72
+ registry["graphql_types"]["extras.contactassociation"] = ContactAssociationType
72
73
  registry["graphql_types"]["extras.dynamicgroup"] = DynamicGroupType
74
+ registry["graphql_types"]["extras.tag"] = TagType
73
75
  registry["graphql_types"]["ipam.ipaddress"] = IPAddressType
74
76
  registry["graphql_types"]["ipam.prefix"] = PrefixType
75
77
  registry["graphql_types"]["ipam.vlan"] = VLANType
@@ -127,6 +129,11 @@ def extend_schema_type(schema_type):
127
129
  #
128
130
  schema_type = extend_schema_type_config_context(schema_type, model)
129
131
 
132
+ #
133
+ # Global features (contacts, teams, dynamic groups)
134
+ #
135
+ schema_type = extend_schema_type_global_features(schema_type, model)
136
+
130
137
  #
131
138
  # Relationships
132
139
  #
@@ -199,8 +206,7 @@ def extend_schema_type_filter(schema_type, model):
199
206
  (DjangoObjectType): The extended schema_type object
200
207
  """
201
208
  for field in model._meta.get_fields():
202
- # Check whether attribute is a ManyToOne or ManyToMany field
203
- if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel)):
209
+ if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel, GenericRelation)):
204
210
  continue
205
211
  # OneToOneRel is a subclass of ManyToOneRel, but we don't want to treat it as a list
206
212
  if isinstance(field, OneToOneRel):
@@ -366,14 +372,29 @@ def extend_schema_type_config_context(schema_type, model):
366
372
  return schema_type
367
373
 
368
374
 
375
+ def extend_schema_type_global_features(schema_type, model):
376
+ """
377
+ Extend schema_type object to have attributes and resolvers for global features (dynamic groups, etc.).
378
+ """
379
+ # associated_contacts and associated_object_metadata are handled elsewhere by extend_schema_type_filter()
380
+ if getattr(model, "is_dynamic_group_associable_model", False):
381
+
382
+ def resolve_dynamic_groups(self, args):
383
+ return self.dynamic_groups
384
+
385
+ setattr(schema_type, "resolve_dynamic_groups", resolve_dynamic_groups)
386
+ schema_type._meta.fields["dynamic_groups"] = graphene.Field.mounted(graphene.List(DynamicGroupType))
387
+
388
+ return schema_type
389
+
390
+
369
391
  def extend_schema_type_relationships(schema_type, model):
370
392
  """Extend the schema type with attributes and resolvers corresponding
371
393
  to the relationships associated with this model."""
372
394
 
373
- ct = ContentType.objects.get_for_model(model)
374
395
  relationships_by_side = {
375
- "source": Relationship.objects.filter(source_type=ct),
376
- "destination": Relationship.objects.filter(destination_type=ct),
396
+ "source": Relationship.objects.get_for_model_source(model),
397
+ "destination": Relationship.objects.get_for_model_destination(model),
377
398
  }
378
399
 
379
400
  prefix = ""
@@ -1,3 +1,4 @@
1
+ import codecs
1
2
  import contextlib
2
3
  from io import BytesIO
3
4
 
@@ -14,6 +15,8 @@ from nautobot.core.api.renderers import NautobotCSVRenderer
14
15
  from nautobot.core.api.utils import get_serializer_for_model
15
16
  from nautobot.core.celery import app, register_jobs
16
17
  from nautobot.core.exceptions import AbortTransaction
18
+ from nautobot.core.jobs.cleanup import LogsCleanup
19
+ from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
17
20
  from nautobot.core.utils.lookup import get_filterset_for_model
18
21
  from nautobot.core.utils.requests import get_filterable_params_from_filter_params
19
22
  from nautobot.extras.datasources import ensure_git_repository, git_repository_dry_run, refresh_datasource_content
@@ -36,6 +39,7 @@ class GitRepositorySync(Job):
36
39
 
37
40
  class Meta:
38
41
  name = "Git Repository: Sync"
42
+ description = "Clone and/or pull a Git repository, then refresh data sourced from this repository."
39
43
  has_sensitive_variables = False
40
44
 
41
45
  def run(self, repository):
@@ -65,6 +69,7 @@ class GitRepositoryDryRun(Job):
65
69
 
66
70
  class Meta:
67
71
  name = "Git Repository: Dry-Run"
72
+ description = "Dry run of Git repository sync - will not update data sourced from this repository."
68
73
  has_sensitive_variables = False
69
74
 
70
75
  def run(self, repository):
@@ -111,6 +116,7 @@ class ExportObjectList(Job):
111
116
 
112
117
  class Meta:
113
118
  name = "Export Object List"
119
+ description = "Export a list of objects to CSV or YAML, or render a specified Export Template."
114
120
  has_sensitive_variables = False
115
121
  # Exporting large querysets may take substantial processing time
116
122
  soft_time_limit = 1800
@@ -135,7 +141,15 @@ class ExportObjectList(Job):
135
141
  # such that they never are even seen here.
136
142
  query_params = QueryDict(query_string)
137
143
  self.logger.debug("Parsed query_params: `%s`", query_params.dict())
138
- default_non_filter_params = ("export", "page", "per_page", "sort")
144
+ default_non_filter_params = (
145
+ "all_filters_removed",
146
+ "export",
147
+ "page",
148
+ "per_page",
149
+ "saved_view",
150
+ "sort",
151
+ "table_changes_pending",
152
+ )
139
153
  filter_params = get_filterable_params_from_filter_params(
140
154
  query_params, default_non_filter_params, filterset_class()
141
155
  )
@@ -212,6 +226,7 @@ class ImportObjects(Job):
212
226
 
213
227
  class Meta:
214
228
  name = "Import Objects"
229
+ description = "Import objects from CSV-formatted data."
215
230
  has_sensitive_variables = False
216
231
  # Importing large files may take substantial processing time
217
232
  soft_time_limit = 1800
@@ -282,7 +297,9 @@ class ImportObjects(Job):
282
297
  if not csv_data and not csv_file:
283
298
  raise RunJobTaskFailed("Either csv_data or csv_file must be provided")
284
299
  if csv_file:
285
- csv_bytes = csv_file
300
+ # data_encoding is utf-8 and file_encoding is utf-8-sig
301
+ # Bytes read from the original file are decoded according to file_encoding, and the result is encoded using data_encoding.
302
+ csv_bytes = codecs.EncodedFile(csv_file, "utf-8", "utf-8-sig")
286
303
  else:
287
304
  csv_bytes = BytesIO(csv_data.encode("utf-8"))
288
305
 
@@ -314,5 +331,5 @@ class ImportObjects(Job):
314
331
  raise RunJobTaskFailed("CSV import not fully successful, see logs")
315
332
 
316
333
 
317
- jobs = [ExportObjectList, GitRepositorySync, GitRepositoryDryRun, ImportObjects]
334
+ jobs = [ExportObjectList, GitRepositorySync, GitRepositoryDryRun, ImportObjects, LogsCleanup, RefreshDynamicGroupCaches]
318
335
  register_jobs(*jobs)
@@ -0,0 +1,100 @@
1
+ from datetime import timedelta
2
+
3
+ from django.core.exceptions import PermissionDenied
4
+ from django.db.models.signals import pre_delete
5
+ from django.utils import timezone
6
+
7
+ from nautobot.core.choices import ChoiceSet
8
+ from nautobot.core.utils.config import get_settings_or_config
9
+ from nautobot.extras.jobs import IntegerVar, Job, MultiChoiceVar
10
+ from nautobot.extras.models import JobResult, ObjectChange
11
+ from nautobot.extras.signals import _handle_deleted_object
12
+
13
+ name = "System Jobs"
14
+
15
+
16
+ class CleanupTypes(ChoiceSet):
17
+ JOB_RESULT = "extras.JobResult"
18
+ OBJECT_CHANGE = "extras.ObjectChange"
19
+
20
+ CHOICES = (
21
+ (JOB_RESULT, "Job results"),
22
+ (OBJECT_CHANGE, "Change logs"),
23
+ )
24
+
25
+
26
+ class LogsCleanup(Job):
27
+ """
28
+ System job to clean up ObjectChange and/or JobResult (and JobLogEntry) records older than a given age.
29
+ """
30
+
31
+ cleanup_types = MultiChoiceVar(
32
+ choices=CleanupTypes.CHOICES,
33
+ required=True,
34
+ )
35
+
36
+ max_age = IntegerVar(
37
+ description=(
38
+ "Maximum age of records to retain, in days. "
39
+ "Leave empty to use the CHANGELOG_RETENTION setting as the maximum."
40
+ ),
41
+ label="Max Age",
42
+ min_value=0,
43
+ required=False,
44
+ )
45
+
46
+ class Meta:
47
+ name = "Logs Cleanup"
48
+ description = "Delete ObjectChange and/or JobResult/JobLogEntry records older than a specified cutoff."
49
+ has_sensitive_variables = False
50
+
51
+ def run(self, *, cleanup_types, max_age=None):
52
+ if max_age in (None, ""):
53
+ max_age = get_settings_or_config("CHANGELOG_RETENTION")
54
+ if max_age == 0:
55
+ self.logger.warning(
56
+ "CHANGELOG_RETENTION setting is set to zero, disabling this Job. "
57
+ "If you wish to use this Job to delete records, you must specify a `max_age` value."
58
+ )
59
+ return 0
60
+
61
+ if CleanupTypes.JOB_RESULT in cleanup_types and not self.user.has_perm("extras.delete_jobresult"):
62
+ self.logger.error('User "%s" does not have permission to delete JobResult records', self.user)
63
+ raise PermissionDenied("User does not have delete permissions for JobResult records")
64
+
65
+ if CleanupTypes.OBJECT_CHANGE in cleanup_types and not self.user.has_perm("extras.delete_objectchange"):
66
+ self.logger.error('User "%s" does not have permission to delete ObjectChange records', self.user)
67
+ raise PermissionDenied("User does not have delete permissions for ObjectChange records")
68
+
69
+ # Bulk delete goes much faster if Django doesn't have signals to process.
70
+ # Temporarily detach the ones we *know* to be irrelevant.
71
+ self.logger.debug("Temporarily disconnecting some signals for performance")
72
+ pre_delete.disconnect(_handle_deleted_object)
73
+
74
+ try:
75
+ cutoff = timezone.now() - timedelta(days=max_age)
76
+ result = {}
77
+
78
+ if CleanupTypes.JOB_RESULT in cleanup_types:
79
+ self.logger.info("Deleting JobResult records prior to %s", cutoff)
80
+ _, deleted_dict = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff).delete()
81
+ result.setdefault("extras.JobResult", 0)
82
+ result.setdefault("extras.JobLogEntry", 0)
83
+ result.update(**deleted_dict)
84
+ self.logger.info(
85
+ "Deleted %d JobResult records and their associated %d JobLogEntry records",
86
+ result["extras.JobResult"],
87
+ result["extras.JobLogEntry"],
88
+ )
89
+
90
+ if CleanupTypes.OBJECT_CHANGE in cleanup_types:
91
+ self.logger.info("Deleting ObjectChange records prior to %s", cutoff)
92
+ deleted_count, _ = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff).delete()
93
+ self.logger.info("Deleted %d ObjectChange records", deleted_count)
94
+ result["extras.ObjectChange"] = deleted_count
95
+
96
+ return result
97
+ finally:
98
+ # Be sure to clean up after ourselves!
99
+ self.logger.debug("Re-connecting signals")
100
+ pre_delete.connect(_handle_deleted_object)
@@ -0,0 +1,38 @@
1
+ from nautobot.extras.choices import DynamicGroupTypeChoices
2
+ from nautobot.extras.jobs import Job, ObjectVar
3
+ from nautobot.extras.models import DynamicGroup
4
+
5
+ name = "System Jobs"
6
+
7
+
8
+ class RefreshDynamicGroupCaches(Job):
9
+ """
10
+ System job to recalculate and re-cache the members of Dynamic Groups for improved performance.
11
+ """
12
+
13
+ single_group = ObjectVar(
14
+ description="Select to refresh only a single specified group instead of all groups",
15
+ model=DynamicGroup,
16
+ query_params={
17
+ "group_type": [DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER, DynamicGroupTypeChoices.TYPE_DYNAMIC_SET],
18
+ },
19
+ required=False,
20
+ )
21
+
22
+ class Meta:
23
+ name = "Refresh Dynamic Group Caches"
24
+ description = "Re-calculate and re-cache the membership lists of Dynamic Groups."
25
+ has_sensitive_variables = False
26
+
27
+ def run(self, single_group=None):
28
+ groups = DynamicGroup.objects.restrict(self.user, "view").exclude(
29
+ group_type=DynamicGroupTypeChoices.TYPE_STATIC
30
+ )
31
+ if single_group is not None:
32
+ groups = groups.filter(pk=single_group.pk)
33
+
34
+ for group in groups:
35
+ group.update_cached_members()
36
+ self.logger.info("Cache refreshed successfully, now with %d members", group.count, extra={"object": group})
37
+
38
+ self.logger.info("Cache(s) refreshed")