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
@@ -0,0 +1,285 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.core.exceptions import (
3
+ FieldDoesNotExist,
4
+ ObjectDoesNotExist,
5
+ PermissionDenied,
6
+ ValidationError,
7
+ )
8
+ from django.db.models import ManyToManyField, ProtectedError
9
+
10
+ from nautobot.core import exceptions
11
+ from nautobot.core.forms.utils import restrict_form_fields
12
+ from nautobot.core.utils.filtering import get_filterset_field
13
+ from nautobot.core.utils.lookup import get_filterset_for_model, get_form_for_model
14
+ from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
15
+ from nautobot.extras.jobs import (
16
+ BooleanVar,
17
+ Job,
18
+ JSONVar,
19
+ ObjectVar,
20
+ RunJobTaskFailed,
21
+ )
22
+ from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, remove_prefix_from_cf_key
23
+
24
+ name = "System Jobs"
25
+
26
+
27
+ class BulkEditObjects(Job):
28
+ """System Job to bulk Edit objects."""
29
+
30
+ content_type = ObjectVar(
31
+ model=ContentType,
32
+ description="Type of objects to update",
33
+ )
34
+ form_data = JSONVar(description="BulkEditForm data")
35
+ edit_all = BooleanVar(description="Bulk Edit all object / all filtered objects", required=False)
36
+ filter_query_params = JSONVar(label="Filter Query Params", required=False)
37
+
38
+ class Meta:
39
+ name = "Bulk Edit Objects"
40
+ description = "Bulk edit objects."
41
+ has_sensitive_variables = False
42
+ soft_time_limit = 1800
43
+ time_limit = 2000
44
+
45
+ def _update_objects(self, model, form, filter_query_params, edit_all, nullified_fields):
46
+ with deferred_change_logging_for_bulk_operation():
47
+ base_queryset = model.objects.restrict(self.user, "change")
48
+ updated_objects_pk = []
49
+ filterset_cls = get_filterset_for_model(model)
50
+
51
+ if filter_query_params:
52
+ if not filterset_cls:
53
+ self.logger.error(f"Filterset not found for {model._meta.verbose_name}")
54
+ raise RunJobTaskFailed(
55
+ f"Filter query provided but {model._meta.verbose_name} does not have a filterset."
56
+ )
57
+
58
+ # Discarding non-filter query params
59
+ new_filter_query_params = {}
60
+
61
+ for key, value in filter_query_params.items():
62
+ try:
63
+ get_filterset_field(filterset_cls(), key)
64
+ new_filter_query_params[key] = value
65
+ except exceptions.FilterSetFieldNotFound:
66
+ self.logger.debug(
67
+ f"Query parameter `{key}` not found in filterset for `{filterset_cls}`, discarding it"
68
+ )
69
+
70
+ filter_query_params = new_filter_query_params
71
+
72
+ if edit_all:
73
+ if filter_query_params:
74
+ queryset = filterset_cls(filter_query_params).qs.restrict(self.user, "change")
75
+ else:
76
+ queryset = model.objects.restrict(self.user, "change")
77
+ else:
78
+ queryset = model.objects.restrict(self.user, "change").filter(pk__in=form.cleaned_data["pk"])
79
+
80
+ form_custom_fields = getattr(form, "custom_fields", [])
81
+ form_relationships = getattr(form, "relationships", [])
82
+ standard_fields = [
83
+ field
84
+ for field in form.fields
85
+ if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
86
+ ]
87
+
88
+ self.logger.debug(f"Performing update on {queryset.count()} {model._meta.verbose_name_plural}")
89
+ for obj in queryset.iterator():
90
+ # Update standard fields. If a field is listed in _nullify, delete its value.
91
+ for field_name in standard_fields:
92
+ try:
93
+ model_field = model._meta.get_field(field_name)
94
+ except FieldDoesNotExist:
95
+ # This form field is used to modify a field rather than set its value directly
96
+ model_field = None
97
+
98
+ # Handle nullification
99
+ if nullified_fields:
100
+ if field_name in form.nullable_fields and field_name in nullified_fields:
101
+ if isinstance(model_field, ManyToManyField):
102
+ getattr(obj, field_name).set([])
103
+ else:
104
+ setattr(obj, field_name, None if model_field is not None and model_field.null else "")
105
+
106
+ # ManyToManyFields
107
+ elif isinstance(model_field, ManyToManyField):
108
+ if form.cleaned_data[field_name]:
109
+ getattr(obj, field_name).set(form.cleaned_data[field_name])
110
+ # Normal fields
111
+ elif form.cleaned_data[field_name] not in (None, ""):
112
+ setattr(obj, field_name, form.cleaned_data[field_name])
113
+
114
+ # Update custom fields
115
+ for field_name in form_custom_fields:
116
+ if field_name in form.nullable_fields and nullified_fields and field_name in nullified_fields:
117
+ obj.cf[remove_prefix_from_cf_key(field_name)] = None
118
+ elif form.cleaned_data.get(field_name) not in (None, "", []):
119
+ obj.cf[remove_prefix_from_cf_key(field_name)] = form.cleaned_data[field_name]
120
+
121
+ obj.full_clean()
122
+ obj.save()
123
+ updated_objects_pk.append(obj.pk)
124
+ form.post_save(obj)
125
+
126
+ if hasattr(form, "save_relationships") and callable(form.save_relationships):
127
+ # Add/remove relationship associations
128
+ form.save_relationships(instance=obj, nullified_fields=nullified_fields)
129
+
130
+ if hasattr(form, "save_note") and callable(form.save_note):
131
+ form.save_note(instance=obj, user=self.user)
132
+ total_updated_objs = len(updated_objects_pk)
133
+ # Enforce object-level permissions
134
+ if base_queryset.filter(pk__in=updated_objects_pk).count() != total_updated_objs:
135
+ raise ObjectDoesNotExist
136
+ return total_updated_objs
137
+
138
+ def _process_valid_form(self, model, form, filter_query_params, edit_all, nullified_fields):
139
+ try:
140
+ total_updated_objs = self._update_objects(model, form, filter_query_params, edit_all, nullified_fields)
141
+ msg = f"Updated {total_updated_objs} {model._meta.verbose_name_plural}"
142
+ self.logger.info(msg)
143
+ return msg
144
+ except ValidationError as e:
145
+ self.logger.error(str(e))
146
+ except ObjectDoesNotExist:
147
+ msg = "Object update failed due to object-level permissions violation"
148
+ self.logger.error(msg)
149
+ raise RunJobTaskFailed("Bulk Edit not fully successful, see logs")
150
+
151
+ def run( # pylint: disable=arguments-differ
152
+ self, *, content_type, form_data, edit_all=False, filter_query_params=None
153
+ ):
154
+ if not self.user.has_perm(f"{content_type.app_label}.change_{content_type.model}"):
155
+ self.logger.error('User "%s" does not have permission to update %s objects', self.user, content_type.model)
156
+ raise PermissionDenied("User does not have change permissions on the requested content-type")
157
+
158
+ model = content_type.model_class()
159
+ if model is None:
160
+ self.logger.error(
161
+ 'Could not find the "%s.%s" data model. Perhaps an app is uninstalled?',
162
+ content_type.app_label,
163
+ content_type.model,
164
+ )
165
+ raise RunJobTaskFailed("Model not found")
166
+ try:
167
+ form_cls = get_form_for_model(model, form_prefix="BulkEdit")
168
+ except Exception:
169
+ self.logger.debug(
170
+ 'Could not find the "%s.%s" data bulk edit form. Unable to process Bulk Edit for this model.',
171
+ content_type.app_label,
172
+ content_type.model,
173
+ )
174
+ raise
175
+ form = form_cls(model, form_data, edit_all=edit_all)
176
+ restrict_form_fields(form, self.user)
177
+
178
+ if form.is_valid():
179
+ self.logger.debug("Form validation was successful")
180
+ nullified_fields = form_data.get("_nullify")
181
+ return self._process_valid_form(model, form, filter_query_params, edit_all, nullified_fields)
182
+ else:
183
+ self.logger.error(f"Form validation unsuccessful: {form.errors.as_json()}")
184
+
185
+ raise RunJobTaskFailed("Updating Jobs Failed")
186
+
187
+
188
+ class BulkDeleteObjects(Job):
189
+ """
190
+ System job to bulk delete objects.
191
+ """
192
+
193
+ content_type = ObjectVar(
194
+ model=ContentType,
195
+ description="Type of objects to delete",
196
+ )
197
+ pk_list = JSONVar(description="List of objects pks to delete", required=False)
198
+ delete_all = BooleanVar(description="Delete all (filtered) objects instead of a list of PKs", required=False)
199
+ filter_query_params = JSONVar(label="Filter Query Params", required=False)
200
+
201
+ class Meta:
202
+ name = "Bulk Delete Objects"
203
+ description = "Bulk delete objects."
204
+ has_sensitive_variables = False
205
+ soft_time_limit = 1800
206
+ time_limit = 2000
207
+
208
+ def run( # pylint: disable=arguments-differ
209
+ self, *, content_type, pk_list=None, delete_all=False, filter_query_params=None
210
+ ):
211
+ if not self.user.has_perm(f"{content_type.app_label}.delete_{content_type.model}"):
212
+ self.logger.error('User "%s" does not have permission to delete %s objects', self.user, content_type.model)
213
+ raise PermissionDenied("User does not have delete permissions on the requested content-type")
214
+
215
+ model = content_type.model_class()
216
+ if model is None:
217
+ self.logger.error(
218
+ 'Could not find the "%s.%s" data model. Perhaps an app is uninstalled?',
219
+ content_type.app_label,
220
+ content_type.model,
221
+ )
222
+ raise RunJobTaskFailed("Model not found")
223
+
224
+ filterset_cls = get_filterset_for_model(model)
225
+
226
+ if filter_query_params:
227
+ if not filterset_cls:
228
+ self.logger.error(f"Filterset not found for {model._meta.verbose_name}")
229
+ raise RunJobTaskFailed(
230
+ f"Filter query provided but {model._meta.verbose_name} does not have a filterset."
231
+ )
232
+
233
+ # Discarding non-filter query params
234
+ new_filter_query_params = {}
235
+
236
+ for key, value in filter_query_params.items():
237
+ try:
238
+ get_filterset_field(filterset_cls(), key)
239
+ new_filter_query_params[key] = value
240
+ except exceptions.FilterSetFieldNotFound:
241
+ self.logger.debug(f"Query parameter `{key}` not found in `{filterset_cls}`, discarding it")
242
+
243
+ filter_query_params = new_filter_query_params
244
+
245
+ if delete_all:
246
+ # Case for selecting all objects (or all filtered objects) to delete
247
+ if filter_query_params:
248
+ # If there is filter query params, we need to apply it to the queryset
249
+ queryset = filterset_cls(filter_query_params).qs.restrict(self.user, "delete")
250
+ # We take this approach because filterset.qs has already applied .distinct(),
251
+ # and performing a .delete directly on a queryset with .distinct applied is not allowed.
252
+ queryset = model.objects.filter(pk__in=queryset)
253
+ else:
254
+ # If there is not, we can just restrict the queryset to the user's permissions
255
+ queryset = model.objects.restrict(self.user, "delete")
256
+ elif pk_list:
257
+ # Case for selecting specific objects to delete, delete_all overrides this option
258
+ queryset = model.objects.restrict(self.user, "delete").filter(pk__in=pk_list)
259
+ if queryset.count() < len(pk_list):
260
+ # We do not check ObjectPermission error for `delete_all` case because user is trying to
261
+ # delete all objs they have permission to which would not raise any issue.
262
+ self.logger.error("You do not have permissions to delete some of the objects provided in `pk_list`.")
263
+ raise RunJobTaskFailed("Caught ObjectPermission error while attempting to delete objects")
264
+ elif not pk_list and not delete_all:
265
+ self.logger.error("You must select at least one object instance to delete.")
266
+ raise RunJobTaskFailed("Either `pk_list` or `delete_all` is required.")
267
+
268
+ verbose_name_plural = model._meta.verbose_name_plural
269
+
270
+ # Currently the only purpose of a bulk delete form is to perform a `pre_delete` operation
271
+ if form_cls := get_form_for_model(model, form_prefix="BulkDelete"):
272
+ form = form_cls(model, {"pk": pk_list}, delete_all=delete_all)
273
+ if hasattr(form, "perform_pre_delete"):
274
+ form.perform_pre_delete(queryset)
275
+
276
+ try:
277
+ self.logger.info(f"Deleting {queryset.count()} {verbose_name_plural}...")
278
+ _, deleted_info = bulk_delete_with_bulk_change_logging(queryset)
279
+ deleted_count = deleted_info.get(model._meta.label, 0)
280
+ except ProtectedError as err:
281
+ # TODO this error message needs to be cleaner, ideally using a variant of handle_protectederror
282
+ self.logger.error(f"Caught ProtectedError while attempting to delete objects: `{err}`")
283
+ raise RunJobTaskFailed("Caught ProtectedError while attempting to delete objects")
284
+ msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
285
+ self.logger.info(msg)
@@ -1,6 +1,7 @@
1
1
  from datetime import timedelta
2
2
 
3
3
  from django.core.exceptions import PermissionDenied
4
+ from django.db.models import CASCADE
4
5
  from django.db.models.signals import pre_delete
5
6
  from django.utils import timezone
6
7
 
@@ -48,7 +49,28 @@ class LogsCleanup(Job):
48
49
  description = "Delete ObjectChange and/or JobResult/JobLogEntry records older than a specified cutoff."
49
50
  has_sensitive_variables = False
50
51
 
51
- def run(self, *, cleanup_types, max_age=None):
52
+ def recursive_delete_with_cascade(self, queryset, deletion_summary):
53
+ """
54
+ Recursively deletes all related objects with CASCADE for a given queryset.
55
+
56
+ Args:
57
+ queryset (QuerySet): The queryset of objects to delete.
58
+ deletion_summary (dict): A dictionary to store the count of deleted objects for each model.
59
+ """
60
+ related_objects = queryset.model._meta.related_objects
61
+ queryset = queryset.only("id")
62
+
63
+ for related_object in related_objects:
64
+ if related_object.on_delete is CASCADE:
65
+ related_model = related_object.related_model
66
+ related_field_name = related_object.field.name
67
+ cascade_queryset = related_model.objects.filter(**{f"{related_field_name}__id__in": queryset})
68
+ self.recursive_delete_with_cascade(cascade_queryset, deletion_summary)
69
+ _, deleted_dict = queryset.delete()
70
+ deletion_summary.update(deleted_dict)
71
+ return deletion_summary
72
+
73
+ def run(self, *, cleanup_types, max_age=None): # pylint: disable=arguments-differ
52
74
  if max_age in (None, ""):
53
75
  max_age = get_settings_or_config("CHANGELOG_RETENTION", fallback=90)
54
76
  if max_age == 0:
@@ -77,22 +99,36 @@ class LogsCleanup(Job):
77
99
 
78
100
  if CleanupTypes.JOB_RESULT in cleanup_types:
79
101
  self.logger.info("Deleting JobResult records prior to %s", cutoff)
80
- _, deleted_dict = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff).delete()
102
+ queryset = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff)
103
+ deletion_summary = {}
104
+ self.recursive_delete_with_cascade(queryset, deletion_summary)
81
105
  result.setdefault("extras.JobResult", 0)
82
106
  result.setdefault("extras.JobLogEntry", 0)
83
- result.update(**deleted_dict)
84
- self.logger.info(
85
- "Deleted %d JobResult records and their associated %d JobLogEntry records",
86
- result["extras.JobResult"],
87
- result["extras.JobLogEntry"],
88
- )
107
+ result.update(deletion_summary)
108
+
109
+ for modelname, count in deletion_summary.items():
110
+ self.logger.info(
111
+ "As part of deleting %d JobResult records, also deleted %d related %s records",
112
+ result["extras.JobResult"],
113
+ count,
114
+ modelname,
115
+ )
89
116
 
90
117
  if CleanupTypes.OBJECT_CHANGE in cleanup_types:
91
118
  self.logger.info("Deleting ObjectChange records prior to %s", cutoff)
92
- deleted_count, _ = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff).delete()
93
- self.logger.info("Deleted %d ObjectChange records", deleted_count)
94
- result["extras.ObjectChange"] = deleted_count
95
-
119
+ queryset = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff)
120
+ deletion_summary = {}
121
+ self.recursive_delete_with_cascade(queryset, deletion_summary)
122
+ result.setdefault("extras.ObjectChange", 0)
123
+ result.update(deletion_summary)
124
+
125
+ for modelname, count in deletion_summary.items():
126
+ self.logger.info(
127
+ "As part of deleting %d ObjectChange records, also deleted %d related %s records",
128
+ result["extras.ObjectChange"],
129
+ count,
130
+ modelname,
131
+ )
96
132
  return result
97
133
  finally:
98
134
  # Be sure to clean up after ourselves!
@@ -24,7 +24,7 @@ class RefreshDynamicGroupCaches(Job):
24
24
  description = "Re-calculate and re-cache the membership lists of Dynamic Groups."
25
25
  has_sensitive_variables = False
26
26
 
27
- def run(self, single_group=None):
27
+ def run(self, single_group=None): # pylint: disable=arguments-differ
28
28
  groups = DynamicGroup.objects.restrict(self.user, "view").exclude(
29
29
  group_type=DynamicGroupTypeChoices.TYPE_STATIC
30
30
  )
@@ -63,7 +63,7 @@ class Command(BaseCommand):
63
63
  for model in models:
64
64
  model_name = f"{model._meta.app_label}.{model.__name__}"
65
65
  # Most swap out for user_model
66
- if model_name == "auth.User":
66
+ if model_name == "auth.User": # pylint: disable=hard-coded-auth-user
67
67
  model = get_user_model()
68
68
  # Skip models that aren't actually in the database
69
69
  if not model._meta.managed:
@@ -248,6 +248,8 @@ class BaseModel(models.Model):
248
248
  Unlike `get_natural_key_def()`, this doesn't auto-exclude all AutoField and BigAutoField fields,
249
249
  but instead explicitly discounts the `id` field (only) as a candidate.
250
250
  """
251
+ if cls != cls._meta.concrete_model:
252
+ return cls._meta.concrete_model.natural_key_field_lookups
251
253
  # First, figure out which local fields comprise the natural key:
252
254
  natural_key_field_names = []
253
255
  if hasattr(cls, "natural_key_field_names"):
@@ -270,7 +272,7 @@ class BaseModel(models.Model):
270
272
 
271
273
  if not natural_key_field_names:
272
274
  raise AttributeError(
273
- f"Unable to identify an intrinsic natural-key definition for {cls.__name__}. "
275
+ f"Unable to identify an intrinsic natural-key definition for {cls.__name__}. " # pylint: disable=no-member
274
276
  "If there isn't at least one UniqueConstraint, unique_together, or field with unique=True, "
275
277
  "you probably need to explicitly declare the 'natural_key_field_names' for this model, "
276
278
  "or potentially override the default 'natural_key_field_lookups' implementation for this model."
@@ -69,7 +69,7 @@ class JSONSet(Func):
69
69
  }
70
70
  return c
71
71
 
72
- def as_sql(self, compiler, connection, function=None, **extra_context):
72
+ def as_sql(self, compiler, connection, function=None, **extra_context): # pylint:disable=arguments-differ
73
73
  """
74
74
  MySQL implementation based on https://github.com/django/django/pull/18489/files.
75
75
 
@@ -138,7 +138,7 @@ class JSONRemove(Func):
138
138
  self.path = path
139
139
  super().__init__(expression)
140
140
 
141
- def as_sql(self, compiler, connection, function=None, **extra_context):
141
+ def as_sql(self, compiler, connection, function=None, **extra_context): # pylint:disable=arguments-differ
142
142
  """
143
143
  MySQL implementation based on https://github.com/django/django/pull/18489/files.
144
144
 
@@ -111,12 +111,15 @@ class TreeModel(TreeNode):
111
111
  if display_str:
112
112
  return display_str
113
113
  try:
114
- if self.parent is not None:
115
- display_str = self.parent.display + ""
114
+ if self.parent_id is not None:
115
+ parent_display_str = cache.get(cache_key.replace(str(self.id), str(self.parent_id)), "")
116
+ if not parent_display_str:
117
+ parent_display_str = self.parent.display # pylint: disable=no-member
118
+ display_str = parent_display_str + " → "
116
119
  except self.DoesNotExist:
117
120
  # Expected to occur at times during bulk-delete operations
118
121
  pass
119
- display_str += self.name
122
+ display_str += self.name # pylint: disable=no-member # we checked with hasattr() above
120
123
  cache.set(cache_key, display_str, 5)
121
124
  return display_str
122
125
 
nautobot/core/settings.py CHANGED
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  import os
2
3
  import os.path
3
4
  import platform
@@ -163,6 +164,16 @@ NAPALM_PASSWORD = os.getenv("NAUTOBOT_NAPALM_PASSWORD", "")
163
164
  NAPALM_TIMEOUT = int(os.getenv("NAUTOBOT_NAPALM_TIMEOUT", "30"))
164
165
  NAPALM_USERNAME = os.getenv("NAUTOBOT_NAPALM_USERNAME", "")
165
166
 
167
+ # Expiration date (YYYY-MM-DD) for an active Nautobot support contract with Network to Code.
168
+ # Displayed in the About page.
169
+ if (
170
+ "NAUTOBOT_NTC_SUPPORT_CONTRACT_EXPIRATION_DATE" in os.environ
171
+ and os.environ["NAUTOBOT_NTC_SUPPORT_CONTRACT_EXPIRATION_DATE"] != ""
172
+ ):
173
+ NTC_SUPPORT_CONTRACT_EXPIRATION_DATE = datetime.date.fromisoformat(
174
+ os.environ["NAUTOBOT_NTC_SUPPORT_CONTRACT_EXPIRATION_DATE"]
175
+ )
176
+
166
177
  # Default number of objects to display per page of the UI and REST API. Default is 50
167
178
  if "NAUTOBOT_PAGINATE_COUNT" in os.environ and os.environ["NAUTOBOT_PAGINATE_COUNT"] != "":
168
179
  PAGINATE_COUNT = int(os.environ["NAUTOBOT_PAGINATE_COUNT"])
@@ -180,6 +191,9 @@ PLUGINS_CONFIG = {}
180
191
  if "NAUTOBOT_PREFER_IPV4" in os.environ and os.environ["NAUTOBOT_PREFER_IPV4"] != "":
181
192
  PREFER_IPV4 = is_truthy(os.environ["NAUTOBOT_PREFER_IPV4"])
182
193
 
194
+ # Publish a simple "no-index" robots.txt for Nautobot?
195
+ PUBLISH_ROBOTS_TXT = is_truthy(os.getenv("NAUTOBOT_PUBLISH_ROBOTS_TXT", "True"))
196
+
183
197
  # Default height and width in pixels of a single rack unit in rendered rack elevations. Defaults are 22 and 220
184
198
  if (
185
199
  "NAUTOBOT_RACK_ELEVATION_DEFAULT_UNIT_HEIGHT" in os.environ
@@ -232,7 +246,7 @@ if SSO_ENABLE_GROUP_SYNC:
232
246
  "social_core.pipeline.social_auth.associate_user",
233
247
  "social_core.pipeline.social_auth.load_extra_data",
234
248
  "social_core.pipeline.user.user_details",
235
- "nautobot.extras.group_sync",
249
+ "nautobot.extras.group_sync.group_sync",
236
250
  )
237
251
  # OAuth2/OIDC claim where the list of groups the authenticating user is a part of
238
252
  SSO_CLAIMS_GROUP = os.getenv("NAUTOBOT_SSO_CLAIMS_GROUP", "groups")
@@ -718,6 +732,13 @@ CONSTANCE_ADDITIONAL_FIELDS = {
718
732
  "required": False,
719
733
  },
720
734
  ],
735
+ "optional_date_field": [
736
+ "django.forms.DateField",
737
+ {
738
+ "widget": "nautobot.core.forms.widgets.DatePicker",
739
+ "required": False,
740
+ },
741
+ ],
721
742
  }
722
743
 
723
744
  CONSTANCE_CONFIG = {
@@ -803,6 +824,12 @@ CONSTANCE_CONFIG = {
803
824
  # Use custom field type defined above
804
825
  field_type="optional_json_field",
805
826
  ),
827
+ "NTC_SUPPORT_CONTRACT_EXPIRATION_DATE": ConstanceConfigItem(
828
+ default="",
829
+ help_text="Expiration date for an active Nautobot support contract with Network to Code. "
830
+ "This value is displayed in the About page to provide additional support information.",
831
+ field_type="optional_date_field",
832
+ ),
806
833
  "PREFER_IPV4": ConstanceConfigItem(
807
834
  default=False,
808
835
  help_text="Whether to prefer IPv4 primary addresses over IPv6 primary addresses for devices.",
@@ -856,7 +883,7 @@ CONSTANCE_CONFIG_FIELDSETS = {
856
883
  "RACK_ELEVATION_UNIT_TWO_DIGIT_FORMAT",
857
884
  ],
858
885
  "Release Checking": ["RELEASE_CHECK_URL", "RELEASE_CHECK_TIMEOUT"],
859
- "User Interface": ["SUPPORT_MESSAGE"],
886
+ "User Interface": ["SUPPORT_MESSAGE", "NTC_SUPPORT_CONTRACT_EXPIRATION_DATE"],
860
887
  "Debugging": ["ALLOW_REQUEST_PROFILING"],
861
888
  }
862
889
 
@@ -6,6 +6,8 @@ definitions:
6
6
  type: "string"
7
7
  callable:
8
8
  type: "string"
9
+ date:
10
+ type: "string"
9
11
  regex:
10
12
  type: "string"
11
13
  relative_path:
@@ -1491,6 +1493,17 @@ properties:
1491
1493
  is_constance_config: true
1492
1494
  type: "object"
1493
1495
  version_added: "1.6.0"
1496
+ NTC_SUPPORT_CONTRACT_EXPIRATION_DATE:
1497
+ "$ref": "#/definitions/date"
1498
+ default: ""
1499
+ description: >-
1500
+ Expiration date for an active Nautobot support contract with Network to Code.
1501
+ This value is displayed in the "About" page to provide additional support information.
1502
+ details: >-
1503
+ If specified as an environment variable, use ISO 8601 (`YYYY-MM-DD`) format.
1504
+ environment_variable: "NAUTOBOT_NTC_SUPPORT_CONTRACT_EXPIRATION_DATE"
1505
+ is_constance_config: true
1506
+ version_added: "2.4.0"
1494
1507
  PAGINATE_COUNT:
1495
1508
  default: 50
1496
1509
  description: >-
@@ -1559,6 +1572,14 @@ properties:
1559
1572
  environment_variable: "NAUTOBOT_PREFER_IPV4"
1560
1573
  is_constance_config: true
1561
1574
  type: "boolean"
1575
+ PUBLISH_ROBOTS_TXT:
1576
+ default: true
1577
+ description: >-
1578
+ Setting this to true causes Nautobot to publish a `robots.txt` that discourages indexing by web crawlers.
1579
+ Irrelevant if deployed behind a firewall.
1580
+ environment_variable: "NAUTOBOT_PUBLISH_ROBOTS_TXT"
1581
+ type: "boolean"
1582
+ version_added: "2.3.15"
1562
1583
  RACK_ELEVATION_DEFAULT_UNIT_HEIGHT:
1563
1584
  default: 22
1564
1585
  description: >-