nautobot 2.2.9__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 (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 +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 +95 -13
  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/schema.py +26 -4
  61. nautobot/core/jobs/__init__.py +16 -2
  62. nautobot/core/jobs/cleanup.py +100 -0
  63. nautobot/core/jobs/groups.py +38 -0
  64. nautobot/core/management/commands/generate_test_data.py +116 -3
  65. nautobot/core/models/__init__.py +34 -9
  66. nautobot/core/models/generics.py +19 -3
  67. nautobot/core/models/name_color_content_types.py +7 -28
  68. nautobot/core/models/querysets.py +4 -3
  69. nautobot/core/models/tree_queries.py +1 -1
  70. nautobot/core/models/utils.py +21 -5
  71. nautobot/core/settings.py +2 -17
  72. nautobot/core/settings.yaml +34 -13
  73. nautobot/core/settings_funcs.py +103 -0
  74. nautobot/core/tables.py +130 -56
  75. nautobot/core/templates/admin/search_form.html +1 -1
  76. nautobot/core/templates/buttons/add.html +11 -3
  77. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  78. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/export.html +101 -53
  80. nautobot/core/templates/buttons/job_import.html +11 -3
  81. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  82. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  83. nautobot/core/templates/generic/object_changelog.html +0 -9
  84. nautobot/core/templates/generic/object_list.html +156 -17
  85. nautobot/core/templates/generic/object_retrieve.html +80 -16
  86. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  87. nautobot/core/templates/inc/javascript.html +2 -0
  88. nautobot/core/templates/inc/media.html +2 -2
  89. nautobot/core/templates/inc/nav_menu.html +1 -0
  90. nautobot/core/templates/inc/paginator.html +7 -7
  91. nautobot/core/templates/inc/search_panel.html +2 -2
  92. nautobot/core/templates/inc/table.html +2 -2
  93. nautobot/core/templates/nautobot_config.py.j2 +13 -8
  94. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  95. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  96. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  97. nautobot/core/templates/utilities/theme_preview.html +25 -8
  98. nautobot/core/templates/utilities/worker_status.html +152 -0
  99. nautobot/core/templatetags/buttons.py +335 -38
  100. nautobot/core/templatetags/form_helpers.py +1 -1
  101. nautobot/core/templatetags/helpers.py +181 -11
  102. nautobot/core/testing/api.py +5 -4
  103. nautobot/core/testing/filters.py +63 -14
  104. nautobot/core/testing/mixins.py +46 -0
  105. nautobot/core/testing/models.py +22 -0
  106. nautobot/core/testing/schema.py +4 -8
  107. nautobot/core/testing/views.py +31 -14
  108. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  109. nautobot/core/tests/integration/test_swagger.py +1 -1
  110. nautobot/core/tests/nautobot_config.py +0 -1
  111. nautobot/core/tests/runner.py +2 -2
  112. nautobot/core/tests/test_api.py +1 -0
  113. nautobot/core/tests/test_authentication.py +7 -2
  114. nautobot/core/tests/test_filters.py +11 -9
  115. nautobot/core/tests/test_forms.py +9 -0
  116. nautobot/core/tests/test_graphql.py +27 -16
  117. nautobot/core/tests/test_jobs.py +122 -0
  118. nautobot/core/tests/test_tables.py +3 -1
  119. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  120. nautobot/core/tests/test_utils.py +31 -20
  121. nautobot/core/tests/test_views.py +6 -6
  122. nautobot/core/urls.py +8 -3
  123. nautobot/core/utils/deprecation.py +29 -0
  124. nautobot/core/utils/filtering.py +12 -9
  125. nautobot/core/utils/lookup.py +37 -2
  126. nautobot/core/utils/requests.py +4 -1
  127. nautobot/core/views/__init__.py +137 -24
  128. nautobot/core/views/generic.py +119 -67
  129. nautobot/core/views/mixins.py +105 -36
  130. nautobot/core/views/paginator.py +9 -3
  131. nautobot/core/views/renderers.py +121 -56
  132. nautobot/core/views/utils.py +81 -1
  133. nautobot/dcim/__init__.py +0 -1
  134. nautobot/dcim/api/serializers.py +180 -44
  135. nautobot/dcim/api/urls.py +7 -3
  136. nautobot/dcim/api/views.py +53 -7
  137. nautobot/dcim/apps.py +3 -0
  138. nautobot/dcim/choices.py +25 -0
  139. nautobot/dcim/constants.py +7 -0
  140. nautobot/dcim/factory.py +252 -18
  141. nautobot/dcim/filters/__init__.py +373 -193
  142. nautobot/dcim/filters/mixins.py +274 -1
  143. nautobot/dcim/forms.py +834 -121
  144. nautobot/dcim/graphql/types.py +2 -2
  145. nautobot/dcim/homepage.py +1 -1
  146. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  147. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  148. nautobot/dcim/migrations/0061_module_models.py +862 -0
  149. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  150. nautobot/dcim/models/__init__.py +8 -0
  151. nautobot/dcim/models/cables.py +15 -0
  152. nautobot/dcim/models/device_component_templates.py +207 -53
  153. nautobot/dcim/models/device_components.py +275 -99
  154. nautobot/dcim/models/devices.py +468 -13
  155. nautobot/dcim/models/racks.py +0 -1
  156. nautobot/dcim/navigation.py +47 -0
  157. nautobot/dcim/signals.py +3 -3
  158. nautobot/dcim/tables/__init__.py +35 -23
  159. nautobot/dcim/tables/devices.py +229 -43
  160. nautobot/dcim/tables/devicetypes.py +65 -9
  161. nautobot/dcim/tables/racks.py +5 -1
  162. nautobot/dcim/tables/template_code.py +46 -26
  163. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  164. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  165. nautobot/dcim/templates/dcim/device/base.html +14 -6
  166. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  167. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  168. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  169. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  170. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  171. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  172. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  173. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  174. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  175. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  176. nautobot/dcim/templates/dcim/device.html +45 -1
  177. nautobot/dcim/templates/dcim/device_component.html +13 -5
  178. nautobot/dcim/templates/dcim/device_list.html +2 -1
  179. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  180. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  181. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  182. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  183. nautobot/dcim/templates/dcim/interface.html +17 -2
  184. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  185. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  186. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  187. nautobot/dcim/templates/dcim/module/base.html +97 -0
  188. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  189. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  190. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  191. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  192. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  193. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  194. nautobot/dcim/templates/dcim/module_list.html +20 -0
  195. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  196. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  197. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  198. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  199. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  200. nautobot/dcim/templates/dcim/module_update.html +71 -0
  201. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  202. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  203. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  204. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  205. nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
  206. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  207. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  208. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  209. nautobot/dcim/tests/test_api.py +693 -208
  210. nautobot/dcim/tests/test_filters.py +843 -217
  211. nautobot/dcim/tests/test_models.py +1072 -8
  212. nautobot/dcim/tests/test_views.py +1510 -341
  213. nautobot/dcim/urls.py +17 -2
  214. nautobot/dcim/utils.py +2 -3
  215. nautobot/dcim/views.py +1106 -116
  216. nautobot/extras/__init__.py +0 -1
  217. nautobot/extras/api/serializers.py +115 -3
  218. nautobot/extras/api/urls.py +12 -0
  219. nautobot/extras/api/views.py +66 -0
  220. nautobot/extras/apps.py +2 -2
  221. nautobot/extras/choices.py +43 -0
  222. nautobot/extras/context_managers.py +13 -8
  223. nautobot/extras/datasources/git.py +2 -0
  224. nautobot/extras/factory.py +460 -9
  225. nautobot/extras/filters/__init__.py +174 -3
  226. nautobot/extras/filters/mixins.py +46 -43
  227. nautobot/extras/forms/base.py +24 -5
  228. nautobot/extras/forms/forms.py +227 -8
  229. nautobot/extras/forms/mixins.py +93 -0
  230. nautobot/extras/graphql/types.py +23 -10
  231. nautobot/extras/homepage.py +14 -1
  232. nautobot/extras/management/__init__.py +1 -0
  233. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  234. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  235. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  236. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  237. nautobot/extras/migrations/0111_metadata.py +162 -0
  238. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  239. nautobot/extras/migrations/0113_saved_views.py +77 -0
  240. nautobot/extras/models/__init__.py +15 -1
  241. nautobot/extras/models/change_logging.py +3 -3
  242. nautobot/extras/models/contacts.py +4 -0
  243. nautobot/extras/models/customfields.py +18 -3
  244. nautobot/extras/models/groups.py +389 -225
  245. nautobot/extras/models/jobs.py +6 -3
  246. nautobot/extras/models/metadata.py +441 -0
  247. nautobot/extras/models/mixins.py +72 -62
  248. nautobot/extras/models/models.py +118 -9
  249. nautobot/extras/models/relationships.py +9 -2
  250. nautobot/extras/models/tags.py +13 -2
  251. nautobot/extras/navigation.py +57 -0
  252. nautobot/extras/plugins/__init__.py +3 -1
  253. nautobot/extras/querysets.py +30 -66
  254. nautobot/extras/signals.py +95 -100
  255. nautobot/extras/tables.py +165 -12
  256. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  257. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  258. nautobot/extras/templates/extras/job.html +1 -1
  259. nautobot/extras/templates/extras/jobresult.html +61 -74
  260. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  261. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  262. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  263. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  264. nautobot/extras/templates/extras/plugins_list.html +1 -3
  265. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  266. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  267. nautobot/extras/tests/integration/test_customfields.py +1 -0
  268. nautobot/extras/tests/test_api.py +509 -23
  269. nautobot/extras/tests/test_changelog.py +20 -9
  270. nautobot/extras/tests/test_context_managers.py +22 -15
  271. nautobot/extras/tests/test_datasources.py +13 -1
  272. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  273. nautobot/extras/tests/test_filters.py +211 -12
  274. nautobot/extras/tests/test_jobs.py +6 -6
  275. nautobot/extras/tests/test_models.py +501 -4
  276. nautobot/extras/tests/test_relationships.py +1 -0
  277. nautobot/extras/tests/test_views.py +565 -8
  278. nautobot/extras/tests/test_webhooks.py +1 -1
  279. nautobot/extras/urls.py +5 -0
  280. nautobot/extras/utils.py +51 -11
  281. nautobot/extras/views.py +542 -76
  282. nautobot/ipam/__init__.py +0 -1
  283. nautobot/ipam/apps.py +1 -0
  284. nautobot/ipam/factory.py +17 -19
  285. nautobot/ipam/filters.py +13 -0
  286. nautobot/ipam/forms.py +8 -4
  287. nautobot/ipam/graphql/types.py +2 -2
  288. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  289. nautobot/ipam/models.py +11 -8
  290. nautobot/ipam/querysets.py +1 -1
  291. nautobot/ipam/signals.py +4 -2
  292. nautobot/ipam/tables.py +5 -0
  293. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  294. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  295. nautobot/ipam/templates/ipam/prefix.html +1 -0
  296. nautobot/ipam/tests/test_api.py +37 -18
  297. nautobot/ipam/tests/test_filters.py +26 -2
  298. nautobot/ipam/tests/test_models.py +6 -0
  299. nautobot/ipam/tests/test_querysets.py +1 -1
  300. nautobot/ipam/tests/test_views.py +3 -2
  301. nautobot/ipam/urls.py +2 -2
  302. nautobot/ipam/views.py +18 -26
  303. nautobot/project-static/css/base.css +20 -0
  304. nautobot/project-static/css/dark.css +11 -0
  305. nautobot/project-static/docs/404.html +892 -88
  306. nautobot/project-static/docs/apps/index.html +892 -88
  307. nautobot/project-static/docs/apps/nautobot-apps.html +892 -88
  308. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  309. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  310. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  311. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +919 -120
  312. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +904 -101
  313. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1618 -903
  314. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +935 -144
  315. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +977 -188
  316. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +901 -99
  317. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +897 -93
  318. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +991 -193
  319. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +974 -131
  320. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1078 -272
  321. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1242 -334
  322. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1727 -875
  323. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1164 -381
  324. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2088 -1374
  325. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2246 -1422
  326. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +912 -111
  327. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +963 -163
  328. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1010 -223
  329. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1913 -1277
  330. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1846 -1102
  331. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +904 -101
  332. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2331 -1699
  333. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1802 -1024
  334. nautobot/project-static/docs/development/apps/api/configuration-view.html +892 -88
  335. nautobot/project-static/docs/development/apps/api/database-backend-config.html +892 -88
  336. nautobot/project-static/docs/development/apps/api/models/django-admin.html +892 -88
  337. nautobot/project-static/docs/development/apps/api/models/global-search.html +892 -88
  338. nautobot/project-static/docs/development/apps/api/models/graphql.html +892 -88
  339. nautobot/project-static/docs/development/apps/api/models/index.html +942 -90
  340. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +892 -88
  341. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +892 -88
  342. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +892 -88
  343. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +892 -88
  344. nautobot/project-static/docs/development/apps/api/platform-features/index.html +892 -88
  345. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +892 -88
  346. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +892 -88
  347. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +892 -88
  348. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +892 -88
  349. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +892 -88
  350. nautobot/project-static/docs/development/apps/api/prometheus.html +892 -88
  351. nautobot/project-static/docs/development/apps/api/setup.html +892 -88
  352. nautobot/project-static/docs/development/apps/api/testing.html +892 -88
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +892 -88
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +892 -88
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +892 -88
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +892 -88
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +892 -88
  358. nautobot/project-static/docs/development/apps/api/views/base-template.html +892 -88
  359. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +892 -88
  360. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +892 -88
  361. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +892 -88
  362. nautobot/project-static/docs/development/apps/api/views/index.html +892 -88
  363. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +892 -88
  364. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +892 -88
  365. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +892 -88
  366. nautobot/project-static/docs/development/apps/api/views/notes.html +892 -88
  367. nautobot/project-static/docs/development/apps/api/views/rest-api.html +892 -88
  368. nautobot/project-static/docs/development/apps/api/views/urls.html +892 -88
  369. nautobot/project-static/docs/development/apps/index.html +892 -88
  370. nautobot/project-static/docs/development/apps/migration/code-updates.html +892 -88
  371. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +892 -88
  372. nautobot/project-static/docs/development/apps/migration/from-v1.html +892 -88
  373. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +892 -88
  374. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +892 -88
  375. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +892 -88
  376. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +892 -88
  377. nautobot/project-static/docs/development/apps/porting-from-netbox.html +892 -88
  378. nautobot/project-static/docs/development/core/application-registry.html +892 -88
  379. nautobot/project-static/docs/development/core/best-practices.html +893 -88
  380. nautobot/project-static/docs/development/core/bootstrap-ui.html +892 -88
  381. nautobot/project-static/docs/development/core/caching.html +892 -88
  382. nautobot/project-static/docs/development/core/controllers.html +892 -88
  383. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +892 -88
  384. nautobot/project-static/docs/development/core/generic-views.html +892 -88
  385. nautobot/project-static/docs/development/core/getting-started.html +892 -88
  386. nautobot/project-static/docs/development/core/homepage.html +892 -88
  387. nautobot/project-static/docs/development/core/index.html +892 -88
  388. nautobot/project-static/docs/development/core/model-checklist.html +901 -89
  389. nautobot/project-static/docs/development/core/model-features.html +892 -88
  390. nautobot/project-static/docs/development/core/natural-keys.html +892 -88
  391. nautobot/project-static/docs/development/core/navigation-menu.html +892 -88
  392. nautobot/project-static/docs/development/core/release-checklist.html +895 -91
  393. nautobot/project-static/docs/development/core/role-internals.html +892 -88
  394. nautobot/project-static/docs/development/core/settings.html +892 -88
  395. nautobot/project-static/docs/development/core/style-guide.html +893 -89
  396. nautobot/project-static/docs/development/core/templates.html +904 -89
  397. nautobot/project-static/docs/development/core/testing.html +892 -88
  398. nautobot/project-static/docs/development/core/user-preferences.html +892 -88
  399. nautobot/project-static/docs/development/index.html +892 -88
  400. nautobot/project-static/docs/development/jobs/index.html +893 -89
  401. nautobot/project-static/docs/development/jobs/migration/from-v1.html +892 -88
  402. nautobot/project-static/docs/index.html +892 -88
  403. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  404. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  405. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  406. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  407. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  408. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  409. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  410. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  411. nautobot/project-static/docs/models/dcim/module.html +15 -0
  412. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  413. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  414. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  415. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  416. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  417. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  418. nautobot/project-static/docs/models/extras/role.html +15 -0
  419. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  420. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  421. nautobot/project-static/docs/models/extras/status.html +15 -0
  422. nautobot/project-static/docs/objects.inv +0 -0
  423. nautobot/project-static/docs/overview/application_stack.html +900 -89
  424. nautobot/project-static/docs/overview/design_philosophy.html +892 -88
  425. nautobot/project-static/docs/release-notes/index.html +1129 -92
  426. nautobot/project-static/docs/release-notes/version-1.0.html +892 -88
  427. nautobot/project-static/docs/release-notes/version-1.1.html +892 -88
  428. nautobot/project-static/docs/release-notes/version-1.2.html +892 -88
  429. nautobot/project-static/docs/release-notes/version-1.3.html +892 -88
  430. nautobot/project-static/docs/release-notes/version-1.4.html +892 -88
  431. nautobot/project-static/docs/release-notes/version-1.5.html +893 -89
  432. nautobot/project-static/docs/release-notes/version-1.6.html +893 -89
  433. nautobot/project-static/docs/release-notes/version-2.0.html +892 -88
  434. nautobot/project-static/docs/release-notes/version-2.1.html +892 -88
  435. nautobot/project-static/docs/release-notes/version-2.2.html +895 -91
  436. nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
  437. nautobot/project-static/docs/requirements.txt +5 -5
  438. nautobot/project-static/docs/search/search_index.json +1 -1
  439. nautobot/project-static/docs/sitemap.xml +331 -256
  440. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  441. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +892 -88
  442. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +892 -88
  443. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +892 -88
  444. nautobot/project-static/docs/user-guide/administration/configuration/index.html +892 -88
  445. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +992 -174
  446. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +892 -88
  447. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +892 -88
  448. nautobot/project-static/docs/user-guide/administration/guides/caching.html +892 -88
  449. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +896 -88
  450. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +892 -88
  451. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +892 -88
  452. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +892 -88
  453. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +892 -88
  454. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +892 -88
  455. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +892 -88
  456. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +892 -88
  457. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +892 -88
  458. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +944 -153
  459. nautobot/project-static/docs/user-guide/administration/installation/index.html +901 -93
  460. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +934 -122
  461. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +954 -157
  462. nautobot/project-static/docs/user-guide/administration/installation/services.html +913 -112
  463. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +908 -99
  464. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +892 -88
  465. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +892 -88
  466. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +892 -88
  467. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +892 -88
  468. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +893 -89
  469. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +892 -88
  470. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +892 -88
  471. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +892 -88
  472. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +892 -88
  473. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +892 -88
  474. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +892 -88
  475. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +892 -88
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +892 -88
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +892 -88
  478. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +892 -88
  479. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +892 -88
  480. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +893 -89
  481. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +892 -88
  482. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +896 -88
  483. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +895 -91
  484. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  485. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  486. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  487. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  488. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  491. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +906 -102
  492. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +923 -105
  493. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +923 -105
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +918 -100
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +923 -105
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +906 -102
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +906 -102
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +913 -105
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +920 -116
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +921 -117
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +918 -114
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +906 -102
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +914 -105
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +926 -108
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +936 -118
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +928 -106
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +906 -102
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +937 -119
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +928 -110
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +918 -114
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +921 -117
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +923 -115
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +916 -112
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +906 -102
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +940 -83
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +924 -106
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +906 -102
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +943 -86
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +921 -103
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +929 -125
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +918 -114
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +906 -102
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +922 -104
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +924 -106
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +906 -102
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +906 -102
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +906 -102
  532. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +936 -88
  533. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +892 -88
  534. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +897 -89
  535. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +897 -89
  536. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +892 -88
  537. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +892 -88
  538. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +892 -88
  539. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +892 -88
  540. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +892 -88
  541. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +892 -88
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +892 -88
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +892 -88
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +892 -88
  545. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +892 -88
  546. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +901 -96
  547. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +892 -88
  548. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +892 -88
  549. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +892 -88
  550. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +892 -88
  551. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +892 -88
  552. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +897 -89
  553. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +892 -88
  554. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +892 -88
  555. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +892 -88
  556. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +892 -88
  557. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +892 -88
  558. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +892 -88
  559. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +892 -88
  560. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +892 -88
  561. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +892 -88
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +892 -88
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +892 -88
  564. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +892 -88
  565. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +892 -88
  566. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  567. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  568. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  569. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  570. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  571. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  572. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +892 -88
  592. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +892 -88
  593. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +892 -88
  594. nautobot/project-static/docs/user-guide/index.html +892 -88
  595. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +892 -88
  596. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +892 -88
  597. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +892 -88
  598. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +892 -88
  599. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1258 -785
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +895 -91
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +892 -88
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +892 -88
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +892 -88
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +892 -88
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +892 -88
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +892 -88
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +892 -88
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +892 -88
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +892 -88
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +896 -88
  611. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +892 -88
  612. nautobot/project-static/docs/user-guide/platform-functionality/note.html +895 -91
  613. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
  614. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +895 -91
  615. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +892 -88
  616. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +892 -88
  617. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +892 -88
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +892 -88
  619. nautobot/project-static/docs/user-guide/platform-functionality/role.html +895 -91
  620. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  621. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +895 -91
  622. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  623. nautobot/project-static/docs/user-guide/platform-functionality/status.html +892 -88
  624. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +892 -88
  625. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +950 -121
  626. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +892 -88
  627. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +892 -88
  628. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +892 -88
  629. nautobot/project-static/js/forms.js +71 -0
  630. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  631. nautobot/project-static/js/tableconfig.js +6 -1
  632. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  633. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  634. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  635. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  636. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  637. nautobot/tenancy/__init__.py +0 -1
  638. nautobot/tenancy/apps.py +1 -0
  639. nautobot/tenancy/factory.py +3 -2
  640. nautobot/tenancy/filters/__init__.py +1 -0
  641. nautobot/tenancy/forms.py +1 -1
  642. nautobot/tenancy/templates/tenancy/tenant.html +24 -20
  643. nautobot/tenancy/views.py +11 -10
  644. nautobot/users/__init__.py +0 -1
  645. nautobot/users/api/serializers.py +1 -1
  646. nautobot/users/api/views.py +4 -2
  647. nautobot/users/apps.py +3 -2
  648. nautobot/users/factory.py +3 -3
  649. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  650. nautobot/users/models.py +12 -0
  651. nautobot/users/tests/test_filters.py +6 -3
  652. nautobot/users/urls.py +8 -0
  653. nautobot/virtualization/__init__.py +0 -1
  654. nautobot/virtualization/apps.py +1 -0
  655. nautobot/virtualization/filters.py +6 -1
  656. nautobot/virtualization/forms.py +11 -3
  657. nautobot/virtualization/graphql/types.py +2 -2
  658. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  659. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  660. nautobot/virtualization/models.py +0 -2
  661. nautobot/virtualization/tables.py +10 -3
  662. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  663. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  664. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  665. nautobot/virtualization/tests/test_api.py +9 -4
  666. nautobot/virtualization/tests/test_filters.py +22 -0
  667. nautobot/virtualization/tests/test_models.py +7 -3
  668. nautobot/virtualization/tests/test_views.py +19 -3
  669. nautobot/virtualization/urls.py +2 -2
  670. nautobot/virtualization/views.py +10 -32
  671. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/METADATA +20 -18
  672. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/RECORD +677 -557
  673. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  674. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  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.0.dist-info}/LICENSE.txt +0 -0
  695. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
  696. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
  697. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -269,7 +269,7 @@ class ViewTestCases:
269
269
  response = self.client.get(url)
270
270
  self.assertHttpStatus(response, 200)
271
271
  response_data = response.content.decode(response.charset)
272
- if type(obj) not in [extras_models.Contact, extras_models.Team]:
272
+ if getattr(obj, "is_contact_associable_model", False):
273
273
  self.assertInHTML(
274
274
  f'<a href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)" aria-controls="contacts" role="tab" data-toggle="tab">Contacts</a>',
275
275
  response_data,
@@ -288,7 +288,7 @@ class ViewTestCases:
288
288
  response = self.client.get(url)
289
289
  self.assertHttpStatus(response, 200)
290
290
  response_data = response.content.decode(response.charset)
291
- if type(obj) not in [extras_models.Contact, extras_models.Team]:
291
+ if getattr(obj, "is_contact_associable_model", False):
292
292
  self.assertInHTML(
293
293
  f'<a href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)" aria-controls="contacts" role="tab" data-toggle="tab">Contacts</a>',
294
294
  response_data,
@@ -467,10 +467,12 @@ class ViewTestCases:
467
467
  """
468
468
  Edit a single existing instance.
469
469
 
470
- :form_data: Data to be used when updating the first existing object.
470
+ :update_data: Data to be used when updating the first existing object, fall back to form_data if not provided.
471
+ :form_data: Fall back to this data if update_data is not provided, for backward compatibility.
471
472
  """
472
473
 
473
474
  form_data = {}
475
+ update_data = {}
474
476
 
475
477
  def test_edit_object_without_permission(self):
476
478
  instance = self._get_queryset().first()
@@ -480,9 +482,10 @@ class ViewTestCases:
480
482
  self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), [403, 404])
481
483
 
482
484
  # Try POST without permission
485
+ update_data = self.update_data or self.form_data
483
486
  request = {
484
487
  "path": self._get_url("edit", instance),
485
- "data": utils.post_data(self.form_data),
488
+ "data": utils.post_data(update_data),
486
489
  }
487
490
  with utils.disable_warnings("django.request"):
488
491
  self.assertHttpStatus(self.client.post(**request), [403, 404])
@@ -501,17 +504,17 @@ class ViewTestCases:
501
504
  self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), 200)
502
505
 
503
506
  # Try POST with model-level permission
507
+ update_data = self.update_data or self.form_data
504
508
  request = {
505
509
  "path": self._get_url("edit", instance),
506
- "data": utils.post_data(self.form_data),
510
+ "data": utils.post_data(update_data),
507
511
  }
508
512
  self.assertHttpStatus(self.client.post(**request), 302)
509
- self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), self.form_data)
513
+ self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), update_data)
510
514
 
511
515
  if hasattr(self.model, "to_objectchange"):
512
516
  # Verify ObjectChange creation
513
517
  objectchanges = lookup.get_changes_for_model(instance)
514
- self.assertEqual(len(objectchanges), 1)
515
518
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
516
519
  # Validate if detail view exists
517
520
  validate = URLValidator()
@@ -549,17 +552,18 @@ class ViewTestCases:
549
552
  self.assertHttpStatus(self.client.get(self._get_url("edit", instance2)), 404)
550
553
 
551
554
  # Try to edit a permitted object
555
+ update_data = self.update_data or self.form_data
552
556
  request = {
553
557
  "path": self._get_url("edit", instance1),
554
- "data": utils.post_data(self.form_data),
558
+ "data": utils.post_data(update_data),
555
559
  }
556
560
  self.assertHttpStatus(self.client.post(**request), 302)
557
- self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk), self.form_data)
561
+ self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk), update_data)
558
562
 
559
563
  # Try to edit a non-permitted object
560
564
  request = {
561
565
  "path": self._get_url("edit", instance2),
562
- "data": utils.post_data(self.form_data),
566
+ "data": utils.post_data(update_data),
563
567
  }
564
568
  self.assertHttpStatus(self.client.post(**request), 404)
565
569
 
@@ -637,7 +641,6 @@ class ViewTestCases:
637
641
  if hasattr(self.model, "to_objectchange"):
638
642
  # Verify ObjectChange creation
639
643
  objectchanges = lookup.get_changes_for_model(instance)
640
- self.assertEqual(len(objectchanges), 1)
641
644
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_DELETE)
642
645
 
643
646
  if hasattr(self.model, "notes") and isinstance(instance.notes, extras_querysets.NotesQuerySet):
@@ -678,7 +681,6 @@ class ViewTestCases:
678
681
  if hasattr(self.model, "to_objectchange"):
679
682
  # Verify ObjectChange creation
680
683
  objectchanges = lookup.get_changes_for_model(instance)
681
- self.assertEqual(len(objectchanges), 1)
682
684
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_DELETE)
683
685
 
684
686
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -790,6 +792,10 @@ class ViewTestCases:
790
792
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
791
793
  def test_list_objects_filtered(self):
792
794
  instance1, instance2 = self._get_queryset().all()[:2]
795
+ if hasattr(self.model, "name") and instance1.name == instance2.name:
796
+ instance2.name += "X"
797
+ instance2.save()
798
+
793
799
  response = self.client.get(f"{self._get_url('list')}?id={instance1.pk}")
794
800
  self.assertHttpStatus(response, 200)
795
801
  content = utils.extract_page_body(response.content.decode(response.charset))
@@ -815,6 +821,10 @@ class ViewTestCases:
815
821
  def test_list_objects_unknown_filter_no_strict_filtering(self):
816
822
  """Verify that without STRICT_FILTERING, an unknown filter is ignored."""
817
823
  instance1, instance2 = self._get_queryset().all()[:2]
824
+ if hasattr(self.model, "name") and instance1.name == instance2.name:
825
+ instance2.name += "X"
826
+ instance2.save()
827
+
818
828
  with self.assertLogs("nautobot.core.filters") as cm:
819
829
  response = self.client.get(f"{self._get_url('list')}?ice_cream_flavor=chocolate")
820
830
  filterset = self.get_filterset()
@@ -881,6 +891,9 @@ class ViewTestCases:
881
891
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
882
892
  def test_list_objects_with_constrained_permission(self):
883
893
  instance1, instance2 = self._get_queryset().all()[:2]
894
+ if hasattr(self.model, "name") and instance1.name == instance2.name:
895
+ instance2.name += "X"
896
+ instance2.save()
884
897
 
885
898
  # Add object-level permission
886
899
  obj_perm = users_models.ObjectPermission(
@@ -1409,7 +1422,9 @@ class ViewTestCases:
1409
1422
  # Try POST with model-level permission
1410
1423
  self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
1411
1424
  for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
1412
- self.assertEqual(instance.name, f"{objects[i].name}X")
1425
+ name = getattr(instance, "name")
1426
+ expected_name = getattr(objects[i], "name") + "X"
1427
+ self.assertEqual(name, expected_name)
1413
1428
 
1414
1429
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1415
1430
  def test_bulk_rename_objects_with_constrained_permission(self):
@@ -1442,7 +1457,9 @@ class ViewTestCases:
1442
1457
  # Bulk rename permitted objects
1443
1458
  self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
1444
1459
  for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
1445
- self.assertEqual(instance.name, f"{objects[i].name}X")
1460
+ name = getattr(instance, "name")
1461
+ expected_name = getattr(objects[i], "name") + "X"
1462
+ self.assertEqual(name, expected_name)
1446
1463
 
1447
1464
  class PrimaryObjectViewTestCase(
1448
1465
  GetObjectViewTestCase,
@@ -18,6 +18,7 @@ class ImportObjectsUITestCase(SeleniumTestCase):
18
18
  self.browser.visit(self.live_server_url)
19
19
  self.browser.links.find_by_partial_text("Organization").click()
20
20
  self.browser.links.find_by_partial_text("Locations").click()
21
+ self.browser.find_by_id("actions-dropdown").click()
21
22
  self.browser.find_by_id("import-button").click()
22
23
 
23
24
  # Make sure the table of fields for a Location import is populated via a few spot checks
@@ -20,7 +20,7 @@ class SwaggerUITestCase(SeleniumTestCase):
20
20
  """Check that the dcim.location API endpoints are rendered correctly."""
21
21
  self.browser.visit(self.live_server_url + reverse("api_docs"))
22
22
  # Wait for Swagger UI to load, look for the location-list endpoint in the UI and click on it to expand it.
23
- dcim_locations_list = self.browser.find_by_id("operations-dcim-dcim_locations_list", wait_time=20).first
23
+ dcim_locations_list = self.browser.find_by_id("operations-dcim-dcim_locations_list", wait_time=60).first
24
24
  dcim_locations_list.find_by_tag("button").first.click()
25
25
 
26
26
  # Look for the "Try it out" button and click it
@@ -58,5 +58,4 @@ METRICS_ENABLED = True
58
58
 
59
59
  METRICS_AUTHENTICATED = True
60
60
 
61
- DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT = 0
62
61
  CONTENT_TYPE_CACHE_TIMEOUT = 0
@@ -13,9 +13,9 @@ from nautobot.core.celery import app, setup_nautobot_job_logging
13
13
  from nautobot.core.settings_funcs import parse_redis_connection
14
14
 
15
15
 
16
- def init_worker_with_unique_cache(counter):
16
+ def init_worker_with_unique_cache(*args, **kwargs):
17
17
  """Extend Django's default parallel unit test setup to also ensure distinct Redis caches."""
18
- _init_worker(counter) # call Django default to set _worker_id and set up parallel DB instances
18
+ _init_worker(*args, **kwargs) # call Django default to set _worker_id and set up parallel DB instances
19
19
  # _worker_id is now 1, 2, 3, 4, etc.
20
20
 
21
21
  from django.test.runner import _worker_id
@@ -992,6 +992,7 @@ class NewUIGetMenuAPIViewTestCase(testing.APITestCase):
992
992
  "Custom Links": "/extras/custom-links/",
993
993
  "Notes": "/extras/notes/",
994
994
  },
995
+ "Users": {"Saved Views": "/extras/saved-views/"},
995
996
  },
996
997
  }
997
998
 
@@ -4,6 +4,7 @@ from django.conf import settings
4
4
  from django.contrib.auth import get_user_model
5
5
  from django.contrib.auth.models import Group
6
6
  from django.contrib.contenttypes.models import ContentType
7
+ from django.db.models import Q
7
8
  from django.test.utils import override_settings
8
9
  from django.urls import reverse
9
10
  from netaddr import IPNetwork
@@ -573,10 +574,14 @@ class ObjectPermissionAPIViewTestCase(TestCase):
573
574
 
574
575
  # Check against 1st user's response
575
576
  self.assertEqual(response_user1.status_code, 200)
576
- self.assertEqual(response_user1.data["count"], 1)
577
+ self.assertEqual(
578
+ response_user1.data["count"], ObjectChange.objects.filter(Q(user=self.user) | Q(action="delete")).count()
579
+ )
577
580
  self.assertEqual(response_user1.data["results"][0]["user"]["id"], self.user.pk)
578
581
 
579
582
  # Check against 2nd user's response
580
583
  self.assertEqual(response_user2.status_code, 200)
581
- self.assertEqual(response_user2.data["count"], 1)
584
+ self.assertEqual(
585
+ response_user2.data["count"], ObjectChange.objects.filter(Q(user=obj_user2) | Q(action="delete")).count()
586
+ )
582
587
  self.assertEqual(response_user2.data["results"][0]["user"]["id"], obj_user2.pk)
@@ -142,6 +142,7 @@ class TreeNodeMultipleChoiceFilterTest(TestCase):
142
142
  self.child_location_2ab,
143
143
  self.child_location_same_name_2,
144
144
  ],
145
+ ordered=False,
145
146
  )
146
147
 
147
148
  def test_filter_null(self):
@@ -212,6 +213,7 @@ class TreeNodeMultipleChoiceFilterTest(TestCase):
212
213
  self.child_location_2ab,
213
214
  self.child_location_same_name_2,
214
215
  ],
216
+ ordered=False,
215
217
  )
216
218
 
217
219
  def test_filter_combined_name_exclude(self):
@@ -1304,14 +1306,14 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
1304
1306
  parent=self.parent_location_1,
1305
1307
  name="Test Child Location 1",
1306
1308
  location_type=self.lt,
1307
- asn=1234,
1309
+ asn=12345,
1308
1310
  status=status,
1309
1311
  )
1310
1312
  self.child_location_2 = dcim_models.Location.objects.create(
1311
1313
  parent=self.parent_location_2,
1312
1314
  name="Test Child Location 2",
1313
1315
  location_type=self.lt,
1314
- asn=12345,
1316
+ asn=123456,
1315
1317
  status=status,
1316
1318
  )
1317
1319
  self.child_location_3 = dcim_models.Location.objects.create(
@@ -1353,19 +1355,19 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
1353
1355
 
1354
1356
  def test_default_exact(self):
1355
1357
  """Test a default search for an "exact" value."""
1356
- params = {"q": "1234"}
1358
+ params = {"q": "12345"}
1357
1359
  self.assertQuerysetEqualAndNotEmpty(
1358
- self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact="1234")
1360
+ self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=12345)
1359
1361
  )
1360
1362
  asn = (
1361
- dcim_models.Location.objects.exclude(asn="1234")
1363
+ dcim_models.Location.objects.exclude(asn="12345")
1362
1364
  .exclude(asn__isnull=True)
1363
1365
  .values_list("asn", flat=True)
1364
1366
  .first()
1365
1367
  )
1366
1368
  params = {"q": str(asn)}
1367
1369
  self.assertQuerysetEqualAndNotEmpty(
1368
- self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=str(asn))
1370
+ self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=asn)
1369
1371
  )
1370
1372
 
1371
1373
  def test_default_id(self):
@@ -1389,9 +1391,9 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
1389
1391
 
1390
1392
  q = filters.SearchFilter(filter_predicates={"asn": {"lookup_expr": "exact", "preprocessor": int}})
1391
1393
 
1392
- params = {"q": "1234"}
1394
+ params = {"q": "12345"}
1393
1395
  self.assertQuerysetEqualAndNotEmpty(
1394
- MyLocationFilterSet(params, self.queryset).qs, self.queryset.filter(asn__exact="1234")
1396
+ MyLocationFilterSet(params, self.queryset).qs, self.queryset.filter(asn__exact="12345")
1395
1397
  )
1396
1398
  params = {"q": "123"}
1397
1399
  # Both querysets are empty so we dont use assertQuerysetEqualAndNotEmpty here.
@@ -1403,7 +1405,7 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
1403
1405
 
1404
1406
  q = filters.SearchFilter(filter_predicates={"asn": {"lookup_expr": "exact", "preprocessor": dict}})
1405
1407
 
1406
- params = {"q": "1234"}
1408
+ params = {"q": "12345"}
1407
1409
  # Both querysets are empty so we dont use assertQuerysetEqualAndNotEmpty here.
1408
1410
  self.assertEqual(self.get_filterset_count(params, MyLocationFilterSet2), 0)
1409
1411
 
@@ -637,11 +637,14 @@ class DynamicFilterFormTest(TestCase):
637
637
  form._get_lookup_field_choices(),
638
638
  [
639
639
  ("color", "Color"),
640
+ ("contacts", "Contacts (name or ID)"),
640
641
  ("content_types", "Content type(s)"),
641
642
  ("created", "Created"),
643
+ ("dynamic_groups", "Dynamic groups (name or ID)"),
642
644
  ("id", "Id"),
643
645
  ("last_updated", "Last updated"),
644
646
  ("name", "Name"),
647
+ ("teams", "Teams (name or ID)"),
645
648
  ],
646
649
  )
647
650
  self.assertEqual(
@@ -655,9 +658,11 @@ class DynamicFilterFormTest(TestCase):
655
658
  ("contact_email", "Contact E-mail"),
656
659
  ("contact_name", "Contact name"),
657
660
  ("contact_phone", "Contact phone"),
661
+ ("contacts", "Contacts (name or ID)"),
658
662
  ("created", "Created"),
659
663
  ("description", "Description"),
660
664
  ("devices", "Devices (name or ID)"),
665
+ ("dynamic_groups", "Dynamic groups (name or ID)"),
661
666
  ("cf_example_app_auto_custom_field", "Example App Automatically Added Custom Field"),
662
667
  ("facility", "Facility"),
663
668
  ("has_vlan_groups", "Has VLAN groups"),
@@ -687,6 +692,7 @@ class DynamicFilterFormTest(TestCase):
687
692
  ("status", "Status (name or ID)"),
688
693
  ("vlans", "Tagged VLANs (VID or ID)"),
689
694
  ("tags", "Tags"),
695
+ ("teams", "Teams (name or ID)"),
690
696
  ("tenant_id", 'Tenant (ID) (deprecated, use "tenant" filter instead)'),
691
697
  ("tenant", "Tenant (name or ID)"),
692
698
  ("tenant_group", "Tenant Group (name or ID)"),
@@ -723,11 +729,14 @@ class DynamicFilterFormTest(TestCase):
723
729
  [
724
730
  (None, "---------"),
725
731
  ("color", "Color"),
732
+ ("contacts", "Contacts (name or ID)"),
726
733
  ("content_types", "Content type(s)"),
727
734
  ("created", "Created"),
735
+ ("dynamic_groups", "Dynamic groups (name or ID)"),
728
736
  ("id", "Id"),
729
737
  ("last_updated", "Last updated"),
730
738
  ("name", "Name"),
739
+ ("teams", "Teams (name or ID)"),
731
740
  ],
732
741
  )
733
742
  self.assertEqual(
@@ -52,6 +52,8 @@ from nautobot.dcim.models import (
52
52
  InterfaceRedundancyGroupAssociation,
53
53
  Location,
54
54
  LocationType,
55
+ Manufacturer,
56
+ Module,
55
57
  PowerFeed,
56
58
  PowerOutlet,
57
59
  PowerPanel,
@@ -93,15 +95,13 @@ User = get_user_model()
93
95
 
94
96
 
95
97
  class GraphQLTestCaseBase(TestCase):
96
- @classmethod
97
- def setUpTestData(cls):
98
- # TODO: the below *shouldn't* be needed, but without it, when run with --parallel test flag,
99
- # these tests consistently fail with schema construction errors
100
- cls.SCHEMA = graphene_settings.SCHEMA # not a no-op; this causes the schema to be built
98
+ def setUp(self):
99
+ self.SCHEMA = graphene_settings.SCHEMA # not a no-op; this causes the schema to be built when first called
101
100
 
102
101
 
103
102
  class GraphQLTestCase(GraphQLTestCaseBase):
104
103
  def setUp(self):
104
+ super().setUp()
105
105
  self.user = create_test_user("graphql_testuser")
106
106
  GraphQLQuery.objects.create(name="GQL 1", query="{ query: locations {name} }")
107
107
  GraphQLQuery.objects.create(name="GQL 2", query="query ($name: [String!]) { locations(name:$name) {name} }")
@@ -178,7 +178,7 @@ class GraphQLTestCase(GraphQLTestCaseBase):
178
178
  for app_label, models in registry["model_features"]["graphql"].items():
179
179
  for model_name in models:
180
180
  model = apps.get_model(app_label=app_label, model_name=model_name)
181
- self.assertIsNotNone(graphene_django_registry.get_type_for_model(model))
181
+ self.assertIsNotNone(graphene_django_registry.get_type_for_model(model), model)
182
182
 
183
183
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
184
184
  def test_graphql_url_field(self):
@@ -238,6 +238,7 @@ class GraphQLGenerateSchemaTypeTestCase(GraphQLTestCaseBase):
238
238
 
239
239
  class GraphQLExtendSchemaType(GraphQLTestCaseBase):
240
240
  def setUp(self):
241
+ super().setUp()
241
242
  self.datas = (
242
243
  {"field_name": "my_text", "field_type": CustomFieldTypeChoices.TYPE_TEXT},
243
244
  {
@@ -329,6 +330,7 @@ class GraphQLExtendSchemaType(GraphQLTestCaseBase):
329
330
 
330
331
  class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
331
332
  def setUp(self):
333
+ super().setUp()
332
334
  location_ct = ContentType.objects.get_for_model(Location)
333
335
  rack_ct = ContentType.objects.get_for_model(Rack)
334
336
  vlan_ct = ContentType.objects.get_for_model(VLAN)
@@ -455,6 +457,7 @@ class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
455
457
 
456
458
  class GraphQLSearchParameters(GraphQLTestCaseBase):
457
459
  def setUp(self):
460
+ super().setUp()
458
461
  self.schema = generate_schema_type(app_name="dcim", model=Location)
459
462
 
460
463
  def test_search_parameters(self):
@@ -476,8 +479,6 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
476
479
  @classmethod
477
480
  def setUpTestData(cls):
478
481
  """Initialize the Database with some datas and multiple users associated with different permissions."""
479
- super().setUpTestData()
480
-
481
482
  cls.groups = (
482
483
  Group.objects.create(name="Group 1"),
483
484
  Group.objects.create(name="Group 2"),
@@ -705,22 +706,23 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
705
706
  @classmethod
706
707
  def setUpTestData(cls):
707
708
  """Initialize the Database with some datas."""
708
- super().setUpTestData()
709
709
  cls.user = User.objects.create(username="Super User", is_active=True, is_superuser=True)
710
710
 
711
- # Remove random IPAddress and Device fixtures for this custom test
711
+ # Remove random IPAddress, Module and Device fixtures for this custom test
712
712
  IPAddress.objects.all().delete()
713
713
  Controller.objects.filter(controller_device__isnull=False).delete()
714
714
  Device.objects.all().delete()
715
+ Module.objects.all().delete()
715
716
 
716
717
  # Initialize fake request that will be required to execute GraphQL query
717
718
  cls.request = RequestFactory().request(SERVER_NAME="WebRequestContext")
718
719
  cls.request.id = uuid.uuid4()
719
720
  cls.request.user = cls.user
720
721
 
721
- # Populate Data
722
- cls.device_type1 = DeviceType.objects.first()
723
- cls.device_type2 = DeviceType.objects.last()
722
+ # Populate Data - create new device types with no component templates
723
+ manufacturer = Manufacturer.objects.first()
724
+ cls.device_type1 = DeviceType.objects.create(manufacturer=manufacturer, model="test device_type1")
725
+ cls.device_type2 = DeviceType.objects.create(manufacturer=manufacturer, model="test device_type2")
724
726
  roles = Role.objects.get_for_model(Device)
725
727
  cls.device_role1 = roles[0]
726
728
  cls.device_role2 = roles[1]
@@ -863,6 +865,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
863
865
  ]
864
866
 
865
867
  interface_status = Status.objects.get_for_model(Interface).first()
868
+ interface_role = Role.objects.get_for_model(Interface).first()
866
869
  cls.interface11 = Interface.objects.create(
867
870
  name="Int1",
868
871
  type=InterfaceTypeChoices.TYPE_VIRTUAL,
@@ -871,12 +874,14 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
871
874
  mode=InterfaceModeChoices.MODE_ACCESS,
872
875
  untagged_vlan=cls.vlan1,
873
876
  status=interface_status,
877
+ role=interface_role,
874
878
  )
875
879
  cls.interface12 = Interface.objects.create(
876
880
  name="Int2",
877
881
  type=InterfaceTypeChoices.TYPE_VIRTUAL,
878
882
  device=cls.device1,
879
883
  status=interface_status,
884
+ role=interface_role,
880
885
  )
881
886
  cls.namespace = Namespace.objects.first()
882
887
  cls.intr_group_status = Status.objects.get_for_model(InterfaceRedundancyGroup).first()
@@ -958,6 +963,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
958
963
  untagged_vlan=cls.vlan2,
959
964
  mode=InterfaceModeChoices.MODE_ACCESS,
960
965
  status=interface_status,
966
+ role=interface_role,
961
967
  )
962
968
  cls.interface22 = Interface.objects.create(
963
969
  name="Int2",
@@ -984,7 +990,11 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
984
990
  )
985
991
 
986
992
  cls.interface31 = Interface.objects.create(
987
- name="Int1", type=InterfaceTypeChoices.TYPE_VIRTUAL, device=cls.device3, status=interface_status
993
+ name="Int1",
994
+ type=InterfaceTypeChoices.TYPE_VIRTUAL,
995
+ device=cls.device3,
996
+ status=interface_status,
997
+ role=interface_role,
988
998
  )
989
999
  cls.interface31 = Interface.objects.create(
990
1000
  name="Mgmt1",
@@ -993,6 +1003,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
993
1003
  mgmt_only=True,
994
1004
  enabled=False,
995
1005
  status=interface_status,
1006
+ role=interface_role,
996
1007
  )
997
1008
 
998
1009
  cable_statuses = Status.objects.get_for_model(Cable)
@@ -2197,7 +2208,7 @@ query {
2197
2208
  self.assertIsNone(result.errors)
2198
2209
  self.assertIsInstance(result.data, dict, result)
2199
2210
  self.assertIsInstance(result.data["device_types"], list, result)
2200
- self.assertEqual(result.data["device_types"][0]["model"], self.device_type1.model, result)
2211
+ self.assertEqual(result.data["device_types"][0]["model"], DeviceType.objects.first().model, result)
2201
2212
 
2202
2213
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2203
2214
  def test_query_interface_pagination(self):
@@ -2233,7 +2244,7 @@ query {
2233
2244
  self.assertEqual(interface_names, ["Int2", "Int1"])
2234
2245
 
2235
2246
  result_2 = self.execute_query(query_all)
2236
- self.assertEqual(len(result_2.data.get("interfaces", [])), 6)
2247
+ self.assertEqual(len(result_2.data.get("interfaces", [])), Interface.objects.count())
2237
2248
 
2238
2249
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2239
2250
  def test_query_power_feeds_cable_peer(self):
@@ -1,18 +1,25 @@
1
+ from datetime import timedelta
1
2
  from pathlib import Path
2
3
 
3
4
  from django.contrib.contenttypes.models import ContentType
5
+ from django.core.cache import cache
4
6
  from django.core.files.base import ContentFile
7
+ from django.utils import timezone
5
8
  import yaml
6
9
 
10
+ from nautobot.core.jobs.cleanup import CleanupTypes
7
11
  from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
8
12
  from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
9
13
  from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
14
+ from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
10
15
  from nautobot.extras.models import (
11
16
  Contact,
12
17
  ContactAssociation,
13
18
  ExportTemplate,
14
19
  FileProxy,
15
20
  JobLogEntry,
21
+ JobResult,
22
+ ObjectChange,
16
23
  Role,
17
24
  Status,
18
25
  )
@@ -413,3 +420,118 @@ class ImportObjectsTestCase(TransactionTestCase):
413
420
  )
414
421
 
415
422
  self.assertEqual(associations_job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
423
+
424
+
425
+ class LogsCleanupTestCase(TransactionTestCase):
426
+ """
427
+ Test the LogsCleanup system job.
428
+ """
429
+
430
+ databases = ("default", "job_logs")
431
+
432
+ def setUp(self):
433
+ super().setUp()
434
+ # Recall that TransactionTestCase truncates the DB after each test case...
435
+ cache.delete("nautobot.extras.utils.change_logged_models_queryset")
436
+ if ObjectChange.objects.count() < 2:
437
+ ObjectChangeFactory.create_batch(40)
438
+ if JobResult.objects.count() < 2:
439
+ JobResultFactory.create_batch(20)
440
+
441
+ def test_cleanup_without_permission(self):
442
+ """Job should enforce user permissions on the content-types being deleted."""
443
+ job_result_count = JobResult.objects.count()
444
+ job_log_entry_count = JobLogEntry.objects.count()
445
+ object_change_count = ObjectChange.objects.count()
446
+
447
+ job_result = create_job_result_and_run_job(
448
+ "nautobot.core.jobs.cleanup",
449
+ "LogsCleanup",
450
+ username=self.user.username,
451
+ cleanup_types=[CleanupTypes.JOB_RESULT],
452
+ max_age=0,
453
+ )
454
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
455
+ log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
456
+ self.assertEqual(log_error.message, f'User "{self.user}" does not have permission to delete JobResult records')
457
+ self.assertEqual(JobResult.objects.count(), job_result_count + 1)
458
+ self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
459
+ self.assertEqual(ObjectChange.objects.count(), object_change_count)
460
+
461
+ job_result = create_job_result_and_run_job(
462
+ "nautobot.core.jobs.cleanup",
463
+ "LogsCleanup",
464
+ username=self.user.username,
465
+ cleanup_types=[CleanupTypes.OBJECT_CHANGE],
466
+ max_age=0,
467
+ )
468
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
469
+ log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
470
+ self.assertEqual(
471
+ log_error.message, f'User "{self.user}" does not have permission to delete ObjectChange records'
472
+ )
473
+ self.assertEqual(JobResult.objects.count(), job_result_count + 2)
474
+ self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
475
+ self.assertEqual(ObjectChange.objects.count(), object_change_count)
476
+
477
+ def test_cleanup_with_constrained_permission(self):
478
+ """Job should only allow the user to cleanup records they have permission to delete."""
479
+ job_result_1 = JobResult.objects.last()
480
+ job_result_2 = JobResult.objects.first()
481
+ self.assertNotEqual(job_result_1.pk, job_result_2.pk)
482
+ object_change_1 = ObjectChange.objects.first()
483
+ object_change_2 = ObjectChange.objects.last()
484
+ self.assertNotEqual(object_change_1.pk, object_change_2.pk)
485
+ obj_perm = ObjectPermission(
486
+ name="Test permission",
487
+ constraints={"pk__in": [str(job_result_1.pk), str(object_change_1.pk)]},
488
+ actions=["delete"],
489
+ )
490
+ obj_perm.save()
491
+ obj_perm.users.add(self.user)
492
+ obj_perm.object_types.add(ContentType.objects.get_for_model(JobResult))
493
+ obj_perm.object_types.add(ContentType.objects.get_for_model(ObjectChange))
494
+ job_result = create_job_result_and_run_job(
495
+ "nautobot.core.jobs.cleanup",
496
+ "LogsCleanup",
497
+ username=self.user.username,
498
+ cleanup_types=[CleanupTypes.JOB_RESULT, CleanupTypes.OBJECT_CHANGE],
499
+ max_age=0,
500
+ )
501
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
502
+ self.assertEqual(job_result.result["extras.JobResult"], 1)
503
+ self.assertEqual(job_result.result["extras.ObjectChange"], 1)
504
+ with self.assertRaises(JobResult.DoesNotExist):
505
+ JobResult.objects.get(pk=job_result_1.pk)
506
+ JobResult.objects.get(pk=job_result_2.pk)
507
+ with self.assertRaises(ObjectChange.DoesNotExist):
508
+ ObjectChange.objects.get(pk=object_change_1.pk)
509
+ ObjectChange.objects.get(pk=object_change_2.pk)
510
+
511
+ def test_cleanup_job_results(self):
512
+ """With unconstrained permissions, all JobResults before the cutoff should be deleted."""
513
+ cutoff = timezone.now() - timedelta(days=60)
514
+ create_job_result_and_run_job(
515
+ "nautobot.core.jobs.cleanup",
516
+ "LogsCleanup",
517
+ cleanup_types=[CleanupTypes.JOB_RESULT],
518
+ max_age=60,
519
+ )
520
+ self.assertFalse(JobResult.objects.filter(date_done__lt=cutoff).exists())
521
+ self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
522
+ self.assertTrue(ObjectChange.objects.filter(time__lt=cutoff).exists())
523
+ self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
524
+
525
+ def test_cleanup_object_changes(self):
526
+ """With unconstrained permissions, all ObjectChanges before the cutoff should be deleted."""
527
+ cutoff = timezone.now() - timedelta(days=60)
528
+ create_job_result_and_run_job(
529
+ "nautobot.core.jobs.cleanup",
530
+ "LogsCleanup",
531
+ cleanup_types=[CleanupTypes.OBJECT_CHANGE],
532
+ max_age=60,
533
+ )
534
+ self.assertTrue(JobResult.objects.filter(date_done__lt=cutoff).exists())
535
+ self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
536
+ self.assertFalse(ObjectChange.objects.filter(time__lt=cutoff).exists())
537
+ self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())