nautobot 2.4.0b1__py3-none-any.whl → 2.4.1__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 (601) hide show
  1. nautobot/apps/__init__.py +1 -1
  2. nautobot/apps/api.py +8 -8
  3. nautobot/apps/change_logging.py +2 -2
  4. nautobot/apps/choices.py +4 -4
  5. nautobot/apps/events.py +3 -3
  6. nautobot/apps/factory.py +2 -2
  7. nautobot/apps/filters.py +1 -1
  8. nautobot/apps/forms.py +20 -20
  9. nautobot/apps/graphql.py +2 -2
  10. nautobot/apps/jobs.py +8 -8
  11. nautobot/apps/models.py +19 -19
  12. nautobot/apps/tables.py +1 -1
  13. nautobot/apps/testing.py +10 -10
  14. nautobot/apps/ui.py +2 -2
  15. nautobot/apps/utils.py +7 -7
  16. nautobot/apps/views.py +7 -7
  17. nautobot/circuits/api/serializers.py +1 -0
  18. nautobot/circuits/api/views.py +4 -8
  19. nautobot/circuits/tables.py +2 -1
  20. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  21. nautobot/circuits/views.py +3 -3
  22. nautobot/cloud/api/views.py +6 -10
  23. nautobot/cloud/models.py +1 -1
  24. nautobot/cloud/views.py +0 -16
  25. nautobot/core/api/constants.py +11 -0
  26. nautobot/core/api/fields.py +5 -5
  27. nautobot/core/api/filter_backends.py +3 -9
  28. nautobot/core/api/schema.py +13 -2
  29. nautobot/core/api/serializers.py +40 -34
  30. nautobot/core/api/views.py +56 -4
  31. nautobot/core/celery/log.py +4 -4
  32. nautobot/core/celery/schedulers.py +2 -2
  33. nautobot/core/choices.py +2 -2
  34. nautobot/core/events/__init__.py +3 -3
  35. nautobot/core/filters.py +67 -35
  36. nautobot/core/forms/__init__.py +19 -19
  37. nautobot/core/forms/fields.py +14 -11
  38. nautobot/core/forms/forms.py +33 -2
  39. nautobot/core/graphql/types.py +1 -1
  40. nautobot/core/jobs/__init__.py +28 -7
  41. nautobot/core/jobs/bulk_actions.py +285 -0
  42. nautobot/core/jobs/cleanup.py +48 -12
  43. nautobot/core/jobs/groups.py +1 -1
  44. nautobot/core/management/commands/validate_models.py +1 -1
  45. nautobot/core/models/__init__.py +3 -1
  46. nautobot/core/models/query_functions.py +2 -2
  47. nautobot/core/models/tree_queries.py +6 -3
  48. nautobot/core/settings.py +29 -2
  49. nautobot/core/settings.yaml +21 -0
  50. nautobot/core/tables.py +79 -61
  51. nautobot/core/templates/about.html +67 -0
  52. nautobot/core/templates/inc/media.html +3 -0
  53. nautobot/core/templates/inc/nav_menu.html +1 -0
  54. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  55. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  56. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  57. nautobot/core/templates/search.html +7 -0
  58. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  59. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  60. nautobot/core/templates/utilities/theme_preview.html +7 -0
  61. nautobot/core/templatetags/helpers.py +11 -2
  62. nautobot/core/testing/__init__.py +8 -8
  63. nautobot/core/testing/api.py +170 -15
  64. nautobot/core/testing/filters.py +45 -10
  65. nautobot/core/testing/forms.py +2 -0
  66. nautobot/core/testing/integration.py +86 -4
  67. nautobot/core/testing/mixins.py +7 -2
  68. nautobot/core/testing/views.py +44 -29
  69. nautobot/core/tests/integration/test_app_home.py +0 -1
  70. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  71. nautobot/core/tests/integration/test_filters.py +0 -2
  72. nautobot/core/tests/integration/test_home.py +0 -1
  73. nautobot/core/tests/integration/test_navbar.py +0 -1
  74. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  75. nautobot/core/tests/runner.py +1 -1
  76. nautobot/core/tests/test_api.py +98 -1
  77. nautobot/core/tests/test_csv.py +25 -3
  78. nautobot/core/tests/test_filters.py +209 -246
  79. nautobot/core/tests/test_forms.py +1 -0
  80. nautobot/core/tests/test_jobs.py +460 -1
  81. nautobot/core/tests/test_models.py +9 -0
  82. nautobot/core/tests/test_settings_schema.py +7 -0
  83. nautobot/core/tests/test_tables.py +100 -0
  84. nautobot/core/tests/test_utils.py +63 -1
  85. nautobot/core/tests/test_views.py +30 -3
  86. nautobot/core/ui/nav.py +1 -0
  87. nautobot/core/ui/object_detail.py +15 -1
  88. nautobot/core/urls.py +11 -0
  89. nautobot/core/utils/lookup.py +11 -8
  90. nautobot/core/utils/querysets.py +64 -0
  91. nautobot/core/utils/requests.py +24 -9
  92. nautobot/core/views/__init__.py +42 -0
  93. nautobot/core/views/generic.py +131 -197
  94. nautobot/core/views/mixins.py +126 -38
  95. nautobot/core/views/renderers.py +6 -6
  96. nautobot/dcim/api/serializers.py +56 -64
  97. nautobot/dcim/api/views.py +47 -113
  98. nautobot/dcim/constants.py +6 -13
  99. nautobot/dcim/factory.py +6 -1
  100. nautobot/dcim/filters/__init__.py +31 -2
  101. nautobot/dcim/forms.py +36 -17
  102. nautobot/dcim/graphql/types.py +2 -2
  103. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  104. nautobot/dcim/models/__init__.py +1 -1
  105. nautobot/dcim/models/device_component_templates.py +2 -2
  106. nautobot/dcim/models/device_components.py +22 -20
  107. nautobot/dcim/models/devices.py +10 -1
  108. nautobot/dcim/models/locations.py +3 -3
  109. nautobot/dcim/models/power.py +6 -5
  110. nautobot/dcim/models/racks.py +4 -4
  111. nautobot/dcim/tables/__init__.py +3 -3
  112. nautobot/dcim/tables/devices.py +7 -5
  113. nautobot/dcim/tables/devicetypes.py +2 -2
  114. nautobot/dcim/tables/racks.py +1 -1
  115. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  116. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  117. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  118. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  119. nautobot/dcim/templates/dcim/device.html +1 -9
  120. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  121. nautobot/dcim/templates/dcim/location.html +1 -9
  122. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  123. nautobot/dcim/templates/dcim/rack.html +1 -9
  124. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  125. nautobot/dcim/templates/dcim/rackreservation.html +1 -9
  126. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  127. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  128. nautobot/dcim/tests/integration/test_controller.py +62 -0
  129. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  130. nautobot/dcim/tests/integration/test_device_bulk_delete.py +189 -0
  131. nautobot/dcim/tests/integration/test_device_bulk_edit.py +181 -0
  132. nautobot/dcim/tests/test_api.py +16 -5
  133. nautobot/dcim/tests/test_filters.py +33 -0
  134. nautobot/dcim/tests/test_forms.py +51 -2
  135. nautobot/dcim/tests/test_graphql.py +52 -0
  136. nautobot/dcim/tests/test_jobs.py +118 -0
  137. nautobot/dcim/tests/test_models.py +52 -9
  138. nautobot/dcim/tests/test_views.py +21 -83
  139. nautobot/dcim/views.py +1 -13
  140. nautobot/extras/api/customfields.py +2 -2
  141. nautobot/extras/api/serializers.py +90 -85
  142. nautobot/extras/api/views.py +22 -27
  143. nautobot/extras/constants.py +2 -0
  144. nautobot/extras/filters/__init__.py +8 -6
  145. nautobot/extras/forms/base.py +2 -2
  146. nautobot/extras/forms/forms.py +139 -31
  147. nautobot/extras/forms/mixins.py +14 -6
  148. nautobot/extras/group_sync.py +3 -3
  149. nautobot/extras/health_checks.py +1 -2
  150. nautobot/extras/jobs.py +85 -18
  151. nautobot/extras/managers.py +3 -1
  152. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  153. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  154. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  155. nautobot/extras/models/__init__.py +1 -1
  156. nautobot/extras/models/contacts.py +1 -1
  157. nautobot/extras/models/customfields.py +12 -11
  158. nautobot/extras/models/groups.py +11 -9
  159. nautobot/extras/models/jobs.py +23 -4
  160. nautobot/extras/models/models.py +2 -2
  161. nautobot/extras/plugins/__init__.py +13 -2
  162. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  163. nautobot/extras/plugins/tables.py +16 -14
  164. nautobot/extras/plugins/views.py +65 -69
  165. nautobot/extras/registry.py +1 -1
  166. nautobot/extras/secrets/__init__.py +2 -2
  167. nautobot/extras/tables.py +7 -5
  168. nautobot/extras/templates/extras/dynamicgroup.html +1 -9
  169. nautobot/extras/templates/extras/job_detail.html +16 -0
  170. nautobot/extras/templates/extras/job_edit.html +1 -0
  171. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  172. nautobot/extras/templates/extras/marketplace.html +29 -11
  173. nautobot/extras/templates/extras/plugin_detail.html +32 -15
  174. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  175. nautobot/extras/templatetags/job_buttons.py +4 -4
  176. nautobot/extras/test_jobs/api_test_job.py +1 -1
  177. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  178. nautobot/extras/test_jobs/dry_run.py +1 -1
  179. nautobot/extras/test_jobs/fail.py +5 -5
  180. nautobot/extras/test_jobs/file_output.py +1 -1
  181. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  182. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  183. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  184. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  185. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  186. nautobot/extras/test_jobs/log_redaction.py +1 -1
  187. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  188. nautobot/extras/test_jobs/modify_db.py +1 -1
  189. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  190. nautobot/extras/test_jobs/object_var_required.py +1 -1
  191. nautobot/extras/test_jobs/object_vars.py +1 -1
  192. nautobot/extras/test_jobs/pass.py +3 -3
  193. nautobot/extras/test_jobs/profiling.py +1 -1
  194. nautobot/extras/test_jobs/relative_import.py +3 -3
  195. nautobot/extras/test_jobs/singleton.py +16 -0
  196. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  197. nautobot/extras/test_jobs/task_queues.py +1 -1
  198. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  199. nautobot/extras/tests/test_api.py +13 -13
  200. nautobot/extras/tests/test_customfields.py +1 -1
  201. nautobot/extras/tests/test_datasources.py +2 -1
  202. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  203. nautobot/extras/tests/test_filters.py +6 -6
  204. nautobot/extras/tests/test_forms.py +33 -1
  205. nautobot/extras/tests/test_jobs.py +178 -32
  206. nautobot/extras/tests/test_models.py +16 -10
  207. nautobot/extras/tests/test_plugins.py +62 -9
  208. nautobot/extras/tests/test_relationships.py +120 -9
  209. nautobot/extras/tests/test_views.py +56 -194
  210. nautobot/extras/utils.py +3 -2
  211. nautobot/extras/views.py +30 -98
  212. nautobot/ipam/api/fields.py +3 -3
  213. nautobot/ipam/api/serializers.py +41 -33
  214. nautobot/ipam/api/views.py +68 -117
  215. nautobot/ipam/factory.py +1 -1
  216. nautobot/ipam/filters.py +3 -2
  217. nautobot/ipam/lookups.py +101 -62
  218. nautobot/ipam/models.py +66 -16
  219. nautobot/ipam/querysets.py +2 -2
  220. nautobot/ipam/tables.py +23 -7
  221. nautobot/ipam/templates/ipam/ipaddress.html +1 -9
  222. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  223. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  224. nautobot/ipam/templates/ipam/prefix.html +1 -9
  225. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  226. nautobot/ipam/templates/ipam/vlan.html +1 -9
  227. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  228. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  229. nautobot/ipam/tests/test_api.py +436 -3
  230. nautobot/ipam/tests/test_forms.py +49 -47
  231. nautobot/ipam/tests/test_migrations.py +30 -30
  232. nautobot/ipam/tests/test_models.py +95 -34
  233. nautobot/ipam/tests/test_querysets.py +63 -1
  234. nautobot/ipam/tests/test_views.py +3 -0
  235. nautobot/ipam/utils/__init__.py +36 -6
  236. nautobot/ipam/views.py +61 -87
  237. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  238. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  239. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  240. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  241. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  242. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  243. nautobot/project-static/docs/404.html +46 -4
  244. nautobot/project-static/docs/apps/index.html +46 -4
  245. nautobot/project-static/docs/apps/nautobot-apps.html +47 -6
  246. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  247. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
  248. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
  249. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +62 -10
  250. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +59 -7
  251. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +374 -122
  252. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +90 -18
  253. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +95 -21
  254. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +53 -6
  255. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +52 -5
  256. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +79 -17
  257. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +102 -28
  258. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +108 -21
  259. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +131 -38
  260. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +239 -65
  261. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +581 -165
  262. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +109 -36
  263. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +453 -167
  264. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +493 -211
  265. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -8
  266. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +71 -15
  267. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +407 -55
  268. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +620 -205
  269. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +858 -412
  270. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +59 -7
  271. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +448 -186
  272. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +365 -147
  273. nautobot/project-static/docs/development/apps/api/configuration-view.html +46 -4
  274. nautobot/project-static/docs/development/apps/api/database-backend-config.html +46 -4
  275. nautobot/project-static/docs/development/apps/api/models/django-admin.html +46 -4
  276. nautobot/project-static/docs/development/apps/api/models/global-search.html +46 -4
  277. nautobot/project-static/docs/development/apps/api/models/graphql.html +46 -4
  278. nautobot/project-static/docs/development/apps/api/models/index.html +46 -4
  279. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +46 -4
  280. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +46 -4
  281. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +46 -4
  282. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +46 -4
  283. nautobot/project-static/docs/development/apps/api/platform-features/index.html +46 -4
  284. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +46 -4
  285. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +46 -4
  286. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +46 -4
  287. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +46 -4
  288. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +68 -7
  289. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +46 -4
  290. nautobot/project-static/docs/development/apps/api/prometheus.html +46 -4
  291. nautobot/project-static/docs/development/apps/api/setup.html +46 -4
  292. nautobot/project-static/docs/development/apps/api/testing.html +46 -4
  293. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +46 -4
  294. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +46 -4
  295. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +46 -4
  296. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +46 -4
  297. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +46 -4
  298. nautobot/project-static/docs/development/apps/api/views/base-template.html +46 -4
  299. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +46 -4
  300. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +46 -4
  301. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +46 -4
  302. nautobot/project-static/docs/development/apps/api/views/index.html +46 -4
  303. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +46 -4
  304. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +46 -4
  305. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +46 -4
  306. nautobot/project-static/docs/development/apps/api/views/notes.html +46 -4
  307. nautobot/project-static/docs/development/apps/api/views/rest-api.html +52 -6
  308. nautobot/project-static/docs/development/apps/api/views/urls.html +46 -4
  309. nautobot/project-static/docs/development/apps/index.html +46 -4
  310. nautobot/project-static/docs/development/apps/migration/code-updates.html +46 -4
  311. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +46 -4
  312. nautobot/project-static/docs/development/apps/migration/from-v1.html +46 -4
  313. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +46 -4
  314. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +46 -4
  315. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +46 -4
  316. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +46 -4
  317. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +50 -8
  318. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +46 -4
  319. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +211 -14
  320. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +46 -4
  321. nautobot/project-static/docs/development/apps/porting-from-netbox.html +46 -4
  322. nautobot/project-static/docs/development/core/application-registry.html +46 -4
  323. nautobot/project-static/docs/development/core/best-practices.html +46 -4
  324. nautobot/project-static/docs/development/core/bootstrap-ui.html +46 -4
  325. nautobot/project-static/docs/development/core/caching.html +46 -4
  326. nautobot/project-static/docs/development/core/controllers.html +46 -4
  327. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +73 -74
  328. nautobot/project-static/docs/development/core/generic-views.html +46 -4
  329. nautobot/project-static/docs/development/core/getting-started.html +249 -224
  330. nautobot/project-static/docs/development/core/homepage.html +49 -7
  331. nautobot/project-static/docs/development/core/index.html +46 -4
  332. nautobot/project-static/docs/development/core/{local-k8s.html → minikube-dev-environment-for-k8s-jobs.html} +469 -168
  333. nautobot/project-static/docs/development/core/model-checklist.html +56 -12
  334. nautobot/project-static/docs/development/core/model-features.html +46 -4
  335. nautobot/project-static/docs/development/core/natural-keys.html +46 -4
  336. nautobot/project-static/docs/development/core/navigation-menu.html +46 -4
  337. nautobot/project-static/docs/development/core/release-checklist.html +49 -7
  338. nautobot/project-static/docs/development/core/role-internals.html +46 -4
  339. nautobot/project-static/docs/development/core/settings.html +46 -4
  340. nautobot/project-static/docs/development/core/style-guide.html +49 -7
  341. nautobot/project-static/docs/development/core/templates.html +46 -4
  342. nautobot/project-static/docs/development/core/testing.html +46 -4
  343. nautobot/project-static/docs/development/core/ui-component-framework.html +369 -273
  344. nautobot/project-static/docs/development/core/user-preferences.html +46 -4
  345. nautobot/project-static/docs/development/index.html +46 -4
  346. nautobot/project-static/docs/development/jobs/index.html +216 -122
  347. nautobot/project-static/docs/development/jobs/migration/from-v1.html +46 -4
  348. nautobot/project-static/docs/index.html +54 -23
  349. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  350. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  351. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  352. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  353. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  354. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  355. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  356. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  357. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  358. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  359. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  360. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  361. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  362. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  363. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  364. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  365. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  366. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  367. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  368. nautobot/project-static/docs/objects.inv +0 -0
  369. nautobot/project-static/docs/overview/application_stack.html +47 -7
  370. nautobot/project-static/docs/overview/design_philosophy.html +46 -4
  371. nautobot/project-static/docs/release-notes/index.html +52 -12
  372. nautobot/project-static/docs/release-notes/version-1.0.html +234 -193
  373. nautobot/project-static/docs/release-notes/version-1.1.html +231 -190
  374. nautobot/project-static/docs/release-notes/version-1.2.html +306 -265
  375. nautobot/project-static/docs/release-notes/version-1.3.html +332 -291
  376. nautobot/project-static/docs/release-notes/version-1.4.html +417 -377
  377. nautobot/project-static/docs/release-notes/version-1.5.html +605 -566
  378. nautobot/project-static/docs/release-notes/version-1.6.html +904 -447
  379. nautobot/project-static/docs/release-notes/version-2.0.html +528 -489
  380. nautobot/project-static/docs/release-notes/version-2.1.html +363 -324
  381. nautobot/project-static/docs/release-notes/version-2.2.html +356 -317
  382. nautobot/project-static/docs/release-notes/version-2.3.html +997 -352
  383. nautobot/project-static/docs/release-notes/version-2.4.html +525 -101
  384. nautobot/project-static/docs/requirements.txt +2 -2
  385. nautobot/project-static/docs/search/search_index.json +1 -1
  386. nautobot/project-static/docs/sitemap.xml +295 -287
  387. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  388. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +46 -4
  389. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +46 -4
  390. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +48 -6
  391. nautobot/project-static/docs/user-guide/administration/configuration/index.html +46 -4
  392. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +46 -4
  393. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +110 -8
  394. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +46 -4
  395. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +46 -4
  396. nautobot/project-static/docs/user-guide/administration/guides/docker.html +46 -4
  397. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +46 -4
  398. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +46 -4
  399. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +46 -4
  400. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +46 -4
  401. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +46 -4
  402. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -6
  403. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +46 -4
  404. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +46 -4
  405. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +46 -4
  406. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +66 -8
  407. nautobot/project-static/docs/user-guide/administration/installation/index.html +46 -4
  408. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +47 -5
  409. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +46 -4
  410. nautobot/project-static/docs/user-guide/administration/installation/services.html +46 -4
  411. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +46 -4
  412. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +46 -4
  413. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +46 -4
  414. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +46 -4
  415. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +46 -4
  416. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +46 -4
  417. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +46 -4
  418. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +46 -4
  419. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +46 -4
  420. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +49 -8
  421. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +46 -4
  422. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +46 -4
  423. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +46 -4
  424. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +46 -4
  425. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +46 -4
  426. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +46 -4
  427. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +46 -4
  428. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +46 -4
  429. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +46 -4
  430. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +46 -4
  431. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +46 -4
  432. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +46 -4
  433. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +46 -4
  434. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +46 -4
  435. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +46 -4
  436. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +46 -4
  437. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +46 -4
  438. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +46 -4
  439. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +46 -4
  440. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +46 -4
  441. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +46 -4
  442. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +46 -4
  443. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +46 -4
  444. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +46 -4
  445. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +46 -4
  446. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +46 -4
  447. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +46 -4
  448. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +46 -4
  449. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +46 -4
  450. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +46 -4
  451. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +46 -4
  452. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +46 -4
  453. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +46 -4
  454. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +46 -4
  455. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +46 -4
  456. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +46 -4
  457. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +46 -4
  458. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +46 -4
  459. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +46 -4
  460. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +46 -4
  461. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +46 -4
  462. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +46 -4
  463. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +46 -4
  464. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +46 -4
  465. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +46 -4
  466. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +46 -4
  467. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +46 -4
  468. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +46 -4
  469. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +46 -4
  470. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +46 -4
  471. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +46 -4
  472. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +46 -4
  473. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +46 -4
  474. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +46 -4
  475. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +46 -4
  476. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +46 -4
  477. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +46 -4
  478. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +50 -12
  479. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +46 -4
  480. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +46 -4
  481. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +46 -4
  482. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +46 -4
  483. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +46 -4
  484. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +46 -4
  485. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +46 -4
  486. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +46 -4
  487. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +46 -4
  488. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +46 -4
  489. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +46 -4
  490. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +46 -4
  491. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +49 -7
  492. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +46 -4
  493. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +46 -4
  494. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +46 -4
  495. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +46 -4
  496. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +46 -4
  497. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +46 -4
  498. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +46 -4
  499. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +46 -4
  500. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +46 -4
  501. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +46 -4
  502. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +46 -4
  503. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +46 -4
  504. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +46 -4
  505. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +46 -4
  506. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +46 -4
  507. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +46 -4
  508. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +46 -4
  509. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +46 -4
  510. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +46 -4
  511. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +46 -4
  512. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +46 -4
  513. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +46 -4
  514. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +51 -7
  515. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +46 -4
  516. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  517. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  518. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  519. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  520. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  521. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  522. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  523. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  524. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  525. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  526. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  527. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  528. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  529. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +46 -4
  530. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +46 -4
  531. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +49 -7
  532. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
  533. nautobot/project-static/docs/user-guide/index.html +46 -4
  534. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +46 -4
  535. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +50 -8
  536. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +46 -4
  537. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +46 -4
  538. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +46 -4
  539. nautobot/project-static/docs/user-guide/platform-functionality/events.html +46 -4
  540. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +46 -4
  541. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +46 -4
  542. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +46 -4
  543. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +46 -4
  544. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +46 -4
  545. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +46 -4
  546. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +50 -7
  547. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +46 -4
  548. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +49 -7
  549. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +46 -4
  550. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +46 -4
  551. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
  552. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +46 -4
  553. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +46 -4
  554. nautobot/project-static/docs/user-guide/platform-functionality/note.html +46 -4
  555. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +46 -4
  556. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +47 -5
  557. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +46 -4
  558. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +46 -4
  559. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +94 -25
  560. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +74 -5
  561. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +46 -4
  562. nautobot/project-static/docs/user-guide/platform-functionality/role.html +46 -4
  563. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +46 -4
  564. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +46 -4
  565. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +46 -4
  566. nautobot/project-static/docs/user-guide/platform-functionality/status.html +46 -4
  567. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +46 -4
  568. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +46 -4
  569. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +46 -4
  570. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +46 -4
  571. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +46 -4
  572. nautobot/project-static/js/forms.js +1 -1
  573. nautobot/tenancy/api/views.py +9 -13
  574. nautobot/tenancy/views.py +4 -2
  575. nautobot/users/admin.py +1 -1
  576. nautobot/users/api/serializers.py +5 -4
  577. nautobot/users/api/views.py +3 -3
  578. nautobot/virtualization/api/serializers.py +4 -4
  579. nautobot/virtualization/api/views.py +5 -24
  580. nautobot/virtualization/filters.py +20 -3
  581. nautobot/virtualization/models.py +1 -1
  582. nautobot/virtualization/tables.py +2 -2
  583. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  584. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
  585. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  586. nautobot/virtualization/tests/test_filters.py +17 -0
  587. nautobot/wireless/filters.py +2 -2
  588. nautobot/wireless/forms.py +1 -1
  589. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  590. nautobot/wireless/tests/integration/__init__.py +0 -0
  591. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  592. nautobot/wireless/tests/test_filters.py +29 -1
  593. nautobot/wireless/tests/test_views.py +22 -1
  594. nautobot/wireless/views.py +0 -10
  595. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/METADATA +6 -6
  596. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/RECORD +600 -550
  597. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/WHEEL +1 -1
  598. nautobot/core/fixtures/user-data.json +0 -59
  599. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/LICENSE.txt +0 -0
  600. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/NOTICE +0 -0
  601. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/entry_points.txt +0 -0
@@ -37,7 +37,9 @@ from nautobot.extras.choices import (
37
37
  ObjectChangeEventContextChoices,
38
38
  )
39
39
  from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
40
- from nautobot.extras.jobs import get_job, get_jobs
40
+ from nautobot.extras.jobs import BaseJob, get_job, get_jobs
41
+ from nautobot.extras.models import Job, JobQueue
42
+ from nautobot.extras.models.jobs import JobLogEntry
41
43
 
42
44
 
43
45
  class JobTest(TestCase):
@@ -45,6 +47,11 @@ class JobTest(TestCase):
45
47
  Test job features that don't require a transaction test case.
46
48
  """
47
49
 
50
+ def test_as_form_no_job_model(self):
51
+ """Job.as_form() test with no corresponding job_model (https://github.com/nautobot/nautobot/issues/6773)."""
52
+ form = BaseJob.as_form()
53
+ self.assertSequenceEqual(list(form.fields.keys()), ["_job_queue", "_profile"])
54
+
48
55
  def test_field_default(self):
49
56
  """
50
57
  Job test with field that is a default value that is falsey.
@@ -53,7 +60,7 @@ class JobTest(TestCase):
53
60
  module = "field_default"
54
61
  name = "TestFieldDefault"
55
62
  job_class = get_job(f"{module}.{name}")
56
- form = job_class().as_form()
63
+ form = job_class.as_form()
57
64
 
58
65
  self.assertInHTML(
59
66
  """<tr><th><label for="id_var_int">Var int:</label></th><td>
@@ -72,7 +79,7 @@ class JobTest(TestCase):
72
79
  module = "field_order"
73
80
  name = "TestFieldOrder"
74
81
  job_class = get_job(f"{module}.{name}")
75
- form = job_class().as_form()
82
+ form = job_class.as_form()
76
83
  self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_job_queue", "_profile"])
77
84
 
78
85
  def test_no_field_order(self):
@@ -82,7 +89,7 @@ class JobTest(TestCase):
82
89
  module = "no_field_order"
83
90
  name = "TestNoFieldOrder"
84
91
  job_class = get_job(f"{module}.{name}")
85
- form = job_class().as_form()
92
+ form = job_class.as_form()
86
93
  self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_job_queue", "_profile"])
87
94
 
88
95
  def test_no_field_order_inherited_variable(self):
@@ -92,7 +99,7 @@ class JobTest(TestCase):
92
99
  module = "no_field_order"
93
100
  name = "TestDefaultFieldOrderWithInheritance"
94
101
  job_class = get_job(f"{module}.{name}")
95
- form = job_class().as_form()
102
+ form = job_class.as_form()
96
103
  self.assertSequenceEqual(
97
104
  list(form.fields.keys()),
98
105
  ["testvar1", "b_testvar2", "a_testvar3", "_job_queue", "_profile"],
@@ -107,14 +114,14 @@ class JobTest(TestCase):
107
114
  # not overridden on job model, initial form field value should match job class
108
115
  job_model.dryrun_default_override = False
109
116
  job_model.save()
110
- form = job_class().as_form()
117
+ form = job_class.as_form()
111
118
  self.assertEqual(form.fields["dryrun"].initial, job_class.dryrun_default)
112
119
 
113
120
  # overridden on job model, initial form field value should match job model
114
121
  job_model.dryrun_default_override = True
115
122
  job_model.dryrun_default = not job_class.dryrun_default
116
123
  job_model.save()
117
- form = job_class().as_form()
124
+ form = job_class.as_form()
118
125
  self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
119
126
 
120
127
  def test_job_task_queues_setter(self):
@@ -143,7 +150,7 @@ class JobTest(TestCase):
143
150
  name="irrelevant", defaults={"queue_type": JobQueueTypeChoices.TYPE_CELERY}
144
151
  )
145
152
  job_model.job_queues.set([jq_1, jq_2])
146
- form = job_class().as_form()
153
+ form = job_class.as_form()
147
154
  self.assertQuerySetEqual(
148
155
  form.fields["_job_queue"].queryset,
149
156
  models.JobQueue.objects.filter(jobs=job_model),
@@ -161,7 +168,7 @@ class JobTest(TestCase):
161
168
  self.assertTrue(job_model.supports_dryrun)
162
169
 
163
170
  module = "pass"
164
- name = "TestPass"
171
+ name = "TestPassJob"
165
172
  job_class, job_model = get_job_class_and_model(module, name)
166
173
  self.assertFalse(job_class.supports_dryrun)
167
174
  self.assertFalse(job_model.supports_dryrun)
@@ -191,27 +198,31 @@ class JobTest(TestCase):
191
198
  with override_settings(JOBS_ROOT=temp_dir):
192
199
  # Create a new Job and make sure it's discovered correctly
193
200
  with open(os.path.join(temp_dir, "my_jobs.py"), "w") as fd:
194
- fd.write("""\
201
+ fd.write(
202
+ """\
195
203
  from nautobot.apps.jobs import Job, register_jobs
196
204
  class MyJob(Job):
197
205
  def run(self):
198
206
  pass
199
207
  register_jobs(MyJob)
200
- """)
208
+ """
209
+ )
201
210
  jobs_data = get_jobs(reload=True)
202
211
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
203
212
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
204
213
  # Also make sure some representative previous JOBS_ROOT jobs aren't still around:
205
214
  self.assertNotIn("dry_run.TestDryRun", jobs_data.keys())
206
- self.assertNotIn("pass.TestPass", jobs_data.keys())
215
+ self.assertNotIn("pass.TestPassJob", jobs_data.keys())
207
216
 
208
217
  # Create a second Job in the same module
209
218
  with open(os.path.join(temp_dir, "my_jobs.py"), "a") as fd:
210
- fd.write("""
219
+ fd.write(
220
+ """
211
221
  class MyOtherJob(MyJob):
212
222
  pass
213
223
  register_jobs(MyOtherJob)
214
- """)
224
+ """
225
+ )
215
226
  jobs_data = get_jobs(reload=True)
216
227
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
217
228
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -220,14 +231,16 @@ register_jobs(MyOtherJob)
220
231
 
221
232
  # Create a third Job in another module
222
233
  with open(os.path.join(temp_dir, "their_jobs.py"), "w") as fd:
223
- fd.write("""
234
+ fd.write(
235
+ """
224
236
  from nautobot.apps.jobs import Job, register_jobs
225
237
 
226
238
  class MyJob(Job):
227
239
  def run(self):
228
240
  pass
229
241
  register_jobs(MyJob)
230
- """)
242
+ """
243
+ )
231
244
  jobs_data = get_jobs(reload=True)
232
245
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
233
246
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -249,14 +262,16 @@ register_jobs(MyJob)
249
262
 
250
263
  # Create a module with an inauspicious name
251
264
  with open(os.path.join(temp_dir, "traceback.py"), "w") as fd:
252
- fd.write("""
265
+ fd.write(
266
+ """
253
267
  from nautobot.apps.jobs import Job, register_jobs
254
268
 
255
269
  class BadJob(Job):
256
270
  def run(self):
257
271
  raise RuntimeError("You ran a bad job!")
258
272
  register_jobs(BadJob)
259
- """)
273
+ """
274
+ )
260
275
  jobs_data = get_jobs(reload=True)
261
276
  self.assertIn("my_jobs.MyJob", jobs_data.keys())
262
277
  self.assertIsNotNone(get_job("my_jobs.MyJob"))
@@ -288,6 +303,105 @@ class JobTransactionTest(TransactionTestCase):
288
303
  self.request.id = uuid.uuid4()
289
304
  self.request.user = self.user
290
305
 
306
+ def test_bulk_delete_system_jobs_fail(self):
307
+ system_job_queryset = Job.objects.filter(module_name__startswith="nautobot.")
308
+ pk_list = [str(pk) for pk in system_job_queryset.values_list("pk", flat=True)[:3]]
309
+ initial_count = Job.objects.all().count()
310
+ self.add_permissions("extras.delete_job")
311
+ job_result = create_job_result_and_run_job(
312
+ "nautobot.core.jobs.bulk_actions",
313
+ "BulkDeleteObjects",
314
+ content_type=ContentType.objects.get_for_model(Job).id,
315
+ delete_all=False,
316
+ filter_query_params={},
317
+ pk_list=pk_list,
318
+ username=self.user.username,
319
+ )
320
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
321
+ error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
322
+ self.assertIn(
323
+ f"Unable to delete Job {system_job_queryset.first()}. System Job cannot be deleted", error_log.message
324
+ )
325
+ self.assertEqual(initial_count, Job.objects.all().count())
326
+
327
+ def test_job_bulk_edit_default_queue_stays_default_after_one_field_update(self):
328
+ self.add_permissions("extras.change_job", "extras.view_job")
329
+ job_class_to_test = "TestPassJob"
330
+ job_description = "default job queue test"
331
+ job_to_test = Job.objects.get(job_class_name=job_class_to_test)
332
+ queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
333
+ default_job_queue = job_to_test.default_job_queue
334
+ pk_list = list(queryset.values_list("pk", flat=True))
335
+
336
+ job_ct = ContentType.objects.get_for_model(Job)
337
+ job_result = create_job_result_and_run_job(
338
+ "nautobot.core.jobs.bulk_actions",
339
+ "BulkEditObjects",
340
+ content_type=job_ct.id,
341
+ edit_all=False,
342
+ filter_query_params={},
343
+ form_data={
344
+ "pk": pk_list,
345
+ "description": job_description,
346
+ },
347
+ username=self.user.username,
348
+ )
349
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
350
+ self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
351
+ self.assertFalse(
352
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
353
+ )
354
+ self.assertFalse(
355
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
356
+ )
357
+ instance = Job.objects.get(job_class_name=job_class_to_test)
358
+ self.assertEqual(instance.description, job_description)
359
+ self.assertEqual(instance.default_job_queue, default_job_queue)
360
+
361
+ def test_job_bulk_edit_preserve_job_queues_after_one_field_update(self):
362
+ self.add_permissions("extras.change_job", "extras.view_job")
363
+ job_class_to_test = "TestPassJob"
364
+ job_description = "job queues test"
365
+ job_to_test = Job.objects.get(job_class_name=job_class_to_test)
366
+
367
+ # Simulate 3 job queues set
368
+ job_queues = JobQueue.objects.all()[:3]
369
+ job_to_test.job_queues.set(job_queues)
370
+ job_to_test.job_queues.add(job_to_test.default_job_queue)
371
+ job_to_test.job_queues_override = True
372
+ job_to_test.save()
373
+
374
+ expected_job_queues = list(job_to_test.job_queues.values_list("pk", flat=True))
375
+
376
+ queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
377
+ pk_list = list(queryset.values_list("pk", flat=True))
378
+
379
+ job_ct = ContentType.objects.get_for_model(Job)
380
+ job_result = create_job_result_and_run_job(
381
+ "nautobot.core.jobs.bulk_actions",
382
+ "BulkEditObjects",
383
+ content_type=job_ct.id,
384
+ edit_all=False,
385
+ filter_query_params={},
386
+ form_data={
387
+ "pk": pk_list,
388
+ "description": job_description,
389
+ },
390
+ username=self.user.username,
391
+ )
392
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
393
+ self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
394
+ self.assertFalse(
395
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
396
+ )
397
+ self.assertFalse(
398
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
399
+ )
400
+
401
+ instance = Job.objects.get(job_class_name=job_class_to_test)
402
+ self.assertEqual(instance.description, job_description)
403
+ self.assertQuerySetEqual(instance.job_queues.all(), JobQueue.objects.filter(pk__in=expected_job_queues))
404
+
291
405
  def test_job_hard_time_limit_less_than_soft_time_limit(self):
292
406
  """
293
407
  Job test which produces a warning log message because the time_limit is less than the soft_time_limit.
@@ -310,7 +424,7 @@ class JobTransactionTest(TransactionTestCase):
310
424
  Job test with pass result.
311
425
  """
312
426
  module = "pass"
313
- name = "TestPass"
427
+ name = "TestPassJob"
314
428
  job_result = create_job_result_and_run_job(module, name)
315
429
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
316
430
  self.assertEqual(job_result.result, True)
@@ -344,7 +458,7 @@ class JobTransactionTest(TransactionTestCase):
344
458
  Job test with fail result.
345
459
  """
346
460
  module = "fail"
347
- name = "TestFail"
461
+ name = "TestFailJob"
348
462
  job_result = create_job_result_and_run_job(module, name)
349
463
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
350
464
  logs = job_result.job_log_entries
@@ -467,7 +581,7 @@ class JobTransactionTest(TransactionTestCase):
467
581
  "ipv6_with_mask": "2001:db8::1/64",
468
582
  "ipv6_network": "2001:db8::/64",
469
583
  }
470
- form = job_class().as_form(form_data)
584
+ form = job_class.as_form(form_data)
471
585
  self.assertTrue(form.is_valid())
472
586
 
473
587
  # Prepare the job data
@@ -579,7 +693,7 @@ class JobTransactionTest(TransactionTestCase):
579
693
  Job test to see if the latest_result property is indeed returning the most recent job result
580
694
  """
581
695
  module = "pass"
582
- name = "TestPass"
696
+ name = "TestPassJob"
583
697
  job_result_1 = create_job_result_and_run_job(module, name)
584
698
  self.assertEqual(job_result_1.status, JobResultStatusChoices.STATUS_SUCCESS)
585
699
  job_result_2 = create_job_result_and_run_job(module, name)
@@ -606,6 +720,38 @@ class JobTransactionTest(TransactionTestCase):
606
720
  self.assertTrue(profiling_result.exists())
607
721
  profiling_result.unlink()
608
722
 
723
+ def test_job_singleton(self):
724
+ module = "singleton"
725
+ name = "TestSingletonJob"
726
+
727
+ job_class, _ = get_job_class_and_model(module, name, "local")
728
+ self.assertTrue(job_class.is_singleton)
729
+ cache.set(job_class.singleton_cache_key, 1)
730
+ failed_job_result = create_job_result_and_run_job(module, name)
731
+
732
+ self.assertEqual(
733
+ failed_job_result.status, JobResultStatusChoices.STATUS_FAILURE, msg="Duplicate singleton job didn't error."
734
+ )
735
+ self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
736
+
737
+ def test_job_ignore_singleton(self):
738
+ module = "singleton"
739
+ name = "TestSingletonJob"
740
+
741
+ job_class, _ = get_job_class_and_model(module, name, "local")
742
+ self.assertTrue(job_class.is_singleton)
743
+ cache.set(job_class.singleton_cache_key, 1)
744
+ passed_job_result = create_job_result_and_run_job(
745
+ module, name, celery_kwargs={"nautobot_job_ignore_singleton_lock": True}
746
+ )
747
+
748
+ self.assertEqual(
749
+ passed_job_result.status,
750
+ JobResultStatusChoices.STATUS_SUCCESS,
751
+ msg="Duplicate singleton job didn't succeed with nautobot_job_ignore_singleton_lock=True.",
752
+ )
753
+ self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
754
+
609
755
  @mock.patch("nautobot.extras.context_managers.enqueue_webhooks")
610
756
  def test_job_fires_webhooks(self, mock_enqueue_webhooks):
611
757
  module = "atomic_transaction"
@@ -645,7 +791,7 @@ class JobFileUploadTest(TransactionTestCase):
645
791
 
646
792
  # Serialize the file to FileProxy
647
793
  data = {"file": self.test_file}
648
- form = job_class().as_form(files=data)
794
+ form = job_class.as_form(files=data)
649
795
  self.assertTrue(form.is_valid())
650
796
  serialized_data = job_class.serialize_data(form.cleaned_data)
651
797
 
@@ -675,7 +821,7 @@ class JobFileUploadTest(TransactionTestCase):
675
821
 
676
822
  # Serialize the file to FileProxy
677
823
  data = {"file": self.test_file}
678
- form = job_class().as_form(files=data)
824
+ form = job_class.as_form(files=data)
679
825
  self.assertTrue(form.is_valid())
680
826
  serialized_data = job_class.serialize_data(form.cleaned_data)
681
827
 
@@ -794,7 +940,7 @@ class RunJobManagementCommandTest(TransactionTestCase):
794
940
  def test_runjob_nochange_successful(self):
795
941
  """Basic success-path test for Jobs that don't modify the Nautobot database."""
796
942
  module = "pass"
797
- name = "TestPass"
943
+ name = "TestPassJob"
798
944
  _job_class, job_model = get_job_class_and_model(module, name)
799
945
 
800
946
  out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
@@ -880,7 +1026,7 @@ class JobButtonReceiverTest(TestCase):
880
1026
  module = "job_button_receiver"
881
1027
  name = "TestJobButtonReceiverSimple"
882
1028
  job_class, _job_model = get_job_class_and_model(module, name)
883
- form = job_class().as_form()
1029
+ form = job_class.as_form()
884
1030
  self.assertSequenceEqual(list(form.fields.keys()), ["object_pk", "object_model_name", "_job_queue", "_profile"])
885
1031
 
886
1032
  def test_hidden(self):
@@ -892,7 +1038,7 @@ class JobButtonReceiverTest(TestCase):
892
1038
  def test_is_job_button(self):
893
1039
  with self.subTest(expected=False):
894
1040
  module = "pass"
895
- name = "TestPass"
1041
+ name = "TestPassJob"
896
1042
  _job_class, job_model = get_job_class_and_model(module, name)
897
1043
  self.assertFalse(job_model.is_job_button_receiver)
898
1044
 
@@ -943,7 +1089,7 @@ class JobHookReceiverTest(TestCase):
943
1089
  module = "job_hook_receiver"
944
1090
  name = "TestJobHookReceiverLog"
945
1091
  job_class, _job_model = get_job_class_and_model(module, name)
946
- form = job_class().as_form()
1092
+ form = job_class.as_form()
947
1093
  self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_job_queue", "_profile"])
948
1094
 
949
1095
  def test_hidden(self):
@@ -955,7 +1101,7 @@ class JobHookReceiverTest(TestCase):
955
1101
  def test_is_job_hook(self):
956
1102
  with self.subTest(expected=False):
957
1103
  module = "pass"
958
- name = "TestPass"
1104
+ name = "TestPassJob"
959
1105
  _job_class, job_model = get_job_class_and_model(module, name)
960
1106
  self.assertFalse(job_model.is_job_hook_receiver)
961
1107
 
@@ -1116,7 +1262,7 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
1116
1262
  for i in range(1, 7):
1117
1263
  models.ScheduledJob.objects.create(
1118
1264
  name=f"test{i}",
1119
- task="pass.TestPass",
1265
+ task="pass.TestPassJob",
1120
1266
  interval=JobExecutionType.TYPE_FUTURE,
1121
1267
  user=self.user,
1122
1268
  start_time=timezone.now() - datetime.timedelta(days=i * 30),
@@ -1125,7 +1271,7 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
1125
1271
 
1126
1272
  models.ScheduledJob.objects.create(
1127
1273
  name="test7",
1128
- task="pass.TestPass",
1274
+ task="pass.TestPassJob",
1129
1275
  interval=JobExecutionType.TYPE_DAILY,
1130
1276
  user=self.user,
1131
1277
  start_time=timezone.now() - datetime.timedelta(days=180),
@@ -1153,7 +1299,7 @@ class ScheduledJobIntervalTestCase(TestCase):
1153
1299
  start_time = timezone.now() + datetime.timedelta(days=6)
1154
1300
  scheduled_job = models.ScheduledJob.objects.create(
1155
1301
  name="weekly_interval",
1156
- task="pass.TestPass",
1302
+ task="pass.TestPassJob",
1157
1303
  interval=JobExecutionType.TYPE_WEEKLY,
1158
1304
  user=self.user,
1159
1305
  start_time=start_time,
@@ -1083,7 +1083,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1083
1083
  @classmethod
1084
1084
  def setUpTestData(cls):
1085
1085
  # JobModel instances are automatically instantiated at startup, so we just need to look them up.
1086
- cls.local_job = JobModel.objects.get(job_class_name="TestPass")
1086
+ cls.local_job = JobModel.objects.get(job_class_name="TestPassJob")
1087
1087
  cls.job_containing_sensitive_variables = JobModel.objects.get(job_class_name="ExampleLoggingJob")
1088
1088
  cls.app_job = JobModel.objects.get(job_class_name="ExampleJob")
1089
1089
 
@@ -1095,7 +1095,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1095
1095
  self.assertEqual(self.app_job.job_class, ExampleJob)
1096
1096
 
1097
1097
  def test_class_path(self):
1098
- self.assertEqual(self.local_job.class_path, "pass.TestPass")
1098
+ self.assertEqual(self.local_job.class_path, "pass.TestPassJob")
1099
1099
  self.assertIsNotNone(self.local_job.job_class)
1100
1100
  self.assertEqual(self.local_job.class_path, self.local_job.job_class.class_path)
1101
1101
 
@@ -1832,11 +1832,11 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1832
1832
 
1833
1833
  def setUp(self):
1834
1834
  self.user = User.objects.create_user(username="scheduledjobuser")
1835
- self.job_model = JobModel.objects.get(name="TestPass")
1835
+ self.job_model = JobModel.objects.get(name="TestPassJob")
1836
1836
 
1837
1837
  self.daily_utc_job = ScheduledJob.objects.create(
1838
1838
  name="Daily UTC Job",
1839
- task="pass.TestPass",
1839
+ task="pass.TestPassJob",
1840
1840
  job_model=self.job_model,
1841
1841
  interval=JobExecutionType.TYPE_DAILY,
1842
1842
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=get_default_timezone()),
@@ -1844,7 +1844,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1844
1844
  )
1845
1845
  self.daily_est_job = ScheduledJob.objects.create(
1846
1846
  name="Daily EST Job",
1847
- task="pass.TestPass",
1847
+ task="pass.TestPassJob",
1848
1848
  job_model=self.job_model,
1849
1849
  interval=JobExecutionType.TYPE_DAILY,
1850
1850
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
@@ -1859,7 +1859,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1859
1859
  )
1860
1860
  self.crontab_est_job = ScheduledJob.objects.create(
1861
1861
  name="Crontab EST Job",
1862
- task="pass.TestPass",
1862
+ task="pass.TestPassJob",
1863
1863
  job_model=self.job_model,
1864
1864
  interval=JobExecutionType.TYPE_CUSTOM,
1865
1865
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
@@ -1868,7 +1868,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1868
1868
  )
1869
1869
  self.one_off_utc_job = ScheduledJob.objects.create(
1870
1870
  name="One-off UTC Job",
1871
- task="pass.TestPass",
1871
+ task="pass.TestPassJob",
1872
1872
  job_model=self.job_model,
1873
1873
  interval=JobExecutionType.TYPE_FUTURE,
1874
1874
  start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("UTC")),
@@ -2015,7 +2015,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2015
2015
  """Test that TYPE_CUSTOM behavior around DST is as expected."""
2016
2016
  cronjob = ScheduledJob.objects.create(
2017
2017
  name="DST Aware Cronjob",
2018
- task="pass.TestPass",
2018
+ task="pass.TestPassJob",
2019
2019
  job_model=self.job_model,
2020
2020
  enabled=False,
2021
2021
  interval=JobExecutionType.TYPE_CUSTOM,
@@ -2070,7 +2070,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2070
2070
  """Test the interaction of TYPE_DAILY around DST."""
2071
2071
  daily = ScheduledJob.objects.create(
2072
2072
  name="Daily Job",
2073
- task="pass.TestPass",
2073
+ task="pass.TestPassJob",
2074
2074
  job_model=self.job_model,
2075
2075
  enabled=False,
2076
2076
  interval=JobExecutionType.TYPE_DAILY,
@@ -2146,6 +2146,12 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2146
2146
  # entry = NautobotScheduleEntry(model=sj)
2147
2147
  # scheduler = NautobotDatabaseScheduler(app=entry.app)
2148
2148
  # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
+ # Check scheduled job runs correctly with no job queue
2150
+ # sj.job_queue = None
2151
+ # sj.save()
2152
+ # entry = NautobotScheduleEntry(model=sj)
2153
+ # scheduler = NautobotDatabaseScheduler(app=entry.app)
2154
+ # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
2155
 
2150
2156
 
2151
2157
  class SecretTest(ModelTestCases.BaseModelTestCase):
@@ -2638,7 +2644,7 @@ class JobLogEntryTest(TestCase): # TODO: change to BaseModelTestCase
2638
2644
 
2639
2645
  def setUp(self):
2640
2646
  module = "pass"
2641
- name = "TestPass"
2647
+ name = "TestPassJob"
2642
2648
  job_class = get_job(f"{module}.{name}")
2643
2649
 
2644
2650
  self.job_result = JobResult.objects.create(name=job_class.class_path, user=None)
@@ -1,5 +1,6 @@
1
- from unittest import mock, skip, skipIf
1
+ from unittest import mock, skip
2
2
 
3
+ from django.apps import apps
3
4
  from django.conf import settings
4
5
  from django.contrib.contenttypes.models import ContentType
5
6
  from django.core.exceptions import ValidationError
@@ -21,6 +22,7 @@ from nautobot.extras.models import CustomField, Relationship, RelationshipAssoci
21
22
  from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
22
23
  from nautobot.extras.plugins.utils import load_plugin
23
24
  from nautobot.extras.plugins.validators import wrap_model_clean_methods
25
+ from nautobot.extras.plugins.views import extract_app_data
24
26
  from nautobot.extras.registry import DatasourceContent, registry
25
27
  from nautobot.ipam.models import IPAddress, Namespace, Prefix
26
28
  from nautobot.tenancy.filters import TenantFilterSet
@@ -315,6 +317,61 @@ class AppListViewTest(TestCase):
315
317
  response_body = extract_page_body(response.content.decode(response.charset)).lower()
316
318
  self.assertIn("example app", response_body, msg=response_body)
317
319
 
320
+ def test_extract_app_data(self):
321
+ app_config = apps.get_app_config("example_app")
322
+ # Without a corresponding entry in marketplace_data
323
+ self.assertEqual(
324
+ extract_app_data(app_config, {"apps": []}),
325
+ {
326
+ "name": "Example Nautobot App",
327
+ "package": "example_app",
328
+ "app_label": "example_app",
329
+ "author": "Nautobot development team",
330
+ "author_email": "nautobot@example.com",
331
+ "headline": "For testing purposes only",
332
+ "description": "For testing purposes only",
333
+ "version": "1.0.0",
334
+ "home_url": "plugins:example_app:home",
335
+ "config_url": "plugins:example_app:config",
336
+ "docs_url": "plugins:example_app:docs",
337
+ },
338
+ )
339
+
340
+ # With a corresponding entry in marketplace_data
341
+ mock_marketplace_data = {
342
+ "apps": [
343
+ {
344
+ "name": "Nautobot Example App",
345
+ "use_cases": ["Example", "Development"],
346
+ "requires": [],
347
+ "docs": "https://example.com/docs/example_app/",
348
+ "headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
349
+ "description": "An example of the kinds of things a Nautobot App can do, including ...",
350
+ "package_name": "example_app",
351
+ "author": "Network to Code (NTC)",
352
+ "availability": "Open Source",
353
+ "icon": "...",
354
+ },
355
+ ],
356
+ }
357
+ self.assertEqual(
358
+ extract_app_data(app_config, mock_marketplace_data),
359
+ {
360
+ "name": "Nautobot Example App",
361
+ "package": "example_app",
362
+ "app_label": "example_app",
363
+ "author": "Network to Code (NTC)",
364
+ "author_email": "nautobot@example.com",
365
+ "availability": "Open Source",
366
+ "headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
367
+ "description": "An example of the kinds of things a Nautobot App can do, including ...",
368
+ "version": "1.0.0",
369
+ "home_url": "plugins:example_app:home",
370
+ "config_url": "plugins:example_app:config",
371
+ "docs_url": "plugins:example_app:docs",
372
+ },
373
+ )
374
+
318
375
 
319
376
  class PluginDetailViewTest(TestCase):
320
377
  def test_view_detail_anonymous(self):
@@ -800,10 +857,6 @@ class TestAppCoreViewOverrides(TestCase):
800
857
  )
801
858
 
802
859
 
803
- @skipIf(
804
- "example_plugin" not in settings.PLUGINS,
805
- "example_plugin not in settings.PLUGINS",
806
- )
807
860
  class PluginTemplateExtensionsTest(TestCase):
808
861
  """
809
862
  Test that registered TemplateExtensions inject content as expected
@@ -823,19 +876,19 @@ class PluginTemplateExtensionsTest(TestCase):
823
876
  def test_detail_view_buttons(self):
824
877
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
825
878
  response_body = extract_page_body(response.content.decode(response.charset))
826
- self.assertIn("LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
879
+ self.assertIn("APP INJECTED LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
827
880
 
828
881
  def test_detail_view_left_page(self):
829
882
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
830
883
  response_body = extract_page_body(response.content.decode(response.charset))
831
- self.assertIn("LOCATION CONTENT - LEFT PAGE", response_body, msg=response_body)
884
+ self.assertIn("App Injected Content - Left", response_body, msg=response_body)
832
885
 
833
886
  def test_detail_view_right_page(self):
834
887
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
835
888
  response_body = extract_page_body(response.content.decode(response.charset))
836
- self.assertIn("LOCATION CONTENT - RIGHT PAGE", response_body, msg=response_body)
889
+ self.assertIn("App Injected Content - Right", response_body, msg=response_body)
837
890
 
838
891
  def test_detail_view_full_width_page(self):
839
892
  response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
840
893
  response_body = extract_page_body(response.content.decode(response.charset))
841
- self.assertIn("LOCATION CONTENT - FULL WIDTH PAGE", response_body, msg=response_body)
894
+ self.assertIn("App Injected Content - Full Width", response_body, msg=response_body)