nautobot 2.4.0b1__py3-none-any.whl → 2.4.2__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 (668) 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/tests/integration/test_circuits_bulk_operations.py +43 -0
  22. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  23. nautobot/circuits/views.py +3 -3
  24. nautobot/cloud/api/views.py +6 -10
  25. nautobot/cloud/models.py +1 -1
  26. nautobot/cloud/views.py +0 -16
  27. nautobot/core/api/constants.py +11 -0
  28. nautobot/core/api/fields.py +5 -5
  29. nautobot/core/api/filter_backends.py +3 -9
  30. nautobot/core/api/schema.py +13 -2
  31. nautobot/core/api/serializers.py +40 -34
  32. nautobot/core/api/views.py +56 -4
  33. nautobot/core/apps/__init__.py +0 -5
  34. nautobot/core/celery/log.py +4 -4
  35. nautobot/core/celery/schedulers.py +2 -2
  36. nautobot/core/choices.py +2 -2
  37. nautobot/core/events/__init__.py +3 -3
  38. nautobot/core/filters.py +67 -35
  39. nautobot/core/forms/__init__.py +19 -19
  40. nautobot/core/forms/fields.py +14 -11
  41. nautobot/core/forms/forms.py +33 -2
  42. nautobot/core/graphql/types.py +1 -1
  43. nautobot/core/jobs/__init__.py +28 -7
  44. nautobot/core/jobs/bulk_actions.py +285 -0
  45. nautobot/core/jobs/cleanup.py +48 -12
  46. nautobot/core/jobs/groups.py +1 -1
  47. nautobot/core/management/commands/validate_models.py +1 -1
  48. nautobot/core/models/__init__.py +3 -1
  49. nautobot/core/models/query_functions.py +2 -2
  50. nautobot/core/models/tree_queries.py +6 -3
  51. nautobot/core/settings.py +29 -2
  52. nautobot/core/settings.yaml +21 -0
  53. nautobot/core/tables.py +79 -61
  54. nautobot/core/templates/about.html +67 -0
  55. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  56. nautobot/core/templates/inc/media.html +3 -0
  57. nautobot/core/templates/inc/nav_menu.html +1 -0
  58. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  59. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  60. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  61. nautobot/core/templates/search.html +7 -0
  62. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  63. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  64. nautobot/core/templates/utilities/theme_preview.html +7 -0
  65. nautobot/core/templatetags/helpers.py +11 -2
  66. nautobot/core/testing/__init__.py +8 -8
  67. nautobot/core/testing/api.py +170 -15
  68. nautobot/core/testing/filters.py +45 -10
  69. nautobot/core/testing/forms.py +2 -0
  70. nautobot/core/testing/integration.py +514 -5
  71. nautobot/core/testing/mixins.py +7 -2
  72. nautobot/core/testing/views.py +44 -29
  73. nautobot/core/tests/integration/test_app_home.py +0 -1
  74. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  75. nautobot/core/tests/integration/test_filters.py +0 -2
  76. nautobot/core/tests/integration/test_home.py +0 -1
  77. nautobot/core/tests/integration/test_navbar.py +0 -1
  78. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  79. nautobot/core/tests/runner.py +1 -1
  80. nautobot/core/tests/test_api.py +98 -1
  81. nautobot/core/tests/test_csv.py +25 -3
  82. nautobot/core/tests/test_filters.py +209 -246
  83. nautobot/core/tests/test_forms.py +1 -0
  84. nautobot/core/tests/test_jobs.py +492 -1
  85. nautobot/core/tests/test_models.py +9 -0
  86. nautobot/core/tests/test_settings_schema.py +7 -0
  87. nautobot/core/tests/test_tables.py +100 -0
  88. nautobot/core/tests/test_utils.py +63 -1
  89. nautobot/core/tests/test_views.py +30 -3
  90. nautobot/core/ui/nav.py +1 -0
  91. nautobot/core/ui/object_detail.py +15 -1
  92. nautobot/core/urls.py +11 -0
  93. nautobot/core/utils/git.py +7 -2
  94. nautobot/core/utils/lookup.py +11 -8
  95. nautobot/core/utils/querysets.py +64 -0
  96. nautobot/core/utils/requests.py +24 -9
  97. nautobot/core/views/__init__.py +42 -0
  98. nautobot/core/views/generic.py +131 -197
  99. nautobot/core/views/mixins.py +136 -41
  100. nautobot/core/views/renderers.py +6 -6
  101. nautobot/core/views/utils.py +2 -2
  102. nautobot/dcim/api/serializers.py +56 -64
  103. nautobot/dcim/api/views.py +47 -113
  104. nautobot/dcim/constants.py +6 -13
  105. nautobot/dcim/factory.py +6 -1
  106. nautobot/dcim/filters/__init__.py +31 -2
  107. nautobot/dcim/forms.py +48 -17
  108. nautobot/dcim/graphql/types.py +2 -2
  109. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  110. nautobot/dcim/models/__init__.py +1 -1
  111. nautobot/dcim/models/device_component_templates.py +2 -2
  112. nautobot/dcim/models/device_components.py +22 -20
  113. nautobot/dcim/models/devices.py +10 -1
  114. nautobot/dcim/models/locations.py +3 -3
  115. nautobot/dcim/models/power.py +6 -5
  116. nautobot/dcim/models/racks.py +4 -4
  117. nautobot/dcim/tables/__init__.py +3 -3
  118. nautobot/dcim/tables/devices.py +9 -6
  119. nautobot/dcim/tables/devicetypes.py +2 -2
  120. nautobot/dcim/tables/racks.py +1 -1
  121. nautobot/dcim/templates/dcim/cable.html +1 -1
  122. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  123. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  124. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  125. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  126. nautobot/dcim/templates/dcim/device/base.html +1 -1
  127. nautobot/dcim/templates/dcim/device.html +3 -11
  128. nautobot/dcim/templates/dcim/device_component.html +1 -1
  129. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  130. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  131. nautobot/dcim/templates/dcim/location.html +2 -10
  132. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  133. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  134. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  135. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  136. nautobot/dcim/templates/dcim/platform.html +1 -1
  137. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  138. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  139. nautobot/dcim/templates/dcim/rack.html +2 -10
  140. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  141. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  142. nautobot/dcim/templates/dcim/rackreservation.html +3 -11
  143. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  144. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  145. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  146. nautobot/dcim/tests/integration/test_controller.py +62 -0
  147. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  148. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  149. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  150. nautobot/dcim/tests/test_api.py +16 -5
  151. nautobot/dcim/tests/test_filters.py +33 -0
  152. nautobot/dcim/tests/test_forms.py +51 -2
  153. nautobot/dcim/tests/test_graphql.py +52 -0
  154. nautobot/dcim/tests/test_jobs.py +118 -0
  155. nautobot/dcim/tests/test_models.py +52 -9
  156. nautobot/dcim/tests/test_views.py +30 -84
  157. nautobot/dcim/views.py +13 -28
  158. nautobot/extras/api/customfields.py +2 -2
  159. nautobot/extras/api/serializers.py +123 -85
  160. nautobot/extras/api/views.py +33 -30
  161. nautobot/extras/constants.py +3 -0
  162. nautobot/extras/datasources/git.py +125 -0
  163. nautobot/extras/filters/__init__.py +8 -6
  164. nautobot/extras/forms/base.py +2 -2
  165. nautobot/extras/forms/forms.py +139 -31
  166. nautobot/extras/forms/mixins.py +14 -6
  167. nautobot/extras/group_sync.py +3 -3
  168. nautobot/extras/health_checks.py +1 -2
  169. nautobot/extras/jobs.py +85 -18
  170. nautobot/extras/managers.py +3 -1
  171. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  172. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  173. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  174. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  175. nautobot/extras/models/__init__.py +1 -1
  176. nautobot/extras/models/contacts.py +1 -1
  177. nautobot/extras/models/customfields.py +41 -23
  178. nautobot/extras/models/datasources.py +85 -0
  179. nautobot/extras/models/groups.py +11 -9
  180. nautobot/extras/models/jobs.py +23 -4
  181. nautobot/extras/models/models.py +17 -2
  182. nautobot/extras/models/relationships.py +17 -5
  183. nautobot/extras/plugins/__init__.py +13 -2
  184. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  185. nautobot/extras/plugins/tables.py +16 -14
  186. nautobot/extras/plugins/views.py +65 -69
  187. nautobot/extras/registry.py +1 -1
  188. nautobot/extras/secrets/__init__.py +2 -2
  189. nautobot/extras/signals.py +15 -1
  190. nautobot/extras/tables.py +7 -5
  191. nautobot/extras/templates/extras/computedfield.html +1 -1
  192. nautobot/extras/templates/extras/configcontext.html +1 -1
  193. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  194. nautobot/extras/templates/extras/customfield.html +1 -1
  195. nautobot/extras/templates/extras/customlink.html +1 -1
  196. nautobot/extras/templates/extras/dynamicgroup.html +2 -10
  197. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  198. nautobot/extras/templates/extras/gitrepository.html +1 -1
  199. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  200. nautobot/extras/templates/extras/job_detail.html +17 -1
  201. nautobot/extras/templates/extras/job_edit.html +1 -0
  202. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  203. nautobot/extras/templates/extras/jobhook.html +1 -1
  204. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  205. nautobot/extras/templates/extras/jobresult.html +1 -1
  206. nautobot/extras/templates/extras/marketplace.html +29 -11
  207. nautobot/extras/templates/extras/objectchange.html +1 -1
  208. nautobot/extras/templates/extras/plugin_detail.html +33 -16
  209. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  210. nautobot/extras/templates/extras/relationship.html +1 -63
  211. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  212. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  213. nautobot/extras/templates/extras/secret.html +1 -1
  214. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  215. nautobot/extras/templates/extras/status.html +1 -1
  216. nautobot/extras/templates/extras/tag.html +1 -1
  217. nautobot/extras/templates/extras/webhook.html +1 -1
  218. nautobot/extras/templatetags/job_buttons.py +4 -4
  219. nautobot/extras/test_jobs/api_test_job.py +1 -1
  220. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  221. nautobot/extras/test_jobs/dry_run.py +1 -1
  222. nautobot/extras/test_jobs/fail.py +5 -5
  223. nautobot/extras/test_jobs/file_output.py +1 -1
  224. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  225. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  226. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  227. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  228. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  229. nautobot/extras/test_jobs/log_redaction.py +1 -1
  230. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  231. nautobot/extras/test_jobs/modify_db.py +1 -1
  232. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  233. nautobot/extras/test_jobs/object_var_required.py +1 -1
  234. nautobot/extras/test_jobs/object_vars.py +1 -1
  235. nautobot/extras/test_jobs/pass.py +3 -3
  236. nautobot/extras/test_jobs/profiling.py +1 -1
  237. nautobot/extras/test_jobs/relative_import.py +3 -3
  238. nautobot/extras/test_jobs/singleton.py +16 -0
  239. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  240. nautobot/extras/test_jobs/task_queues.py +1 -1
  241. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  242. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  243. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  244. nautobot/extras/tests/git_helper.py +9 -1
  245. nautobot/extras/tests/integration/__init__.py +29 -16
  246. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  247. nautobot/extras/tests/test_api.py +19 -13
  248. nautobot/extras/tests/test_customfields.py +50 -52
  249. nautobot/extras/tests/test_datasources.py +29 -1
  250. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  251. nautobot/extras/tests/test_filters.py +6 -6
  252. nautobot/extras/tests/test_forms.py +33 -1
  253. nautobot/extras/tests/test_jobs.py +178 -32
  254. nautobot/extras/tests/test_models.py +299 -10
  255. nautobot/extras/tests/test_plugins.py +62 -9
  256. nautobot/extras/tests/test_relationships.py +120 -9
  257. nautobot/extras/tests/test_utils.py +22 -1
  258. nautobot/extras/tests/test_views.py +56 -194
  259. nautobot/extras/utils.py +20 -10
  260. nautobot/extras/views.py +85 -110
  261. nautobot/ipam/api/fields.py +3 -3
  262. nautobot/ipam/api/serializers.py +41 -33
  263. nautobot/ipam/api/views.py +68 -117
  264. nautobot/ipam/factory.py +1 -1
  265. nautobot/ipam/filters.py +3 -2
  266. nautobot/ipam/lookups.py +101 -62
  267. nautobot/ipam/models.py +74 -18
  268. nautobot/ipam/querysets.py +2 -2
  269. nautobot/ipam/tables.py +25 -9
  270. nautobot/ipam/templates/ipam/ipaddress.html +2 -10
  271. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  272. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  273. nautobot/ipam/templates/ipam/prefix.html +2 -10
  274. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  275. nautobot/ipam/templates/ipam/rir.html +1 -1
  276. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  277. nautobot/ipam/templates/ipam/service.html +1 -1
  278. nautobot/ipam/templates/ipam/vlan.html +2 -10
  279. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  280. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  281. nautobot/ipam/templates/ipam/vrf.html +1 -1
  282. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  283. nautobot/ipam/tests/test_api.py +436 -3
  284. nautobot/ipam/tests/test_forms.py +49 -47
  285. nautobot/ipam/tests/test_migrations.py +30 -30
  286. nautobot/ipam/tests/test_models.py +119 -34
  287. nautobot/ipam/tests/test_querysets.py +63 -1
  288. nautobot/ipam/tests/test_utils.py +41 -2
  289. nautobot/ipam/tests/test_views.py +3 -0
  290. nautobot/ipam/utils/__init__.py +54 -17
  291. nautobot/ipam/views.py +61 -87
  292. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  293. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  294. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  295. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  296. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  297. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  298. nautobot/project-static/docs/404.html +131 -14
  299. nautobot/project-static/docs/apps/index.html +131 -14
  300. nautobot/project-static/docs/apps/nautobot-apps.html +132 -16
  301. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  302. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.60a45f97.min.js} +2 -2
  303. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.60a45f97.min.js.map} +2 -2
  304. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  305. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  306. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  307. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +147 -20
  308. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +144 -17
  309. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +459 -132
  310. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +175 -28
  311. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +180 -31
  312. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +138 -16
  313. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +137 -15
  314. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +164 -27
  315. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +187 -38
  316. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +193 -31
  317. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +216 -48
  318. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +324 -75
  319. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +666 -175
  320. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +194 -46
  321. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +538 -177
  322. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +578 -221
  323. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +145 -18
  324. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +156 -25
  325. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +492 -65
  326. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +705 -215
  327. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +943 -422
  328. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +144 -17
  329. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +619 -200
  330. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +474 -159
  331. nautobot/project-static/docs/development/apps/api/configuration-view.html +131 -14
  332. nautobot/project-static/docs/development/apps/api/database-backend-config.html +131 -14
  333. nautobot/project-static/docs/development/apps/api/models/django-admin.html +131 -14
  334. nautobot/project-static/docs/development/apps/api/models/global-search.html +131 -14
  335. nautobot/project-static/docs/development/apps/api/models/graphql.html +131 -14
  336. nautobot/project-static/docs/development/apps/api/models/index.html +131 -14
  337. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +131 -14
  338. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +131 -14
  339. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +131 -14
  340. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +131 -14
  341. nautobot/project-static/docs/development/apps/api/platform-features/index.html +131 -14
  342. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +131 -14
  343. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +131 -14
  344. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +131 -14
  345. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +131 -14
  346. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +153 -17
  347. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +131 -14
  348. nautobot/project-static/docs/development/apps/api/prometheus.html +131 -14
  349. nautobot/project-static/docs/development/apps/api/setup.html +131 -14
  350. nautobot/project-static/docs/development/apps/api/testing.html +131 -14
  351. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +131 -14
  352. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +131 -14
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +131 -14
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +131 -14
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +131 -14
  356. nautobot/project-static/docs/development/apps/api/views/base-template.html +131 -14
  357. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +131 -14
  358. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +131 -14
  359. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +131 -14
  360. nautobot/project-static/docs/development/apps/api/views/index.html +131 -14
  361. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +131 -14
  362. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +131 -14
  363. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +131 -14
  364. nautobot/project-static/docs/development/apps/api/views/notes.html +131 -14
  365. nautobot/project-static/docs/development/apps/api/views/rest-api.html +137 -16
  366. nautobot/project-static/docs/development/apps/api/views/urls.html +131 -14
  367. nautobot/project-static/docs/development/apps/index.html +131 -14
  368. nautobot/project-static/docs/development/apps/migration/code-updates.html +131 -14
  369. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +131 -14
  370. nautobot/project-static/docs/development/apps/migration/from-v1.html +131 -14
  371. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +131 -14
  372. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +131 -14
  373. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +131 -14
  374. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +131 -14
  375. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +135 -18
  376. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +131 -14
  377. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +297 -25
  378. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +131 -14
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +131 -14
  380. nautobot/project-static/docs/development/core/application-registry.html +131 -14
  381. nautobot/project-static/docs/development/core/best-practices.html +131 -14
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +131 -14
  383. nautobot/project-static/docs/development/core/caching.html +131 -14
  384. nautobot/project-static/docs/development/core/controllers.html +131 -14
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +158 -84
  386. nautobot/project-static/docs/development/core/generic-views.html +131 -14
  387. nautobot/project-static/docs/development/core/getting-started.html +334 -234
  388. nautobot/project-static/docs/development/core/homepage.html +134 -17
  389. nautobot/project-static/docs/development/core/index.html +131 -14
  390. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9829 -0
  391. nautobot/project-static/docs/development/core/model-checklist.html +141 -22
  392. nautobot/project-static/docs/development/core/model-features.html +131 -14
  393. nautobot/project-static/docs/development/core/natural-keys.html +131 -14
  394. nautobot/project-static/docs/development/core/navigation-menu.html +131 -14
  395. nautobot/project-static/docs/development/core/release-checklist.html +134 -17
  396. nautobot/project-static/docs/development/core/role-internals.html +131 -14
  397. nautobot/project-static/docs/development/core/settings.html +131 -14
  398. nautobot/project-static/docs/development/core/style-guide.html +134 -17
  399. nautobot/project-static/docs/development/core/templates.html +132 -15
  400. nautobot/project-static/docs/development/core/testing.html +131 -14
  401. nautobot/project-static/docs/development/core/ui-component-framework.html +454 -283
  402. nautobot/project-static/docs/development/core/user-preferences.html +131 -14
  403. nautobot/project-static/docs/development/index.html +131 -14
  404. nautobot/project-static/docs/development/jobs/index.html +301 -132
  405. nautobot/project-static/docs/development/jobs/migration/from-v1.html +131 -14
  406. nautobot/project-static/docs/index.html +139 -33
  407. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  408. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  409. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  410. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  411. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  412. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  413. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  414. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  415. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  416. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  417. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  418. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  419. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  420. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  421. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  422. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  423. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  424. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  425. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  426. nautobot/project-static/docs/objects.inv +0 -0
  427. nautobot/project-static/docs/overview/application_stack.html +132 -17
  428. nautobot/project-static/docs/overview/design_philosophy.html +131 -14
  429. nautobot/project-static/docs/release-notes/index.html +137 -22
  430. nautobot/project-static/docs/release-notes/version-1.0.html +319 -203
  431. nautobot/project-static/docs/release-notes/version-1.1.html +316 -200
  432. nautobot/project-static/docs/release-notes/version-1.2.html +391 -275
  433. nautobot/project-static/docs/release-notes/version-1.3.html +417 -301
  434. nautobot/project-static/docs/release-notes/version-1.4.html +502 -387
  435. nautobot/project-static/docs/release-notes/version-1.5.html +690 -576
  436. nautobot/project-static/docs/release-notes/version-1.6.html +989 -457
  437. nautobot/project-static/docs/release-notes/version-2.0.html +613 -499
  438. nautobot/project-static/docs/release-notes/version-2.1.html +448 -334
  439. nautobot/project-static/docs/release-notes/version-2.2.html +441 -327
  440. nautobot/project-static/docs/release-notes/version-2.3.html +1171 -451
  441. nautobot/project-static/docs/release-notes/version-2.4.html +800 -111
  442. nautobot/project-static/docs/requirements.txt +2 -2
  443. nautobot/project-static/docs/search/search_index.json +1 -1
  444. nautobot/project-static/docs/sitemap.xml +303 -287
  445. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +131 -14
  447. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +131 -14
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +133 -16
  449. nautobot/project-static/docs/user-guide/administration/configuration/index.html +131 -14
  450. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +131 -14
  451. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +195 -18
  452. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +131 -14
  453. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +134 -17
  454. nautobot/project-static/docs/user-guide/administration/guides/docker.html +131 -14
  455. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +131 -14
  456. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +131 -14
  457. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -14
  458. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +131 -14
  459. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +131 -14
  460. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +133 -16
  461. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +131 -14
  462. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +131 -14
  463. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +131 -14
  464. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +151 -18
  465. nautobot/project-static/docs/user-guide/administration/installation/index.html +131 -14
  466. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +132 -15
  467. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +131 -14
  468. nautobot/project-static/docs/user-guide/administration/installation/services.html +131 -14
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +131 -14
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +131 -14
  471. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  472. nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
  473. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +131 -14
  474. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +131 -14
  475. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +131 -14
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +131 -14
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +131 -14
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +131 -14
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +131 -14
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +134 -18
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +131 -14
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +131 -14
  483. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +131 -14
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +131 -14
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +131 -14
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +131 -14
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +131 -14
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +131 -14
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +131 -14
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +131 -14
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +131 -14
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +131 -14
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +131 -14
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +131 -14
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +131 -14
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +131 -14
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +131 -14
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +131 -14
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +131 -14
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +131 -14
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +131 -14
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +131 -14
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +131 -14
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +131 -14
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +131 -14
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +131 -14
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +131 -14
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +131 -14
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +131 -14
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +131 -14
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +131 -14
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +131 -14
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +131 -14
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +131 -14
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +131 -14
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +131 -14
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +131 -14
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +131 -14
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +131 -14
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +131 -14
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +131 -14
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +131 -14
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +131 -14
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +131 -14
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +131 -14
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +131 -14
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +131 -14
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +131 -14
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +131 -14
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +131 -14
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +131 -14
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +131 -14
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +131 -14
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +131 -14
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +131 -14
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +131 -14
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +131 -14
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +135 -22
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +131 -14
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +131 -14
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +131 -14
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +131 -14
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +131 -14
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +131 -14
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +131 -14
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +131 -14
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +131 -14
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +131 -14
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +131 -14
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +131 -14
  551. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +134 -17
  552. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +131 -14
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +131 -14
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +131 -14
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +131 -14
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +131 -14
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +131 -14
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +131 -14
  559. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +131 -14
  560. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +131 -14
  561. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +131 -14
  562. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +131 -14
  563. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +131 -14
  564. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +131 -14
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +131 -14
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +131 -14
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +134 -17
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +131 -14
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +131 -14
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +131 -14
  571. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +131 -14
  572. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +131 -14
  573. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +131 -14
  574. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +236 -34
  575. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +131 -14
  576. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +131 -14
  590. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +131 -14
  591. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +134 -17
  592. nautobot/project-static/docs/{development/core/local-k8s.html → user-guide/feature-guides/wireless-networks-and-controllers.html} +632 -566
  593. nautobot/project-static/docs/user-guide/index.html +131 -14
  594. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +131 -14
  595. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +135 -18
  596. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +131 -14
  597. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +131 -14
  598. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +131 -14
  599. nautobot/project-static/docs/user-guide/platform-functionality/events.html +131 -14
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +131 -14
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +131 -14
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +451 -16
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +131 -14
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +131 -14
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +131 -14
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +135 -17
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +131 -14
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +134 -17
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +131 -14
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +131 -14
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9797 -0
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +131 -14
  613. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +131 -14
  614. nautobot/project-static/docs/user-guide/platform-functionality/note.html +131 -14
  615. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +131 -14
  616. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +132 -15
  617. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +131 -14
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +131 -14
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +179 -35
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +159 -15
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +131 -14
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +131 -14
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +131 -14
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +131 -14
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +131 -14
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +131 -14
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +131 -14
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +131 -14
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +131 -14
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +131 -14
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +131 -14
  632. nautobot/project-static/js/forms.js +1 -1
  633. nautobot/tenancy/api/views.py +9 -13
  634. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  635. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  636. nautobot/tenancy/views.py +4 -2
  637. nautobot/users/admin.py +1 -1
  638. nautobot/users/api/serializers.py +5 -4
  639. nautobot/users/api/views.py +3 -3
  640. nautobot/virtualization/api/serializers.py +4 -4
  641. nautobot/virtualization/api/views.py +5 -24
  642. nautobot/virtualization/filters.py +20 -3
  643. nautobot/virtualization/models.py +1 -1
  644. nautobot/virtualization/tables.py +2 -2
  645. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  646. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  648. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -10
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  651. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  652. nautobot/virtualization/tests/test_filters.py +17 -0
  653. nautobot/wireless/filters.py +2 -2
  654. nautobot/wireless/forms.py +1 -1
  655. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  656. nautobot/wireless/tests/integration/__init__.py +0 -0
  657. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  658. nautobot/wireless/tests/test_filters.py +29 -1
  659. nautobot/wireless/tests/test_views.py +22 -1
  660. nautobot/wireless/views.py +0 -10
  661. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/METADATA +9 -9
  662. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/RECORD +667 -610
  663. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +1 -1
  664. nautobot/core/fixtures/user-data.json +0 -59
  665. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  666. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
  667. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
  668. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  from datetime import datetime, timedelta, timezone
2
2
  import os
3
+ import shutil
3
4
  import tempfile
4
5
  from unittest import expectedFailure, mock
5
6
  import uuid
@@ -17,6 +18,7 @@ from django.test import override_settings
17
18
  from django.test.utils import isolate_apps
18
19
  from django.utils.timezone import get_default_timezone, now
19
20
  from django_celery_beat.tzcrontab import TzAwareCrontab
21
+ from git import GitCommandError
20
22
  from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
21
23
  import time_machine
22
24
 
@@ -48,6 +50,7 @@ from nautobot.extras.constants import (
48
50
  JOB_LOG_MAX_LOG_OBJECT_LENGTH,
49
51
  JOB_OVERRIDABLE_FIELDS,
50
52
  )
53
+ from nautobot.extras.datasources.registry import get_datasource_contents
51
54
  from nautobot.extras.jobs import get_job
52
55
  from nautobot.extras.models import (
53
56
  ComputedField,
@@ -83,6 +86,7 @@ from nautobot.extras.models import (
83
86
  from nautobot.extras.models.statuses import StatusModel
84
87
  from nautobot.extras.registry import registry
85
88
  from nautobot.extras.secrets.exceptions import SecretParametersError, SecretProviderError, SecretValueNotFoundError
89
+ from nautobot.extras.tests.git_helper import create_and_populate_git_repository
86
90
  from nautobot.ipam.models import IPAddress
87
91
  from nautobot.tenancy.models import Tenant
88
92
  from nautobot.virtualization.models import (
@@ -1072,6 +1076,285 @@ class GitRepositoryTest(ModelTestCases.BaseModelTestCase):
1072
1076
  self.repo.remote_url = "http://some-private-host/example.git"
1073
1077
  self.repo.validated_save()
1074
1078
 
1079
+ def test_clone_to_directory_context_manager(self):
1080
+ """Confirm that the clone_to_directory_context() context manager method works as expected."""
1081
+ try:
1082
+ specified_path = tempfile.mkdtemp()
1083
+ self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
1084
+ create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
1085
+ self.repo_slug = "new_git_repo"
1086
+ self.repo = GitRepository(
1087
+ name="New Git Repository",
1088
+ slug=self.repo_slug,
1089
+ remote_url="file://"
1090
+ + self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
1091
+ branch="main",
1092
+ # Provide everything we know we can provide
1093
+ provided_contents=[
1094
+ entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
1095
+ ],
1096
+ )
1097
+ self.repo.save()
1098
+ with self.subTest("Clone a repository with no path argument provided"):
1099
+ with self.repo.clone_to_directory_context() as path:
1100
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1101
+ self.assertTrue(path.startswith(tempfile.gettempdir()))
1102
+ self.assertTrue(os.path.exists(path))
1103
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1104
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1105
+ self.assertFalse(os.path.exists(path))
1106
+
1107
+ with self.subTest("Clone a repository with a path argument provided"):
1108
+ with self.repo.clone_to_directory_context(path=specified_path) as path:
1109
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1110
+ self.assertTrue(path.startswith(specified_path))
1111
+ self.assertTrue(os.path.exists(path))
1112
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1113
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1114
+ # Temp directory is cleaned up after the context manager exits
1115
+ self.assertFalse(os.path.exists(path))
1116
+
1117
+ with self.subTest("Clone a repository with the branch argument provided"):
1118
+ with self.repo.clone_to_directory_context(path=specified_path, branch="main") as path:
1119
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1120
+ self.assertTrue(path.startswith(specified_path))
1121
+ self.assertTrue(os.path.exists(path))
1122
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1123
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1124
+ # Temp directory is cleaned up after the context manager exits
1125
+ self.assertFalse(os.path.exists(path))
1126
+
1127
+ with self.subTest("Clone a repository with non-default branch provided"):
1128
+ with self.repo.clone_to_directory_context(path=specified_path, branch="empty-repo") as path:
1129
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1130
+ self.assertTrue(path.startswith(specified_path))
1131
+ self.assertTrue(os.path.exists(path))
1132
+ # empty-repo should contain no files
1133
+ self.assertFalse(os.path.exists(path + "/config_context_schemas"))
1134
+ self.assertFalse(os.path.exists(path + "/config_contexts"))
1135
+ # Temp directory is cleaned up after the context manager exits
1136
+ self.assertFalse(os.path.exists(path))
1137
+
1138
+ with self.subTest("Clone a repository with divergent branch provided"):
1139
+ with self.repo.clone_to_directory_context(path=specified_path, branch="divergent-branch") as path:
1140
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1141
+ self.assertTrue(path.startswith(specified_path))
1142
+ self.assertTrue(os.path.exists(path))
1143
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1144
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1145
+ # Temp directory is cleaned up after the context manager exits
1146
+ self.assertFalse(os.path.exists(path))
1147
+
1148
+ with self.subTest("Clone a repository with the head argument provided"):
1149
+ with self.repo.clone_to_directory_context(path=specified_path, head="valid-files") as path:
1150
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1151
+ self.assertTrue(path.startswith(specified_path))
1152
+ self.assertTrue(os.path.exists(path))
1153
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
1154
+ self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
1155
+ # Temp directory is cleaned up after the context manager exits
1156
+ self.assertFalse(os.path.exists(path))
1157
+
1158
+ with self.subTest("Clone a repository with depth argument provided"):
1159
+ with self.repo.clone_to_directory_context(path=specified_path, depth=1) as path:
1160
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1161
+ self.assertTrue(path.startswith(specified_path))
1162
+ self.assertTrue(os.path.exists(path))
1163
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1164
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1165
+ # Temp directory is cleaned up after the context manager exits
1166
+ self.assertFalse(os.path.exists(path))
1167
+
1168
+ with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
1169
+ with self.repo.clone_to_directory_context(
1170
+ path=specified_path, depth=1, head="divergent-branch-tag"
1171
+ ) as path:
1172
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1173
+ self.assertTrue(path.startswith(specified_path))
1174
+ self.assertTrue(os.path.exists(path))
1175
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1176
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1177
+ # Temp directory is cleaned up after the context manager exits
1178
+ self.assertFalse(os.path.exists(path))
1179
+
1180
+ with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
1181
+ with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
1182
+ # Shallow copy a repo should only have the latest commit
1183
+ with self.repo.clone_to_directory_context(path=specified_path, depth=1, head="valid-files") as path:
1184
+ pass
1185
+
1186
+ with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
1187
+ with self.repo.clone_to_directory_context(path=specified_path, depth=1, branch="main") as path:
1188
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1189
+ self.assertTrue(path.startswith(specified_path))
1190
+ self.assertTrue(os.path.exists(path))
1191
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1192
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1193
+ # Temp directory is cleaned up after the context manager exits
1194
+ self.assertFalse(os.path.exists(path))
1195
+
1196
+ with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
1197
+ with self.repo.clone_to_directory_context(
1198
+ path=specified_path, depth=1, branch="divergent-branch"
1199
+ ) as path:
1200
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1201
+ self.assertTrue(path.startswith(specified_path))
1202
+ self.assertTrue(os.path.exists(path))
1203
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1204
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1205
+ # Temp directory is cleaned up after the context manager exits
1206
+ self.assertFalse(os.path.exists(path))
1207
+
1208
+ with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
1209
+ with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
1210
+ with self.repo.clone_to_directory_context(path=specified_path, head="non-existent") as path:
1211
+ pass
1212
+
1213
+ with self.subTest("Assert a value error is raised when branch and head are both provided"):
1214
+ with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
1215
+ with self.repo.clone_to_directory_context(branch="main", head="valid-files") as path:
1216
+ pass
1217
+ finally:
1218
+ shutil.rmtree(specified_path, ignore_errors=True)
1219
+ shutil.rmtree(self.tempdir.name, ignore_errors=True)
1220
+
1221
+ def test_clone_to_directory_helper_methods(self):
1222
+ """Confirm that the clone_to_directory()/cleanup_cloned_directory() methods work as expected."""
1223
+ try:
1224
+ specified_path = tempfile.mkdtemp()
1225
+ self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
1226
+ create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
1227
+ self.repo_slug = "new_git_repo"
1228
+ self.repo = GitRepository(
1229
+ name="New Git Repository",
1230
+ slug=self.repo_slug,
1231
+ remote_url="file://"
1232
+ + self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
1233
+ branch="main",
1234
+ # Provide everything we know we can provide
1235
+ provided_contents=[
1236
+ entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
1237
+ ],
1238
+ )
1239
+ self.repo.save()
1240
+ with self.subTest("Clone a repository with no path argument provided"):
1241
+ path = self.repo.clone_to_directory()
1242
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1243
+ self.assertTrue(path.startswith(tempfile.gettempdir()))
1244
+ self.assertTrue(os.path.exists(path))
1245
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1246
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1247
+ self.repo.cleanup_cloned_directory(path)
1248
+ self.assertFalse(os.path.exists(path))
1249
+
1250
+ with self.subTest("Clone a repository with a path argument provided"):
1251
+ path = self.repo.clone_to_directory(path=specified_path)
1252
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1253
+ self.assertTrue(path.startswith(specified_path))
1254
+ self.assertTrue(os.path.exists(path))
1255
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1256
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1257
+ self.repo.cleanup_cloned_directory(path)
1258
+ self.assertFalse(os.path.exists(path))
1259
+
1260
+ with self.subTest("Clone a repository with the branch argument provided"):
1261
+ path = self.repo.clone_to_directory(path=specified_path, branch="main")
1262
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1263
+ self.assertTrue(path.startswith(specified_path))
1264
+ self.assertTrue(os.path.exists(path))
1265
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1266
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
1267
+ self.repo.cleanup_cloned_directory(path)
1268
+ self.assertFalse(os.path.exists(path))
1269
+
1270
+ with self.subTest("Clone a repository with non-default branch provided"):
1271
+ path = self.repo.clone_to_directory(path=specified_path, branch="empty-repo")
1272
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1273
+ self.assertTrue(path.startswith(specified_path))
1274
+ self.assertTrue(os.path.exists(path))
1275
+ self.assertFalse(os.path.exists(path + "/config_context_schemas"))
1276
+ self.assertFalse(os.path.exists(path + "/config_contexts"))
1277
+ self.repo.cleanup_cloned_directory(path)
1278
+ self.assertFalse(os.path.exists(path))
1279
+
1280
+ with self.subTest("Clone a repository with divergent branch provided"):
1281
+ path = self.repo.clone_to_directory(path=specified_path, branch="divergent-branch")
1282
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1283
+ self.assertTrue(path.startswith(specified_path))
1284
+ self.assertTrue(os.path.exists(path))
1285
+ self.assertTrue(os.path.exists(path + "/config_context_schemas"))
1286
+ self.assertTrue(os.path.exists(path + "/config_contexts"))
1287
+ self.repo.cleanup_cloned_directory(path)
1288
+ self.assertFalse(os.path.exists(path))
1289
+
1290
+ with self.subTest("Clone a repository with the head argument provided"):
1291
+ path = self.repo.clone_to_directory(path=specified_path, head="valid-files")
1292
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1293
+ self.assertTrue(path.startswith(specified_path))
1294
+ self.assertTrue(os.path.exists(path))
1295
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
1296
+ self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
1297
+ self.repo.cleanup_cloned_directory(path)
1298
+ self.assertFalse(os.path.exists(path))
1299
+
1300
+ with self.subTest("Clone a repository with depth argument provided"):
1301
+ path = self.repo.clone_to_directory(path=specified_path, depth=1)
1302
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1303
+ self.assertTrue(path.startswith(specified_path))
1304
+ self.assertTrue(os.path.exists(path))
1305
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1306
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1307
+ self.repo.cleanup_cloned_directory(path)
1308
+ self.assertFalse(os.path.exists(path))
1309
+
1310
+ with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
1311
+ path = self.repo.clone_to_directory(path=specified_path, depth=1, head="divergent-branch-tag")
1312
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1313
+ self.assertTrue(path.startswith(specified_path))
1314
+ self.assertTrue(os.path.exists(path))
1315
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1316
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1317
+ self.repo.cleanup_cloned_directory(path)
1318
+ self.assertFalse(os.path.exists(path))
1319
+
1320
+ with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
1321
+ with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
1322
+ # Shallow copy a repo should only have the latest commit
1323
+ path = self.repo.clone_to_directory(path=specified_path, depth=1, head="valid-files")
1324
+ self.repo.cleanup_cloned_directory(path)
1325
+ self.assertFalse(os.path.exists(path))
1326
+
1327
+ with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
1328
+ path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="main")
1329
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1330
+ self.assertTrue(path.startswith(specified_path))
1331
+ self.assertTrue(os.path.exists(path))
1332
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1333
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1334
+ self.repo.cleanup_cloned_directory(path)
1335
+ self.assertFalse(os.path.exists(path))
1336
+
1337
+ with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
1338
+ path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="divergent-branch")
1339
+ # assert that the temporary directory was created in the expected location i.e. /tmp/
1340
+ self.assertTrue(path.startswith(specified_path))
1341
+ self.assertTrue(os.path.exists(path))
1342
+ self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
1343
+ self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
1344
+ self.repo.cleanup_cloned_directory(path)
1345
+ self.assertFalse(os.path.exists(path))
1346
+
1347
+ with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
1348
+ with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
1349
+ path = self.repo.clone_to_directory(path=specified_path, head="non-existent")
1350
+
1351
+ with self.subTest("Assert a ValuError is raised when branch and head are both provided"):
1352
+ with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
1353
+ path = self.repo.clone_to_directory(branch="main", head="valid-files")
1354
+ finally:
1355
+ shutil.rmtree(specified_path, ignore_errors=True)
1356
+ shutil.rmtree(self.tempdir.name, ignore_errors=True)
1357
+
1075
1358
 
1076
1359
  class JobModelTest(ModelTestCases.BaseModelTestCase):
1077
1360
  """
@@ -1083,7 +1366,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1083
1366
  @classmethod
1084
1367
  def setUpTestData(cls):
1085
1368
  # 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")
1369
+ cls.local_job = JobModel.objects.get(job_class_name="TestPassJob")
1087
1370
  cls.job_containing_sensitive_variables = JobModel.objects.get(job_class_name="ExampleLoggingJob")
1088
1371
  cls.app_job = JobModel.objects.get(job_class_name="ExampleJob")
1089
1372
 
@@ -1095,7 +1378,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1095
1378
  self.assertEqual(self.app_job.job_class, ExampleJob)
1096
1379
 
1097
1380
  def test_class_path(self):
1098
- self.assertEqual(self.local_job.class_path, "pass.TestPass")
1381
+ self.assertEqual(self.local_job.class_path, "pass.TestPassJob")
1099
1382
  self.assertIsNotNone(self.local_job.job_class)
1100
1383
  self.assertEqual(self.local_job.class_path, self.local_job.job_class.class_path)
1101
1384
 
@@ -1832,11 +2115,11 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1832
2115
 
1833
2116
  def setUp(self):
1834
2117
  self.user = User.objects.create_user(username="scheduledjobuser")
1835
- self.job_model = JobModel.objects.get(name="TestPass")
2118
+ self.job_model = JobModel.objects.get(name="TestPassJob")
1836
2119
 
1837
2120
  self.daily_utc_job = ScheduledJob.objects.create(
1838
2121
  name="Daily UTC Job",
1839
- task="pass.TestPass",
2122
+ task="pass.TestPassJob",
1840
2123
  job_model=self.job_model,
1841
2124
  interval=JobExecutionType.TYPE_DAILY,
1842
2125
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=get_default_timezone()),
@@ -1844,7 +2127,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1844
2127
  )
1845
2128
  self.daily_est_job = ScheduledJob.objects.create(
1846
2129
  name="Daily EST Job",
1847
- task="pass.TestPass",
2130
+ task="pass.TestPassJob",
1848
2131
  job_model=self.job_model,
1849
2132
  interval=JobExecutionType.TYPE_DAILY,
1850
2133
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
@@ -1859,7 +2142,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1859
2142
  )
1860
2143
  self.crontab_est_job = ScheduledJob.objects.create(
1861
2144
  name="Crontab EST Job",
1862
- task="pass.TestPass",
2145
+ task="pass.TestPassJob",
1863
2146
  job_model=self.job_model,
1864
2147
  interval=JobExecutionType.TYPE_CUSTOM,
1865
2148
  start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
@@ -1868,7 +2151,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
1868
2151
  )
1869
2152
  self.one_off_utc_job = ScheduledJob.objects.create(
1870
2153
  name="One-off UTC Job",
1871
- task="pass.TestPass",
2154
+ task="pass.TestPassJob",
1872
2155
  job_model=self.job_model,
1873
2156
  interval=JobExecutionType.TYPE_FUTURE,
1874
2157
  start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("UTC")),
@@ -2015,7 +2298,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2015
2298
  """Test that TYPE_CUSTOM behavior around DST is as expected."""
2016
2299
  cronjob = ScheduledJob.objects.create(
2017
2300
  name="DST Aware Cronjob",
2018
- task="pass.TestPass",
2301
+ task="pass.TestPassJob",
2019
2302
  job_model=self.job_model,
2020
2303
  enabled=False,
2021
2304
  interval=JobExecutionType.TYPE_CUSTOM,
@@ -2070,7 +2353,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2070
2353
  """Test the interaction of TYPE_DAILY around DST."""
2071
2354
  daily = ScheduledJob.objects.create(
2072
2355
  name="Daily Job",
2073
- task="pass.TestPass",
2356
+ task="pass.TestPassJob",
2074
2357
  job_model=self.job_model,
2075
2358
  enabled=False,
2076
2359
  interval=JobExecutionType.TYPE_DAILY,
@@ -2146,6 +2429,12 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2146
2429
  # entry = NautobotScheduleEntry(model=sj)
2147
2430
  # scheduler = NautobotDatabaseScheduler(app=entry.app)
2148
2431
  # scheduler.apply_async(entry=entry, producer=None, advance=False)
2432
+ # Check scheduled job runs correctly with no job queue
2433
+ # sj.job_queue = None
2434
+ # sj.save()
2435
+ # entry = NautobotScheduleEntry(model=sj)
2436
+ # scheduler = NautobotDatabaseScheduler(app=entry.app)
2437
+ # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
2438
 
2150
2439
 
2151
2440
  class SecretTest(ModelTestCases.BaseModelTestCase):
@@ -2638,7 +2927,7 @@ class JobLogEntryTest(TestCase): # TODO: change to BaseModelTestCase
2638
2927
 
2639
2928
  def setUp(self):
2640
2929
  module = "pass"
2641
- name = "TestPass"
2930
+ name = "TestPassJob"
2642
2931
  job_class = get_job(f"{module}.{name}")
2643
2932
 
2644
2933
  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)