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
nautobot/extras/jobs.py CHANGED
@@ -18,6 +18,7 @@ from db_file_storage.form_widgets import DBClearableFileInput
18
18
  from django import forms
19
19
  from django.conf import settings
20
20
  from django.contrib.auth import get_user_model
21
+ from django.core.cache import cache
21
22
  from django.core.exceptions import ObjectDoesNotExist
22
23
  from django.core.files.base import ContentFile
23
24
  from django.core.files.uploadedfile import UploadedFile
@@ -67,15 +68,15 @@ User = get_user_model()
67
68
 
68
69
 
69
70
  __all__ = [
70
- "Job",
71
71
  "BooleanVar",
72
72
  "ChoiceVar",
73
73
  "FileVar",
74
- "IntegerVar",
75
74
  "IPAddressVar",
76
75
  "IPAddressWithMaskVar",
77
76
  "IPNetworkVar",
77
+ "IntegerVar",
78
78
  "JSONVar",
79
+ "Job",
79
80
  "MultiChoiceVar",
80
81
  "MultiObjectVar",
81
82
  "ObjectVar",
@@ -105,13 +106,16 @@ class BaseJob:
105
106
 
106
107
  - name (str)
107
108
  - description (str)
108
- - hidden (bool)
109
- - field_order (list)
110
109
  - approval_required (bool)
111
- - soft_time_limit (int)
112
- - time_limit (int)
110
+ - dryrun_default (bool)
111
+ - field_order (list)
113
112
  - has_sensitive_variables (bool)
113
+ - hidden (bool)
114
+ - soft_time_limit (int)
114
115
  - task_queues (list)
116
+ - template_name (str)
117
+ - time_limit (int)
118
+ - is_singleton (bool)
115
119
  """
116
120
 
117
121
  def __init__(self):
@@ -189,6 +193,31 @@ class BaseJob:
189
193
  )
190
194
  raise RunJobTaskFailed(f"Job {self.job_model} is not enabled to be run!")
191
195
 
196
+ ignore_singleton_lock = self.celery_kwargs.get("nautobot_job_ignore_singleton_lock", False)
197
+ if self.job_model.is_singleton:
198
+ is_running = cache.get(self.singleton_cache_key)
199
+ if is_running:
200
+ if ignore_singleton_lock:
201
+ self.logger.info(
202
+ "Job %s is a singleton and already running, but singleton will be ignored because"
203
+ " `ignore_singleton_lock` is set.",
204
+ self.job_model,
205
+ extra={"object": self.job_model, "grouping": "initialization"},
206
+ )
207
+ else:
208
+ self.logger.error(
209
+ "Job %s is a singleton and already running.",
210
+ self.job_model,
211
+ extra={"object": self.job_model, "grouping": "initialization"},
212
+ )
213
+ raise RunJobTaskFailed(f"Job '{self.job_model}' is a singleton and already running.")
214
+ cache_parameters = {
215
+ "key": self.singleton_cache_key,
216
+ "value": 1,
217
+ "timeout": self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT,
218
+ }
219
+ cache.set(**cache_parameters)
220
+
192
221
  soft_time_limit = self.job_model.soft_time_limit or settings.CELERY_TASK_SOFT_TIME_LIMIT
193
222
  time_limit = self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT
194
223
  if time_limit <= soft_time_limit:
@@ -282,6 +311,14 @@ class BaseJob:
282
311
  if status == JobResultStatusChoices.STATUS_SUCCESS:
283
312
  self.logger.success("Job completed", extra={"grouping": "post_run"})
284
313
 
314
+ cache.delete(self.singleton_cache_key)
315
+
316
+ @final
317
+ @classproperty
318
+ def singleton_cache_key(cls) -> str: # pylint: disable=no-self-argument
319
+ """Cache key for singleton jobs."""
320
+ return f"nautobot.extras.jobs.running.{cls.class_path}"
321
+
285
322
  @final
286
323
  @classproperty
287
324
  def file_path(cls) -> str: # pylint: disable=no-self-argument
@@ -300,7 +337,7 @@ class BaseJob:
300
337
  - my_plugin.jobs.MyPluginJob - App-provided Job
301
338
  - git_repository.jobs.myjob.MyJob - GitRepository Job
302
339
  """
303
- return f"{cls.__module__}.{cls.__name__}"
340
+ return f"{cls.__module__}.{cls.__name__}" # pylint: disable=no-member
304
341
 
305
342
  @final
306
343
  @classproperty
@@ -337,7 +374,7 @@ class BaseJob:
337
374
  @final
338
375
  @classproperty
339
376
  def name(cls) -> str: # pylint: disable=no-self-argument
340
- return cls._get_meta_attr_and_assert_type("name", cls.__name__, expected_type=str)
377
+ return cls._get_meta_attr_and_assert_type("name", cls.__name__, expected_type=str) # pylint: disable=no-member
341
378
 
342
379
  @final
343
380
  @classproperty
@@ -401,6 +438,11 @@ class BaseJob:
401
438
  def task_queues(cls) -> list: # pylint: disable=no-self-argument
402
439
  return cls._get_meta_attr_and_assert_type("task_queues", [], expected_type=(list, tuple))
403
440
 
441
+ @final
442
+ @classproperty
443
+ def is_singleton(cls) -> bool: # pylint: disable=no-self-argument
444
+ return cls._get_meta_attr_and_assert_type("is_singleton", False, expected_type=bool)
445
+
404
446
  @final
405
447
  @classproperty
406
448
  def properties_dict(cls) -> dict: # pylint: disable=no-self-argument
@@ -419,13 +461,14 @@ class BaseJob:
419
461
  "time_limit": cls.time_limit,
420
462
  "has_sensitive_variables": cls.has_sensitive_variables,
421
463
  "task_queues": cls.task_queues,
464
+ "is_singleton": cls.is_singleton,
422
465
  }
423
466
 
424
467
  @final
425
468
  @classproperty
426
469
  def registered_name(cls) -> str: # pylint: disable=no-self-argument
427
470
  """Deprecated - use class_path classproperty instead."""
428
- return f"{cls.__module__}.{cls.__name__}"
471
+ return f"{cls.__module__}.{cls.__name__}" # pylint: disable=no-member
429
472
 
430
473
  @classmethod
431
474
  def _get_vars(cls):
@@ -486,11 +529,29 @@ class BaseJob:
486
529
  label="Profile job execution",
487
530
  help_text="Profiles the job execution using cProfile and outputs a report to /tmp/",
488
531
  )
532
+ # If the class already exists there may be overrides, so we have to check this.
533
+ try:
534
+ job_model = JobModel.objects.get(module_name=cls.__module__, job_class_name=cls.__name__)
535
+ is_singleton = job_model.is_singleton
536
+ except JobModel.DoesNotExist:
537
+ logger.warning("No Job instance found in the database corresponding to %s", cls.class_path)
538
+ job_model = None
539
+ is_singleton = cls.is_singleton
540
+
541
+ if is_singleton:
542
+ form.fields["_ignore_singleton_lock"] = forms.BooleanField(
543
+ required=False,
544
+ initial=False,
545
+ label="Ignore singleton lock",
546
+ help_text="Allow this singleton job to run even when another instance is already running",
547
+ )
489
548
 
490
- job_model = JobModel.objects.get_for_class_path(cls.class_path)
491
- dryrun_default = job_model.dryrun_default if job_model.dryrun_default_override else cls.dryrun_default
492
- job_queue_queryset = JobQueue.objects.filter(jobs=job_model)
493
- job_queue_params = {"jobs": [job_model.pk]}
549
+ if job_model is not None:
550
+ job_queue_queryset = JobQueue.objects.filter(jobs=job_model)
551
+ job_queue_params = {"jobs": [job_model.pk]}
552
+ else:
553
+ job_queue_queryset = JobQueue.objects.all()
554
+ job_queue_params = {}
494
555
 
495
556
  # Initialize job_queue choices
496
557
  form.fields["_job_queue"] = DynamicModelChoiceField(
@@ -500,8 +561,12 @@ class BaseJob:
500
561
  help_text="The job queue to route this job to",
501
562
  label="Job queue",
502
563
  )
503
- # Populate the job queue field on the JobRun Form
504
- form.fields["_job_queue"].initial = job_model.default_job_queue.pk
564
+
565
+ dryrun_default = cls.dryrun_default
566
+ if job_model is not None:
567
+ form.fields["_job_queue"].initial = job_model.default_job_queue.pk
568
+ if job_model.dryrun_default_override:
569
+ dryrun_default = job_model.dryrun_default
505
570
 
506
571
  if cls.supports_dryrun and (not initial or "dryrun" not in initial):
507
572
  # Set initial "dryrun" checkbox state based on the Meta parameter
@@ -519,7 +584,9 @@ class BaseJob:
519
584
  field.disabled = True
520
585
 
521
586
  # Ensure non-Job-specific fields are still last after applying field_order
522
- for field in ["_job_queue", "_profile"]:
587
+ for field in ["_job_queue", "_profile", "_ignore_singleton_lock"]:
588
+ if field not in form.fields:
589
+ continue
523
590
  value = form.fields.pop(field)
524
591
  form.fields[field] = value
525
592
 
@@ -1031,7 +1098,7 @@ class JobHookReceiver(Job):
1031
1098
 
1032
1099
  object_change = ObjectVar(model=ObjectChange)
1033
1100
 
1034
- def run(self, object_change):
1101
+ def run(self, object_change): # pylint: disable=arguments-differ
1035
1102
  """JobHookReceiver subclasses generally shouldn't need to override this method."""
1036
1103
  self.receive_job_hook(
1037
1104
  change=object_change,
@@ -1060,7 +1127,7 @@ class JobButtonReceiver(Job):
1060
1127
  object_pk = StringVar()
1061
1128
  object_model_name = StringVar()
1062
1129
 
1063
- def run(self, object_pk, object_model_name):
1130
+ def run(self, object_pk, object_model_name): # pylint: disable=arguments-differ
1064
1131
  """JobButtonReceiver subclasses generally shouldn't need to override this method."""
1065
1132
  model = get_model_from_name(object_model_name)
1066
1133
  obj = model.objects.get(pk=object_pk)
@@ -22,8 +22,9 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
22
22
  return self.model(id=task_id)
23
23
 
24
24
  @transaction_retry(max_retries=2)
25
- def store_result(
25
+ def store_result( # pylint:disable=arguments-differ # Nautobot adds kwargs like job_model_id and scheduled_job_id
26
26
  self,
27
+ *,
27
28
  task_id,
28
29
  result,
29
30
  status,
@@ -41,6 +42,7 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
41
42
  using=None,
42
43
  content_type=None,
43
44
  content_encoding=None,
45
+ **kwargs,
44
46
  ):
45
47
  """
46
48
  Store the result and status of a Celery task.
@@ -2,7 +2,6 @@ from collections import OrderedDict
2
2
 
3
3
  from django.db import migrations
4
4
 
5
- from nautobot.extras.choices import LogLevelChoices
6
5
  from nautobot.extras.constants import (
7
6
  JOB_LOG_MAX_ABSOLUTE_URL_LENGTH,
8
7
  JOB_LOG_MAX_GROUPING_LENGTH,
@@ -123,14 +122,13 @@ def reverse_migrate_params(apps, schema_editor):
123
122
  ]
124
123
  )
125
124
 
126
- if entry.log_level != LogLevelChoices.LOG_DEFAULT:
127
- job_result.data[entry.grouping].setdefault(entry.log_level, 0)
128
- job_result.data[entry.grouping][entry.log_level] += 1
129
- if "total" not in job_result.data:
130
- job_result.data["total"] = _data_grouping_struct()
131
- del job_result.data["total"]["log"]
132
- job_result.data["total"].setdefault(entry.log_level, 0)
133
- job_result.data["total"][entry.log_level] += 1
125
+ job_result.data[entry.grouping].setdefault(entry.log_level, 0)
126
+ job_result.data[entry.grouping][entry.log_level] += 1
127
+ if "total" not in job_result.data:
128
+ job_result.data["total"] = _data_grouping_struct()
129
+ del job_result.data["total"]["log"]
130
+ job_result.data["total"].setdefault(entry.log_level, 0)
131
+ job_result.data["total"][entry.log_level] += 1
134
132
 
135
133
  job_result.save()
136
134
 
@@ -0,0 +1,22 @@
1
+ # Generated by Django 4.2.16 on 2024-11-11 07:04
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("extras", "0119_remove_task_queues_from_job_and_queue_from_scheduled_job"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="job",
14
+ name="is_singleton",
15
+ field=models.BooleanField(default=False),
16
+ ),
17
+ migrations.AddField(
18
+ model_name="job",
19
+ name="is_singleton_override",
20
+ field=models.BooleanField(default=False),
21
+ ),
22
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.16 on 2024-12-03 15:32
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("extras", "0120_job_is_singleton_job_is_singleton_override"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AlterField(
13
+ model_name="team",
14
+ name="contacts",
15
+ field=models.ManyToManyField(blank=True, related_name="teams", to="extras.contact"),
16
+ ),
17
+ ]
@@ -0,0 +1,34 @@
1
+ # Generated by Django 4.2.17 on 2025-01-08 16:59
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+ import nautobot.extras.utils
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ dependencies = [
11
+ ("contenttypes", "0002_remove_content_type_name"),
12
+ ("extras", "0121_alter_team_contacts"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="graphqlquery",
18
+ name="owner_content_type",
19
+ field=models.ForeignKey(
20
+ blank=True,
21
+ default=None,
22
+ limit_choices_to=nautobot.extras.utils.FeatureQuery("graphql_query_owners"),
23
+ null=True,
24
+ on_delete=django.db.models.deletion.CASCADE,
25
+ related_name="graphql_queries",
26
+ to="contenttypes.contenttype",
27
+ ),
28
+ ),
29
+ migrations.AddField(
30
+ model_name="graphqlquery",
31
+ name="owner_object_id",
32
+ field=models.UUIDField(blank=True, default=None, null=True),
33
+ ),
34
+ ]
@@ -77,8 +77,8 @@ __all__ = (
77
77
  "ObjectChange",
78
78
  "ObjectMetadata",
79
79
  "Relationship",
80
- "RelationshipModel",
81
80
  "RelationshipAssociation",
81
+ "RelationshipModel",
82
82
  "Role",
83
83
  "RoleField",
84
84
  "SavedView",
@@ -61,7 +61,7 @@ class Contact(ContactTeamSharedBase):
61
61
  class Team(ContactTeamSharedBase):
62
62
  """A group of Contacts, usable interchangeably with a single Contact in most cases."""
63
63
 
64
- contacts = models.ManyToManyField(to=Contact, related_name="teams")
64
+ contacts = models.ManyToManyField(to=Contact, related_name="teams", blank=True)
65
65
 
66
66
  class Meta(ContactTeamSharedBase.Meta):
67
67
  abstract = False
@@ -370,11 +370,11 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
370
370
 
371
371
  def get_for_model(self, model, exclude_filter_disabled=False):
372
372
  """
373
- Return all CustomFields assigned to the given model.
373
+ Return (and cache) all CustomFields assigned to the given model.
374
374
 
375
375
  Args:
376
- model: The django model to which custom fields are registered
377
- exclude_filter_disabled: Exclude any custom fields which have filter logic disabled
376
+ model (Model): The django model to which custom fields are registered
377
+ exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
378
378
  """
379
379
  concrete_model = model._meta.concrete_model
380
380
  cache_key = (
@@ -521,6 +521,26 @@ class CustomField(
521
521
  def __str__(self):
522
522
  return self.label
523
523
 
524
+ @property
525
+ def choices_cache_key(self):
526
+ return f"nautobot.extras.customfield.choices.{self.pk}"
527
+
528
+ @property
529
+ def choices(self) -> list[str]:
530
+ """
531
+ Cacheable shorthand for retrieving custom_field_choices values associated with this model.
532
+
533
+ Returns:
534
+ list[str]: List of choice values, ordered by weight.
535
+ """
536
+ if self.type not in [CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT]:
537
+ return []
538
+ choices = cache.get(self.choices_cache_key)
539
+ if choices is None:
540
+ choices = list(self.custom_field_choices.order_by("weight", "value").values_list("value", flat=True))
541
+ cache.set(self.choices_cache_key, choices)
542
+ return choices
543
+
524
544
  def save(self, *args, **kwargs):
525
545
  self.clean()
526
546
  super().save(*args, **kwargs)
@@ -578,7 +598,13 @@ class CustomField(
578
598
  )
579
599
 
580
600
  def to_form_field(
581
- self, set_initial=True, enforce_required=True, for_csv_import=False, simple_json_filter=False, label=None
601
+ self,
602
+ set_initial=True,
603
+ enforce_required=True,
604
+ for_csv_import=False,
605
+ simple_json_filter=False,
606
+ label=None,
607
+ for_filter_form=False,
582
608
  ):
583
609
  """
584
610
  Return a form field suitable for setting a CustomField's value for an object.
@@ -590,6 +616,7 @@ class CustomField(
590
616
  this is *not* used for CSV imports since 2.0, but it *is* used for JSON/YAML import of DeviceTypes.
591
617
  simple_json_filter: Return a TextInput widget for JSON filtering instead of the default TextArea widget.
592
618
  label: Set the input label manually (if required); otherwise, defaults to field's __str__() implementation.
619
+ for_filter_form: If True return the relevant form field for filter form
593
620
  """
594
621
  initial = self.default if set_initial else None
595
622
  required = self.required if enforce_required else False
@@ -672,12 +699,11 @@ class CustomField(
672
699
 
673
700
  # Select or Multi-select
674
701
  else:
675
- choices = [(cfc.value, cfc.value) for cfc in self.custom_field_choices.all()]
676
- default_choice = self.custom_field_choices.filter(value=self.default).first()
702
+ choices = [(value, value) for value in self.choices]
677
703
 
678
704
  # Set the initial value to the first available choice (if any)
679
- if self.type == CustomFieldTypeChoices.TYPE_SELECT:
680
- if not required or default_choice is None:
705
+ if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filter_form:
706
+ if not required or self.default not in self.choices:
681
707
  choices = add_blank_choice(choices)
682
708
  field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
683
709
  field = field_class(
@@ -704,16 +730,10 @@ class CustomField(
704
730
 
705
731
  def to_filter_form_field(self, lookup_expr="exact", *args, **kwargs):
706
732
  """Return a filter form field suitable for filtering a CustomField's value for an object."""
707
- form_field = self.to_form_field(*args, **kwargs)
708
- # We would handle type selection differently because:
709
- # 1. We'd need to use StaticSelect2Multiple for lookup_type 'exact' because self.type `select` uses StaticSelect2 by default.
710
- # 2. Remove the blank choice since StaticSelect2Multiple is always blank and interprets the blank choice as an extra option.
711
- # 3. If lookup_type is not the same as exact, use MultiValueCharInput
733
+ form_field = self.to_form_field(*args, **kwargs, for_filter_form=True)
734
+ # We would handle type selection differently because: If lookup_type is not the same as exact, use MultiValueCharInput
712
735
  if self.type == CustomFieldTypeChoices.TYPE_SELECT:
713
- if lookup_expr in ["exact", "contains"]:
714
- choices = form_field.choices[1:]
715
- form_field.widget = StaticSelect2Multiple(choices=choices)
716
- else:
736
+ if lookup_expr not in ["exact", "contains"]:
717
737
  form_field.widget = MultiValueCharInput()
718
738
  return form_field
719
739
 
@@ -778,17 +798,15 @@ class CustomField(
778
798
 
779
799
  # Validate selected choice
780
800
  elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
781
- if value not in self.custom_field_choices.values_list("value", flat=True):
782
- raise ValidationError(
783
- f"Invalid choice ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
784
- )
801
+ if value not in self.choices:
802
+ raise ValidationError(f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}")
785
803
 
786
804
  elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
787
805
  if isinstance(value, str):
788
806
  value = value.split(",")
789
- if not set(value).issubset(self.custom_field_choices.values_list("value", flat=True)):
807
+ if not set(value).issubset(self.choices):
790
808
  raise ValidationError(
791
- f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
809
+ f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.choices)}"
792
810
  )
793
811
 
794
812
  elif self.required:
@@ -1,7 +1,11 @@
1
1
  """Models for representing external data sources."""
2
2
 
3
+ from contextlib import contextmanager
3
4
  from importlib.util import find_spec
5
+ import logging
4
6
  import os
7
+ import shutil
8
+ import tempfile
5
9
 
6
10
  from django.conf import settings
7
11
  from django.core.exceptions import ValidationError
@@ -12,12 +16,16 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
12
16
  from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
13
17
  from nautobot.core.models.generics import PrimaryModel
14
18
  from nautobot.core.models.validators import EnhancedURLValidator
19
+ from nautobot.core.utils.git import GitRepo
15
20
  from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
16
21
 
22
+ logger = logging.getLogger(__name__)
23
+
17
24
 
18
25
  @extras_features(
19
26
  "config_context_owners",
20
27
  "export_template_owners",
28
+ "graphql_query_owners",
21
29
  "graphql",
22
30
  "job_results",
23
31
  "webhooks",
@@ -167,3 +175,80 @@ class GitRepository(PrimaryModel):
167
175
  if dry_run:
168
176
  return enqueue_git_repository_diff_origin_and_local(self, user)
169
177
  return enqueue_pull_git_repository_and_refresh_data(self, user)
178
+
179
+ @contextmanager
180
+ def clone_to_directory_context(self, path=None, branch=None, head=None, depth=0):
181
+ """
182
+ Context manager to perform a (shallow or full) clone of the Git repository in a temporary directory.
183
+
184
+ Args:
185
+ path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
186
+ branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
187
+ head (str, optional): Git commit hash to check out instead of pulling branch latest.
188
+ depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
189
+
190
+ Returns:
191
+ Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
192
+ """
193
+
194
+ if branch and head:
195
+ raise ValueError("Cannot specify both branch and head")
196
+
197
+ path_name = None
198
+ try:
199
+ path_name = self.clone_to_directory(path=path, branch=branch, head=head, depth=depth)
200
+ yield path_name
201
+ finally:
202
+ # Cleanup the temporary directory
203
+ if path_name:
204
+ self.cleanup_cloned_directory(path_name)
205
+
206
+ def clone_to_directory(self, path=None, branch=None, head=None, depth=0):
207
+ """
208
+ Perform a (shallow or full) clone of the Git repository in a temporary directory.
209
+
210
+ Args:
211
+ path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
212
+ branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
213
+ head (str, optional): Git commit hash to check out instead of pulling branch latest.
214
+ depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
215
+
216
+ Returns:
217
+ Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
218
+ """
219
+ if branch and head:
220
+ raise ValueError("Cannot specify both branch and head")
221
+
222
+ try:
223
+ path_name = tempfile.mkdtemp(dir=path, prefix=self.slug)
224
+ except PermissionError as e:
225
+ logger.error(f"Failed to create temporary directory at {path}: {e}")
226
+ raise e
227
+
228
+ if not branch:
229
+ branch = self.branch
230
+
231
+ try:
232
+ repo_helper = GitRepo(path_name, self.remote_url, depth=depth, branch=branch)
233
+ if head:
234
+ repo_helper.checkout(branch, head)
235
+ except Exception as e:
236
+ logger.error(f"Failed to clone repository {self.name} to {path_name}: {e}")
237
+ raise e
238
+
239
+ logger.info(f"Cloned repository {self.name} to {path_name}")
240
+ return path_name
241
+
242
+ def cleanup_cloned_directory(self, path):
243
+ """
244
+ Cleanup the cloned directory.
245
+
246
+ Args:
247
+ path (str): The absolute directory path to cleanup.
248
+ """
249
+
250
+ try:
251
+ shutil.rmtree(path)
252
+ except OSError as os_error:
253
+ # log error if the cleanup fails
254
+ logger.error(f"Failed to cleanup temporary directory at {path}: {os_error}")
@@ -1,6 +1,7 @@
1
1
  """Dynamic Groups Models."""
2
2
 
3
3
  import logging
4
+ from typing import Optional
4
5
 
5
6
  from django import forms
6
7
  from django.contrib.contenttypes.fields import GenericForeignKey
@@ -116,7 +117,7 @@ class DynamicGroup(PrimaryModel):
116
117
  return self._model
117
118
 
118
119
  @property
119
- def filterset_class(self):
120
+ def filterset_class(self) -> Optional[type[django_filters.FilterSet]]:
120
121
  if getattr(self, "_filterset_class", None) is None:
121
122
  try:
122
123
  self._filterset_class = get_filterset_for_model(self.model)
@@ -125,7 +126,7 @@ class DynamicGroup(PrimaryModel):
125
126
  return self._filterset_class
126
127
 
127
128
  @property
128
- def filterform_class(self):
129
+ def filterform_class(self) -> Optional[type[forms.Form]]:
129
130
  if getattr(self, "_filterform_class", None) is None:
130
131
  try:
131
132
  self._filterform_class = get_form_for_model(self.model, form_prefix="Filter")
@@ -134,7 +135,7 @@ class DynamicGroup(PrimaryModel):
134
135
  return self._filterform_class
135
136
 
136
137
  @property
137
- def form_class(self):
138
+ def form_class(self) -> Optional[type[forms.Form]]:
138
139
  if getattr(self, "_form_class", None) is None:
139
140
  try:
140
141
  self._form_class = get_form_for_model(self.model)
@@ -147,19 +148,19 @@ class DynamicGroup(PrimaryModel):
147
148
  """Return all FilterForm fields in a dictionary."""
148
149
 
149
150
  # Fail gracefully with an empty dict if nothing is working yet.
150
- if not self.form_class:
151
+ if self.form_class is None or self.filterform_class is None or self.filterset_class is None:
151
152
  return {}
152
153
 
153
154
  # Get model form and fields
154
- modelform = self.form_class()
155
+ modelform = self.form_class() # pylint: disable=not-callable
155
156
  modelform_fields = modelform.fields
156
157
 
157
158
  # Get filter form and fields
158
- filterform = self.filterform_class()
159
+ filterform = self.filterform_class() # pylint: disable=not-callable
159
160
  filterform_fields = filterform.fields
160
161
 
161
162
  # Get filterset and fields
162
- filterset = self.filterset_class()
163
+ filterset = self.filterset_class() # pylint: disable=not-callable
163
164
  filterset_fields = filterset.filters
164
165
 
165
166
  # Get dynamic group filter field mappings (if any)
@@ -303,6 +304,7 @@ class DynamicGroup(PrimaryModel):
303
304
  # Since associated_object is a GenericForeignKey, we can't just do:
304
305
  # return self.static_group_associations.values_list("associated_object", flat=True)
305
306
  return self.model.objects.filter(
307
+ # pylint: disable=no-member # false positive about self.static_group_associations
306
308
  pk__in=self.static_group_associations(manager="all_objects").values_list("associated_object_id", flat=True)
307
309
  )
308
310
 
@@ -611,7 +613,7 @@ class DynamicGroup(PrimaryModel):
611
613
  raise ValidationError({"filter": "Filter can only be set for groups of type `dynamic-filter`."})
612
614
  else:
613
615
  # Validate against the filterset's internal form validation.
614
- filterset = self.filterset_class(self.filter)
616
+ filterset = self.filterset_class(self.filter) # pylint: disable=not-callable
615
617
  if not filterset.is_valid():
616
618
  raise ValidationError(filterset.errors)
617
619
 
@@ -725,7 +727,7 @@ class DynamicGroup(PrimaryModel):
725
727
  if self.group_type != DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER:
726
728
  raise RuntimeError(f"{self} is not a dynamic-filter group")
727
729
 
728
- filterset = self.filterset_class(self.filter, self.model.objects.all())
730
+ filterset = self.filterset_class(self.filter, self.model.objects.all()) # pylint: disable=not-callable
729
731
  query = models.Q()
730
732
 
731
733
  # In this case we want all filters for a group's filter dict in a set intersection (boolean