nautobot 2.4.13__py3-none-any.whl → 2.4.15__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 (463) hide show
  1. nautobot/apps/choices.py +8 -0
  2. nautobot/apps/ui.py +14 -0
  3. nautobot/core/api/views.py +2 -0
  4. nautobot/core/choices.py +4 -0
  5. nautobot/core/filters.py +21 -41
  6. nautobot/core/graphql/generators.py +8 -0
  7. nautobot/core/graphql/schema.py +30 -30
  8. nautobot/core/management/commands/check_job_approval_status.py +47 -0
  9. nautobot/core/management/commands/generate_test_data.py +1 -1
  10. nautobot/core/management/commands/migrate.py +90 -1
  11. nautobot/core/models/tree_queries.py +17 -0
  12. nautobot/core/settings.py +2 -2
  13. nautobot/core/settings.yaml +3 -3
  14. nautobot/core/tables.py +29 -6
  15. nautobot/core/templates/base_django.html +1 -1
  16. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  17. nautobot/core/templates/generic/object_list.html +17 -20
  18. nautobot/core/templates/inc/breadcrumbs.html +14 -0
  19. nautobot/core/templatetags/buttons.py +2 -4
  20. nautobot/core/templatetags/helpers.py +29 -6
  21. nautobot/core/templatetags/ui_framework.py +21 -0
  22. nautobot/core/testing/api.py +7 -0
  23. nautobot/core/testing/filters.py +20 -3
  24. nautobot/core/testing/forms.py +1 -1
  25. nautobot/core/tests/integration/test_filters.py +2 -2
  26. nautobot/core/tests/test_breadcrumbs.py +366 -0
  27. nautobot/core/tests/test_commands.py +40 -0
  28. nautobot/core/tests/test_filters.py +51 -1
  29. nautobot/core/tests/test_forms.py +1 -1
  30. nautobot/core/tests/test_graphql.py +4 -4
  31. nautobot/core/tests/test_titles.py +183 -0
  32. nautobot/core/tests/test_tree_queries.py +30 -0
  33. nautobot/core/tests/test_views.py +2 -2
  34. nautobot/core/tests/test_views_utils.py +1 -1
  35. nautobot/core/ui/breadcrumbs.py +538 -0
  36. nautobot/core/ui/bulk_buttons.py +53 -0
  37. nautobot/core/ui/object_detail.py +31 -8
  38. nautobot/core/ui/titles.py +127 -0
  39. nautobot/core/ui/utils.py +25 -0
  40. nautobot/core/utils/migrations.py +1 -1
  41. nautobot/core/views/__init__.py +1 -1
  42. nautobot/core/views/mixins.py +26 -1
  43. nautobot/core/views/renderers.py +20 -2
  44. nautobot/core/views/utils.py +14 -13
  45. nautobot/dcim/api/serializers.py +9 -0
  46. nautobot/dcim/choices.py +55 -0
  47. nautobot/dcim/constants.py +0 -16
  48. nautobot/dcim/factory.py +1 -1
  49. nautobot/dcim/filters/__init__.py +15 -3
  50. nautobot/dcim/forms.py +120 -7
  51. nautobot/dcim/management/commands/trace_paths.py +1 -1
  52. nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
  53. nautobot/dcim/models/device_component_templates.py +8 -0
  54. nautobot/dcim/models/device_components.py +31 -12
  55. nautobot/dcim/models/devices.py +1 -1
  56. nautobot/dcim/models/power.py +171 -10
  57. nautobot/dcim/models/racks.py +7 -4
  58. nautobot/dcim/tables/devices.py +2 -0
  59. nautobot/dcim/tables/devicetypes.py +1 -0
  60. nautobot/dcim/tables/power.py +30 -2
  61. nautobot/dcim/templates/dcim/device.html +2 -2
  62. nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
  63. nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
  64. nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
  65. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
  66. nautobot/dcim/templates/dcim/rack.html +2 -318
  67. nautobot/dcim/templates/dcim/rack_edit.html +2 -47
  68. nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
  69. nautobot/dcim/templates/dcim/rack_update.html +47 -0
  70. nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
  71. nautobot/dcim/tests/test_api.py +24 -4
  72. nautobot/dcim/tests/test_filters.py +91 -13
  73. nautobot/dcim/tests/test_models.py +262 -0
  74. nautobot/dcim/tests/test_views.py +20 -12
  75. nautobot/dcim/urls.py +2 -27
  76. nautobot/dcim/utils.py +13 -30
  77. nautobot/dcim/views.py +428 -146
  78. nautobot/extras/choices.py +12 -4
  79. nautobot/extras/factory.py +19 -20
  80. nautobot/extras/filters/__init__.py +3 -2
  81. nautobot/extras/filters/mixins.py +23 -7
  82. nautobot/extras/forms/__init__.py +2 -1
  83. nautobot/extras/forms/forms.py +71 -0
  84. nautobot/extras/forms/mixins.py +4 -2
  85. nautobot/extras/managers.py +4 -1
  86. nautobot/extras/migrations/0062_collect_roles_from_related_apps_roles.py +30 -7
  87. nautobot/extras/migrations/0124_add_joblogentry_index.py +16 -0
  88. nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
  89. nautobot/extras/models/customfields.py +53 -5
  90. nautobot/extras/models/datasources.py +1 -2
  91. nautobot/extras/models/jobs.py +13 -3
  92. nautobot/extras/models/relationships.py +55 -6
  93. nautobot/extras/plugins/views.py +24 -1
  94. nautobot/extras/secrets/__init__.py +1 -1
  95. nautobot/extras/tables.py +9 -0
  96. nautobot/extras/templates/extras/customfield.html +2 -129
  97. nautobot/extras/templates/extras/customfield_edit.html +2 -108
  98. nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
  99. nautobot/extras/templates/extras/customfield_update.html +108 -0
  100. nautobot/extras/templates/extras/graphqlquery.html +2 -97
  101. nautobot/extras/templates/extras/graphqlquery_list.html +1 -0
  102. nautobot/extras/templates/extras/graphqlquery_retrieve.html +97 -0
  103. nautobot/extras/templates/extras/inc/jobresult.html +7 -3
  104. nautobot/extras/templates/extras/jobresult.html +2 -155
  105. nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
  106. nautobot/extras/templates/extras/marketplace.html +5 -6
  107. nautobot/extras/templates/extras/note.html +2 -53
  108. nautobot/extras/templates/extras/note_retrieve.html +53 -0
  109. nautobot/extras/templates/extras/plugins_list.html +5 -6
  110. nautobot/extras/templates/extras/secretsgroup.html +2 -29
  111. nautobot/extras/templates/extras/secretsgroup_edit.html +2 -82
  112. nautobot/extras/templates/extras/secretsgroup_retrieve.html +29 -0
  113. nautobot/extras/templates/extras/secretsgroup_update.html +82 -0
  114. nautobot/extras/templatetags/custom_links.py +2 -2
  115. nautobot/extras/templatetags/job_buttons.py +1 -1
  116. nautobot/extras/templatetags/plugins.py +1 -1
  117. nautobot/extras/tests/integration/test_computedfields.py +2 -2
  118. nautobot/extras/tests/integration/test_customfields.py +14 -11
  119. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  120. nautobot/extras/tests/integration/test_notes.py +1 -1
  121. nautobot/extras/tests/integration/test_plugins.py +6 -6
  122. nautobot/extras/tests/integration/test_relationships.py +2 -2
  123. nautobot/extras/tests/test_customfields.py +115 -7
  124. nautobot/extras/tests/test_filters.py +9 -0
  125. nautobot/extras/tests/test_forms.py +2 -2
  126. nautobot/extras/tests/test_plugins.py +2 -3
  127. nautobot/extras/tests/test_relationships.py +14 -8
  128. nautobot/extras/tests/test_views.py +285 -2
  129. nautobot/extras/urls.py +5 -110
  130. nautobot/extras/utils.py +5 -2
  131. nautobot/extras/views.py +116 -311
  132. nautobot/ipam/api/views.py +69 -6
  133. nautobot/ipam/tables.py +8 -15
  134. nautobot/ipam/tests/migration/test_migrations.py +8 -8
  135. nautobot/ipam/tests/test_api.py +352 -2
  136. nautobot/ipam/tests/test_models.py +1 -1
  137. nautobot/project-static/docs/404.html +34 -34
  138. nautobot/project-static/docs/apps/index.html +34 -34
  139. nautobot/project-static/docs/apps/nautobot-apps.html +34 -34
  140. nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
  141. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js → bundle.50899def.min.js} +2 -2
  142. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js.map → bundle.50899def.min.js.map} +2 -2
  143. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css → main.7e37652d.min.css} +1 -1
  144. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css.map → main.7e37652d.min.css.map} +1 -1
  145. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +39 -34
  146. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +36 -34
  147. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +139 -54
  148. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +48 -38
  149. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +50 -40
  150. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +36 -34
  151. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +35 -34
  152. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +43 -39
  153. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +52 -42
  154. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +50 -41
  155. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +54 -44
  156. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +85 -93
  157. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +154 -62
  158. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +54 -46
  159. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +146 -87
  160. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +240 -70
  161. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +38 -35
  162. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +41 -35
  163. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +173 -52
  164. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +269 -85
  165. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2643
  166. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +36 -34
  167. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +165 -89
  168. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +161 -69
  169. nautobot/project-static/docs/development/apps/api/configuration-view.html +34 -34
  170. nautobot/project-static/docs/development/apps/api/database-backend-config.html +34 -34
  171. nautobot/project-static/docs/development/apps/api/models/django-admin.html +34 -34
  172. nautobot/project-static/docs/development/apps/api/models/global-search.html +34 -34
  173. nautobot/project-static/docs/development/apps/api/models/graphql.html +34 -34
  174. nautobot/project-static/docs/development/apps/api/models/index.html +34 -34
  175. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +34 -34
  176. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +34 -34
  177. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +34 -34
  178. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +34 -34
  179. nautobot/project-static/docs/development/apps/api/platform-features/index.html +34 -34
  180. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +34 -34
  181. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +34 -34
  182. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +34 -34
  183. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +34 -34
  184. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +34 -34
  185. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +34 -34
  186. nautobot/project-static/docs/development/apps/api/prometheus.html +34 -34
  187. nautobot/project-static/docs/development/apps/api/setup.html +34 -34
  188. nautobot/project-static/docs/development/apps/api/testing.html +34 -34
  189. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +34 -34
  190. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +34 -34
  191. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +34 -34
  192. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +34 -34
  193. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +34 -34
  194. nautobot/project-static/docs/development/apps/api/views/base-template.html +34 -34
  195. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +34 -34
  196. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +34 -34
  197. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +34 -34
  198. nautobot/project-static/docs/development/apps/api/views/index.html +34 -34
  199. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +34 -34
  200. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +42 -36
  201. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +34 -34
  202. nautobot/project-static/docs/development/apps/api/views/notes.html +34 -34
  203. nautobot/project-static/docs/development/apps/api/views/rest-api.html +34 -34
  204. nautobot/project-static/docs/development/apps/api/views/urls.html +34 -34
  205. nautobot/project-static/docs/development/apps/index.html +34 -34
  206. nautobot/project-static/docs/development/apps/migration/code-updates.html +34 -34
  207. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +34 -34
  208. nautobot/project-static/docs/development/apps/migration/from-v1.html +34 -34
  209. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +34 -34
  210. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +34 -34
  211. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +34 -34
  212. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +34 -34
  213. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +37 -37
  214. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
  215. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +34 -34
  216. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +34 -34
  217. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +34 -34
  218. nautobot/project-static/docs/development/apps/porting-from-netbox.html +37 -37
  219. nautobot/project-static/docs/development/core/application-registry.html +162 -133
  220. nautobot/project-static/docs/development/core/best-practices.html +34 -34
  221. nautobot/project-static/docs/development/core/bootstrap-ui.html +34 -34
  222. nautobot/project-static/docs/development/core/caching.html +34 -34
  223. nautobot/project-static/docs/development/core/controllers.html +34 -34
  224. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +34 -34
  225. nautobot/project-static/docs/development/core/generic-views.html +34 -34
  226. nautobot/project-static/docs/development/core/getting-started.html +34 -34
  227. nautobot/project-static/docs/development/core/homepage.html +34 -34
  228. nautobot/project-static/docs/development/core/index.html +34 -34
  229. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +34 -34
  230. nautobot/project-static/docs/development/core/model-checklist.html +34 -34
  231. nautobot/project-static/docs/development/core/model-features.html +34 -34
  232. nautobot/project-static/docs/development/core/natural-keys.html +34 -34
  233. nautobot/project-static/docs/development/core/navigation-menu.html +34 -34
  234. nautobot/project-static/docs/development/core/release-checklist.html +34 -34
  235. nautobot/project-static/docs/development/core/role-internals.html +34 -34
  236. nautobot/project-static/docs/development/core/settings.html +34 -34
  237. nautobot/project-static/docs/development/core/style-guide.html +34 -34
  238. nautobot/project-static/docs/development/core/templates.html +34 -34
  239. nautobot/project-static/docs/development/core/testing.html +34 -34
  240. nautobot/project-static/docs/development/core/ui-component-framework.html +724 -289
  241. nautobot/project-static/docs/development/core/user-preferences.html +34 -34
  242. nautobot/project-static/docs/development/index.html +34 -34
  243. nautobot/project-static/docs/development/jobs/getting-started.html +34 -34
  244. nautobot/project-static/docs/development/jobs/index.html +34 -34
  245. nautobot/project-static/docs/development/jobs/installation.html +34 -34
  246. nautobot/project-static/docs/development/jobs/job-extensions.html +34 -34
  247. nautobot/project-static/docs/development/jobs/job-logging.html +34 -34
  248. nautobot/project-static/docs/development/jobs/job-patterns.html +34 -34
  249. nautobot/project-static/docs/development/jobs/job-structure.html +34 -34
  250. nautobot/project-static/docs/development/jobs/migration/from-v1.html +34 -34
  251. nautobot/project-static/docs/development/jobs/testing.html +34 -34
  252. nautobot/project-static/docs/index.html +34 -34
  253. nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
  254. nautobot/project-static/docs/media/power_distribution.png +0 -0
  255. nautobot/project-static/docs/objects.inv +0 -0
  256. nautobot/project-static/docs/overview/application_stack.html +34 -34
  257. nautobot/project-static/docs/overview/design_philosophy.html +34 -34
  258. nautobot/project-static/docs/release-notes/index.html +34 -34
  259. nautobot/project-static/docs/release-notes/version-1.0.html +34 -34
  260. nautobot/project-static/docs/release-notes/version-1.1.html +34 -34
  261. nautobot/project-static/docs/release-notes/version-1.2.html +34 -34
  262. nautobot/project-static/docs/release-notes/version-1.3.html +34 -34
  263. nautobot/project-static/docs/release-notes/version-1.4.html +34 -34
  264. nautobot/project-static/docs/release-notes/version-1.5.html +34 -34
  265. nautobot/project-static/docs/release-notes/version-1.6.html +34 -34
  266. nautobot/project-static/docs/release-notes/version-2.0.html +34 -34
  267. nautobot/project-static/docs/release-notes/version-2.1.html +34 -34
  268. nautobot/project-static/docs/release-notes/version-2.2.html +34 -34
  269. nautobot/project-static/docs/release-notes/version-2.3.html +34 -34
  270. nautobot/project-static/docs/release-notes/version-2.4.html +402 -34
  271. nautobot/project-static/docs/requirements.txt +3 -3
  272. nautobot/project-static/docs/search/search_index.json +1 -1
  273. nautobot/project-static/docs/sitemap.xml +303 -299
  274. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  275. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +34 -34
  276. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +34 -34
  277. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +34 -34
  278. nautobot/project-static/docs/user-guide/administration/configuration/index.html +34 -34
  279. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +34 -34
  280. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +37 -37
  281. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +34 -34
  282. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +34 -34
  283. nautobot/project-static/docs/user-guide/administration/guides/docker.html +34 -34
  284. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +34 -34
  285. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +34 -34
  286. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +34 -34
  287. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +34 -34
  288. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +34 -34
  289. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +34 -34
  290. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +34 -34
  291. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +34 -34
  292. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +34 -34
  293. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +34 -34
  294. nautobot/project-static/docs/user-guide/administration/installation/index.html +34 -34
  295. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +34 -34
  296. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +34 -34
  297. nautobot/project-static/docs/user-guide/administration/installation/services.html +34 -34
  298. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +34 -34
  299. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +34 -34
  300. nautobot/project-static/docs/user-guide/administration/security/index.html +34 -34
  301. nautobot/project-static/docs/user-guide/administration/security/notices.html +34 -34
  302. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +296 -251
  303. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +34 -34
  304. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +34 -34
  305. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +34 -34
  306. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +34 -34
  307. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +34 -34
  308. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +34 -34
  309. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +34 -34
  310. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +34 -34
  311. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +34 -34
  312. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +34 -34
  313. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +34 -34
  314. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +34 -34
  315. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +34 -34
  316. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +34 -34
  317. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +34 -34
  318. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +34 -34
  319. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +34 -34
  320. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +34 -34
  321. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +34 -34
  322. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +34 -34
  323. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +34 -34
  324. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +34 -34
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +34 -34
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +34 -34
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +34 -34
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +34 -34
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +34 -34
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +34 -34
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +34 -34
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +34 -34
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +34 -34
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +34 -34
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +34 -34
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +34 -34
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +34 -34
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +34 -34
  339. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +34 -34
  340. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +37 -37
  341. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +34 -34
  342. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +34 -34
  343. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +42 -52
  344. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +34 -34
  345. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +34 -34
  346. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +34 -34
  347. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +34 -34
  348. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +37 -37
  349. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +34 -34
  350. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +34 -34
  351. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -34
  352. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +34 -34
  353. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +316 -39
  354. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +35 -35
  355. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +34 -34
  356. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +147 -37
  357. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +52 -35
  358. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +51 -34
  359. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +34 -34
  360. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +34 -34
  361. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +34 -34
  362. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +34 -34
  363. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +34 -34
  364. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +34 -34
  365. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +34 -34
  366. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +34 -34
  367. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +34 -34
  368. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +34 -34
  369. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +34 -34
  370. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +34 -34
  371. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +34 -34
  372. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +34 -34
  373. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +34 -34
  374. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +34 -34
  375. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +34 -34
  376. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +34 -34
  377. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +34 -34
  378. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +34 -34
  379. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +34 -34
  380. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +34 -34
  381. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +34 -34
  382. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +34 -34
  383. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +34 -34
  384. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +34 -34
  385. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +34 -34
  386. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +34 -34
  387. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +34 -34
  388. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +34 -34
  389. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +34 -34
  390. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +34 -34
  391. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +34 -34
  392. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +34 -34
  393. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +34 -34
  394. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +34 -34
  395. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +34 -34
  396. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +34 -34
  397. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +34 -34
  398. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +34 -34
  399. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +34 -34
  400. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +34 -34
  401. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +34 -34
  402. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +34 -34
  403. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +34 -34
  404. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +34 -34
  405. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +34 -34
  406. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +34 -34
  407. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +34 -34
  408. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +34 -34
  409. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +34 -34
  410. nautobot/project-static/docs/user-guide/index.html +34 -34
  411. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +34 -34
  412. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +34 -34
  413. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +34 -34
  414. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +34 -34
  415. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +34 -34
  416. nautobot/project-static/docs/user-guide/platform-functionality/events.html +34 -34
  417. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +34 -34
  418. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +34 -34
  419. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +34 -34
  420. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +34 -34
  421. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +34 -34
  422. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +34 -34
  423. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +34 -34
  424. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +35 -35
  425. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +34 -34
  426. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +34 -34
  427. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +34 -34
  428. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +34 -34
  429. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +34 -34
  430. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +34 -34
  431. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +34 -34
  432. nautobot/project-static/docs/user-guide/platform-functionality/note.html +34 -34
  433. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +34 -34
  434. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +34 -34
  435. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +34 -34
  436. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +34 -34
  437. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +34 -34
  438. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +34 -34
  439. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +34 -34
  440. nautobot/project-static/docs/user-guide/platform-functionality/role.html +34 -34
  441. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +34 -34
  442. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +34 -34
  443. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +34 -34
  444. nautobot/project-static/docs/user-guide/platform-functionality/status.html +34 -34
  445. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +34 -34
  446. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +34 -34
  447. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +34 -34
  448. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +34 -34
  449. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +34 -34
  450. nautobot/tenancy/api/views.py +2 -1
  451. nautobot/users/tests/test_api.py +2 -2
  452. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
  453. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
  454. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
  455. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
  456. nautobot/virtualization/urls.py +3 -61
  457. nautobot/virtualization/views.py +48 -72
  458. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/METADATA +27 -27
  459. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/RECORD +463 -439
  460. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/LICENSE.txt +0 -0
  461. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/NOTICE +0 -0
  462. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/WHEEL +0 -0
  463. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/entry_points.txt +0 -0
@@ -483,22 +483,30 @@ class SecretsGroupAccessTypeChoices(ChoiceSet):
483
483
 
484
484
 
485
485
  class SecretsGroupSecretTypeChoices(ChoiceSet):
486
+ TYPE_AUTHKEY = "authentication-key"
487
+ TYPE_AUTHPROTOCOL = "authentication-protocol"
486
488
  TYPE_KEY = "key"
489
+ TYPE_NOTES = "notes"
487
490
  TYPE_PASSWORD = "password" # noqa: S105 # hardcoded-password-string -- false positive
491
+ TYPE_PRIVALGORITHM = "private-algorithm"
492
+ TYPE_PRIVKEY = "private-key"
488
493
  TYPE_SECRET = "secret" # noqa: S105 # hardcoded-password-string -- false positive
489
494
  TYPE_TOKEN = "token" # noqa: S105 # hardcoded-password-string -- false positive
490
- TYPE_USERNAME = "username"
491
495
  TYPE_URL = "url"
492
- TYPE_NOTES = "notes"
496
+ TYPE_USERNAME = "username"
493
497
 
494
498
  CHOICES = (
499
+ (TYPE_AUTHKEY, "Authentication Key"),
500
+ (TYPE_AUTHPROTOCOL, "Authentication Protocol"),
495
501
  (TYPE_KEY, "Key"),
502
+ (TYPE_NOTES, "Notes"),
496
503
  (TYPE_PASSWORD, "Password"),
504
+ (TYPE_PRIVALGORITHM, "Private Algorithm"),
505
+ (TYPE_PRIVKEY, "Private Key"),
497
506
  (TYPE_SECRET, "Secret"),
498
507
  (TYPE_TOKEN, "Token"),
499
- (TYPE_USERNAME, "Username"),
500
508
  (TYPE_URL, "URL"),
501
- (TYPE_NOTES, "Notes"),
509
+ (TYPE_USERNAME, "Username"),
502
510
  )
503
511
 
504
512
 
@@ -1,4 +1,4 @@
1
- from datetime import timedelta, timezone
1
+ from datetime import timezone
2
2
  import json
3
3
 
4
4
  from django.contrib.auth import get_user_model
@@ -137,8 +137,10 @@ class JobLogEntryFactory(BaseModelFactory):
137
137
  def created(self):
138
138
  if self.job_result.date_done:
139
139
  return faker.Faker().date_time_between_dates(
140
- datetime_start=self.job_result.date_created, datetime_end=self.job_result.date_done, tzinfo=timezone.utc
140
+ datetime_start=self.job_result.date_started, datetime_end=self.job_result.date_done, tzinfo=timezone.utc
141
141
  )
142
+ elif self.job_result.date_started:
143
+ return faker.Faker().past_datetime(start_date=self.job_result.date_started, tzinfo=timezone.utc)
142
144
  return faker.Faker().past_datetime(start_date=self.job_result.date_created, tzinfo=timezone.utc)
143
145
 
144
146
 
@@ -183,7 +185,7 @@ class JobResultFactory(BaseModelFactory):
183
185
  job_model = factory.Maybe("has_job_model", random_instance(Job), None)
184
186
  name = factory.Faker("word")
185
187
  task_name = factory.Faker("word")
186
- # date_created and date_done are handled below
188
+ # date_created, date_started, and date_done are handled below
187
189
  user = factory.Maybe("has_user", random_instance(get_user_model()), None)
188
190
  status = factory.Iterator(
189
191
  [
@@ -210,23 +212,20 @@ class JobResultFactory(BaseModelFactory):
210
212
  return None
211
213
 
212
214
  @factory.post_generation
213
- def date_created(self, created, extracted, **kwargs): # pylint: disable=method-hidden
214
- if created:
215
- if extracted:
216
- self.date_created = extracted
217
- else:
218
- self.date_created = faker.Faker().date_time_between(
219
- start_date="-1y", end_date="now", tzinfo=timezone.utc
220
- )
221
-
222
- @factory.post_generation
223
- def date_done(self, created, extracted, **kwargs): # pylint: disable=method-hidden
224
- if created:
225
- if extracted:
226
- self.date_done = extracted
227
- else:
228
- # TODO, should we create "in progress" job results without a date_done value as well?
229
- self.date_done = self.date_created + timedelta(minutes=faker.Faker().random_int())
215
+ def dates(self, created, extracted, **kwargs): # pylint: disable=method-hidden
216
+ if not created:
217
+ return
218
+ if extracted:
219
+ return
220
+ # Create a date_created in the past, but not too far in the past
221
+ self.date_created = faker.Faker().date_time_between(start_date="-1y", end_date="-1w", tzinfo=timezone.utc)
222
+ self.date_started = faker.Faker().date_time_between(
223
+ start_date=self.date_created, end_date="-1d", tzinfo=timezone.utc
224
+ )
225
+ # TODO, should we create "in progress" job results without a date_done value as well?
226
+ self.date_done = faker.Faker().date_time_between(
227
+ start_date=self.date_started, end_date="now", tzinfo=timezone.utc
228
+ )
230
229
 
231
230
 
232
231
  class MetadataChoiceFactory(BaseModelFactory):
@@ -1,4 +1,5 @@
1
1
  from difflib import get_close_matches
2
+ from uuid import UUID
2
3
 
3
4
  from django.conf import settings
4
5
  from django.contrib.auth import get_user_model
@@ -1012,7 +1013,7 @@ class JobResultFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
1012
1013
 
1013
1014
  class Meta:
1014
1015
  model = JobResult
1015
- fields = ["id", "date_created", "date_done", "name", "status", "user", "scheduled_job"]
1016
+ fields = ["id", "date_created", "date_started", "date_done", "name", "status", "user", "scheduled_job"]
1016
1017
 
1017
1018
 
1018
1019
  class JobLogEntryFilterSet(BaseFilterSet):
@@ -1193,7 +1194,7 @@ class NoteFilterSet(BaseFilterSet):
1193
1194
  filter_predicates={
1194
1195
  "user_name": "icontains",
1195
1196
  "note": "icontains",
1196
- "assigned_object_id": "exact",
1197
+ "assigned_object_id": {"lookup_expr": "exact", "preprocessor": UUID},
1197
1198
  },
1198
1199
  )
1199
1200
  assigned_object_type = ContentTypeFilter()
@@ -1,5 +1,7 @@
1
1
  from django.contrib.contenttypes.models import ContentType
2
2
  from django.db.models import Model, Q
3
+ from django.utils.encoding import force_str
4
+ from django.utils.text import capfirst
3
5
  import django_filters
4
6
  from django_filters.constants import EMPTY_VALUES
5
7
  from django_filters.utils import verbose_lookup_expr
@@ -74,7 +76,9 @@ class CustomFieldModelFilterSetMixin(django_filters.FilterSet):
74
76
  CustomFieldTypeChoices.TYPE_SELECT: CustomFieldSelectFilter,
75
77
  }
76
78
 
77
- custom_fields = CustomField.objects.get_for_model(self._meta.model, exclude_filter_disabled=True)
79
+ custom_fields = CustomField.objects.get_for_model(
80
+ self._meta.model, exclude_filter_disabled=True, get_queryset=False
81
+ )
78
82
  for cf in custom_fields:
79
83
  # Determine filter class for this CustomField type, default to CustomFieldCharFilter
80
84
  new_filter_name = cf.add_prefix_to_cf_key()
@@ -129,11 +133,21 @@ class CustomFieldModelFilterSetMixin(django_filters.FilterSet):
129
133
  # Create new filters for each lookup expression in the map
130
134
  for lookup_name, lookup_expr in lookup_map.items():
131
135
  new_filter_name = f"{filter_name}__{lookup_name}"
136
+
137
+ # Based on logic in BaseFilterSet._generate_lookup_expression_filters
138
+ verbose_expression = (
139
+ ["exclude", custom_field.label] if lookup_name.startswith("n") else [custom_field.label]
140
+ )
141
+ if isinstance(lookup_expr, str):
142
+ verbose_expression.append(verbose_lookup_expr(lookup_expr))
143
+ verbose_expression = [force_str(part) for part in verbose_expression if part]
144
+ label = capfirst(" ".join(verbose_expression))
145
+
132
146
  new_filter = filter_type(
133
147
  field_name=custom_field.key,
134
148
  lookup_expr=lookup_expr,
135
149
  custom_field=custom_field,
136
- label=f"{custom_field.label} ({verbose_lookup_expr(lookup_expr)})",
150
+ label=label,
137
151
  exclude=lookup_name.startswith("n"),
138
152
  )
139
153
 
@@ -220,13 +234,13 @@ class RelationshipModelFilterSetMixin(django_filters.FilterSet):
220
234
  """
221
235
  Append form fields for all Relationships assigned to this model.
222
236
  """
223
- src_relationships, dst_relationships = Relationship.objects.get_for_model(model=model, hidden=False)
237
+ src_relationships, dst_relationships = Relationship.objects.get_for_model(
238
+ model=model, hidden=False, get_queryset=False
239
+ )
224
240
 
225
- for rel in src_relationships:
226
- self._append_relationships_side([rel], RelationshipSideChoices.SIDE_SOURCE, model)
241
+ self._append_relationships_side(src_relationships, RelationshipSideChoices.SIDE_SOURCE, model)
227
242
 
228
- for rel in dst_relationships:
229
- self._append_relationships_side([rel], RelationshipSideChoices.SIDE_DESTINATION, model)
243
+ self._append_relationships_side(dst_relationships, RelationshipSideChoices.SIDE_DESTINATION, model)
230
244
 
231
245
  def _append_relationships_side(self, relationships, initial_side, model):
232
246
  """
@@ -295,6 +309,7 @@ class RoleModelFilterSetMixin(django_filters.FilterSet):
295
309
  field_name="role",
296
310
  query_params={"content_types": [cls._meta.model._meta.label_lower]},
297
311
  )
312
+ cls.declared_filters["role"] = filters["role"] # pylint: disable=no-member
298
313
 
299
314
  return filters
300
315
 
@@ -324,5 +339,6 @@ class StatusModelFilterSetMixin(django_filters.FilterSet):
324
339
  field_name="status",
325
340
  query_params={"content_types": [cls._meta.model._meta.label_lower]},
326
341
  )
342
+ cls.declared_filters["status"] = filters["status"] # pylint: disable=no-member
327
343
 
328
344
  return filters
@@ -12,5 +12,6 @@ from .contacts import (
12
12
  TeamFilterForm,
13
13
  TeamForm,
14
14
  )
15
- from .forms import * # noqa: F403 # undefined-local-with-import-star
15
+
16
16
  from .mixins import * # noqa: F403 # undefined-local-with-import-star
17
+ from .forms import * # noqa: F403 # undefined-local-with-import-star
@@ -43,6 +43,7 @@ from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
43
43
  from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
44
44
  from nautobot.extras.choices import (
45
45
  ButtonClassChoices,
46
+ CustomFieldFilterLogicChoices,
46
47
  DynamicGroupTypeChoices,
47
48
  JobExecutionType,
48
49
  JobQueueTypeChoices,
@@ -134,6 +135,7 @@ __all__ = (
134
135
  "ConfigContextSchemaForm",
135
136
  "CustomFieldBulkCreateForm", # 2.0 TODO remove this deprecated class
136
137
  "CustomFieldBulkDeleteForm",
138
+ "CustomFieldBulkEditForm",
137
139
  "CustomFieldChoiceFormSet",
138
140
  "CustomFieldFilterForm",
139
141
  "CustomFieldForm",
@@ -197,6 +199,7 @@ __all__ = (
197
199
  "SecretFilterForm",
198
200
  "SecretForm",
199
201
  "SecretsGroupAssociationFormSet",
202
+ "SecretsGroupBulkEditForm",
200
203
  "SecretsGroupFilterForm",
201
204
  "SecretsGroupForm",
202
205
  "StaticGroupAssociationFilterForm",
@@ -464,6 +467,66 @@ class CustomFieldDescriptionField(CommentField):
464
467
  return "Also used as the help text when editing models using this custom field.<br>" + super().default_helptext
465
468
 
466
469
 
470
+ class CustomFieldBulkEditForm(BootstrapMixin, NoteModelBulkEditFormMixin):
471
+ pk = forms.ModelMultipleChoiceField(queryset=CustomField.objects.all(), widget=forms.MultipleHiddenInput)
472
+ grouping = forms.CharField(
473
+ required=False,
474
+ max_length=CHARFIELD_MAX_LENGTH,
475
+ label="Grouping",
476
+ help_text="Human-readable grouping that this custom field belongs to.",
477
+ )
478
+ description = forms.CharField(
479
+ required=False,
480
+ max_length=CHARFIELD_MAX_LENGTH,
481
+ label="Description",
482
+ help_text="A helpful description for this field.",
483
+ )
484
+ required = forms.NullBooleanField(
485
+ required=False,
486
+ widget=BulkEditNullBooleanSelect,
487
+ label="Required",
488
+ help_text="If true, this field is required when creating new objects or editing an existing object.",
489
+ )
490
+ filter_logic = forms.ChoiceField(
491
+ required=False,
492
+ choices=add_blank_choice(CustomFieldFilterLogicChoices.CHOICES),
493
+ label="Filter logic",
494
+ help_text="Loose matches any instance of a given string; Exact matches the entire field.",
495
+ )
496
+ weight = forms.IntegerField(
497
+ required=False, label="Weight", help_text="Fields with higher weights appear lower in a form."
498
+ )
499
+ advanced_ui = forms.NullBooleanField(
500
+ required=False,
501
+ widget=BulkEditNullBooleanSelect,
502
+ label="Move to Advanced tab",
503
+ help_text="Hide this field from the object's primary information tab. It will appear in the 'Advanced' tab instead.",
504
+ )
505
+ add_content_types = MultipleContentTypeField(
506
+ limit_choices_to=FeatureQuery("custom_fields"), required=False, label="Add Content Types"
507
+ )
508
+ remove_content_types = MultipleContentTypeField(
509
+ limit_choices_to=FeatureQuery("custom_fields"), required=False, label="Remove Content Types"
510
+ )
511
+
512
+ class Meta:
513
+ model = CustomField
514
+ fields = (
515
+ "grouping",
516
+ "description",
517
+ "required",
518
+ "filter_logic",
519
+ "weight",
520
+ "advanced_ui",
521
+ "add_content_types",
522
+ "remove_content_types",
523
+ )
524
+ nullable_fields = [
525
+ "grouping",
526
+ "description",
527
+ ]
528
+
529
+
467
530
  class CustomFieldForm(BootstrapMixin, forms.ModelForm):
468
531
  label = forms.CharField(
469
532
  required=True, max_length=CHARFIELD_MAX_LENGTH, help_text="Name of the field as displayed to users."
@@ -2082,6 +2145,14 @@ class RoleFilterForm(NautobotFilterForm):
2082
2145
  #
2083
2146
 
2084
2147
 
2148
+ class SecretsGroupBulkEditForm(NautobotBulkEditForm):
2149
+ pk = forms.ModelMultipleChoiceField(queryset=SecretsGroup.objects.all(), widget=forms.MultipleHiddenInput())
2150
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
2151
+
2152
+ class Meta:
2153
+ model = SecretsGroup
2154
+
2155
+
2085
2156
  def provider_choices():
2086
2157
  return sorted([(slug, provider.name) for slug, provider in registry["secrets_providers"].items()])
2087
2158
 
@@ -95,7 +95,7 @@ class CustomFieldModelFilterFormMixin(forms.Form):
95
95
  def __init__(self, *args, **kwargs):
96
96
  super().__init__(*args, **kwargs)
97
97
 
98
- custom_fields = CustomField.objects.get_for_model(self.model, exclude_filter_disabled=True)
98
+ custom_fields = CustomField.objects.get_for_model(self.model, exclude_filter_disabled=True, get_queryset=False)
99
99
  self.custom_fields = []
100
100
  for cf in custom_fields:
101
101
  field_name = cf.add_prefix_to_cf_key()
@@ -714,7 +714,9 @@ class RelationshipModelFilterFormMixin(forms.Form):
714
714
  """
715
715
  Append form fields for all Relationships assigned to this model.
716
716
  """
717
- src_relationships, dst_relationships = Relationship.objects.get_for_model(model=self.model, hidden=False)
717
+ src_relationships, dst_relationships = Relationship.objects.get_for_model(
718
+ model=self.model, hidden=False, get_queryset=False
719
+ )
718
720
 
719
721
  for rel in src_relationships:
720
722
  self._append_relationships_side([rel], RelationshipSideChoices.SIDE_SOURCE)
@@ -111,10 +111,13 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
111
111
  except Job.DoesNotExist:
112
112
  pass
113
113
 
114
+ if "date_started" in kwargs:
115
+ fields["date_started"] = kwargs["date_started"]
116
+
114
117
  obj, created = self.using(using).get_or_create(id=task_id, defaults=fields)
115
118
 
116
119
  if not created:
117
- # Make sure `date_done` is allowed to stay null until the task reacheas a ready state.
120
+ # Make sure `date_done` is allowed to stay null until the task reaches a ready state.
118
121
  #
119
122
  # Default behavior in `django-celery-results` has this field as a
120
123
  # `DateField(auto_now=True)` which just automatically updates the `date_done` field on every
@@ -1,7 +1,6 @@
1
1
  # Generated by Django 3.2.16 on 2022-11-19 23:15
2
2
 
3
3
  from collections import namedtuple
4
- import logging
5
4
 
6
5
  from django.db import migrations, models
7
6
 
@@ -24,8 +23,6 @@ color_map = {
24
23
  "success": ColorChoices.COLOR_GREEN,
25
24
  }
26
25
 
27
- logger = logging.getLogger(__name__)
28
-
29
26
 
30
27
  def create_equivalent_roles_of_virtualmachine_device_role(apps):
31
28
  """Create equivalent roles for the VirtualMachine DeviceRole."""
@@ -82,17 +79,43 @@ def create_roles(apps, roles_to_create, content_types):
82
79
  "description": old_role.description,
83
80
  "color": getattr(old_role, "color", color_map["default"]),
84
81
  "weight": getattr(old_role, "weight", None),
82
+ "_custom_field_data": getattr(old_role, "_custom_field_data", {}),
85
83
  },
86
84
  )
87
85
  if created:
88
- logger.info(f'Created Role "{new_role.name}"')
86
+ print(f" Created Role {new_role.name!r}")
87
+ else:
88
+ updated = False
89
+ # Fix up in the case of overlapping roles
90
+ if new_role.weight is None and hasattr(old_role, "weight"):
91
+ new_role.weight = old_role.weight
92
+ updated = True
93
+ if old_role.description and not new_role.description:
94
+ new_role.description = old_role.description
95
+ updated = True
96
+ if hasattr(old_role, "_custom_field_data"):
97
+ for field, value in old_role._custom_field_data.items():
98
+ if field in new_role._custom_field_data:
99
+ if new_role._custom_field_data[field] != value:
100
+ print(
101
+ f" Value conflict for custom field {field!r} for role {new_role.name!r} - DATA LOSS"
102
+ )
103
+ else:
104
+ new_role._custom_field_data[field] = value
105
+ updated = True
106
+ if updated:
107
+ print(f" Updated Role {new_role.name!r}")
108
+ new_role.save()
109
+ else:
110
+ print(f" No changes to already-created Role {new_role.name!r}")
111
+
89
112
  if old_role_ct and hasattr(old_role, "pk"):
90
113
  # Move over existing object change records to the new role we created
91
114
  updated_count = ObjectChange.objects.filter(
92
115
  changed_object_type=old_role_ct, changed_object_id=old_role.pk
93
116
  ).update(changed_object_type=new_role_ct, changed_object_id=new_role.pk)
94
- logger.info(
95
- f'Updated {updated_count} ObjectChanges from the existing "{old_role.name}" {old_role._meta.label}'
117
+ print(
118
+ f" Updated {updated_count} ObjectChanges from the existing {old_role.name!r} {old_role._meta.label}"
96
119
  )
97
120
 
98
121
  # This is for all of the change records for roles which no longer exist
@@ -100,7 +123,7 @@ def create_roles(apps, roles_to_create, content_types):
100
123
  updated_count = ObjectChange.objects.filter(changed_object_type=old_role_ct).update(
101
124
  changed_object_type=new_role_ct
102
125
  )
103
- logger.info(f"Updated {updated_count} leftover ObjectChanges from deleted {old_role_ct.model} records.")
126
+ print(f" Updated {updated_count} leftover ObjectChanges from deleted {old_role_ct.model} records.")
104
127
 
105
128
  roles = Role.objects.filter(name__in=[roles.name for roles in roles_to_create])
106
129
 
@@ -0,0 +1,16 @@
1
+ # Generated by Django 4.2.23 on 2025-07-31 23:49
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("extras", "0123_alter_joblogentry_created"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddIndex(
13
+ model_name="joblogentry",
14
+ index=models.Index(fields=["job_result", "created"], name="extras_joblog_jr_created_idx"),
15
+ ),
16
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.23 on 2025-08-11 16:18
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("django_celery_results", "0012_taskresult_date_started"),
9
+ ("extras", "0124_add_joblogentry_index"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="jobresult",
15
+ name="date_started",
16
+ field=models.DateTimeField(blank=True, db_index=True, null=True),
17
+ ),
18
+ ]
@@ -1,4 +1,4 @@
1
- from collections import OrderedDict
1
+ from collections import defaultdict, OrderedDict
2
2
  from datetime import date, datetime
3
3
  import json
4
4
  import logging
@@ -48,21 +48,42 @@ logger = logging.getLogger(__name__)
48
48
  class ComputedFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
49
49
  use_in_migrations = True
50
50
 
51
- def get_for_model(self, model):
51
+ def get_for_model(self, model, get_queryset=True):
52
52
  """
53
53
  Return all ComputedFields assigned to the given model.
54
+
55
+ Returns a queryset by default, or a list if `get_queryset` param is False.
54
56
  """
55
57
  concrete_model = model._meta.concrete_model
56
58
  cache_key = f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
59
+ list_cache_key = f"{cache_key}.list"
60
+ if not get_queryset:
61
+ listing = cache.get(list_cache_key)
62
+ if listing is not None:
63
+ return listing
57
64
  queryset = cache.get(cache_key)
58
65
  if queryset is None:
59
66
  content_type = ContentType.objects.get_for_model(concrete_model)
60
67
  queryset = self.get_queryset().filter(content_type=content_type)
61
68
  cache.set(cache_key, queryset)
69
+ if not get_queryset:
70
+ listing = list(queryset)
71
+ cache.set(list_cache_key, listing)
72
+ return listing
62
73
  return queryset
63
74
 
64
75
  get_for_model.cache_key_prefix = "nautobot.extras.computedfield.get_for_model"
65
76
 
77
+ def populate_list_caches(self):
78
+ """Populate all caches for `get_for_model(..., get_queryset=False)` lookups."""
79
+ queryset = self.all().select_related("content_type")
80
+ listings = defaultdict(list)
81
+ for cf in queryset:
82
+ listings[f"{cf.content_type.app_label}.{cf.content_type.model}"].append(cf)
83
+ for ct in ContentType.objects.all():
84
+ label = f"{ct.app_label}.{ct.model}"
85
+ cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.list", listings[label])
86
+
66
87
 
67
88
  @extras_features("graphql")
68
89
  class ComputedField(
@@ -370,18 +391,24 @@ class CustomFieldModel(models.Model):
370
391
  class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
371
392
  use_in_migrations = True
372
393
 
373
- def get_for_model(self, model, exclude_filter_disabled=False):
394
+ def get_for_model(self, model, exclude_filter_disabled=False, get_queryset=True):
374
395
  """
375
396
  Return (and cache) all CustomFields assigned to the given model.
376
397
 
377
398
  Args:
378
399
  model (Model): The django model to which custom fields are registered
379
400
  exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
401
+ get_queryset (bool): Whether to return a QuerySet or a list.
380
402
  """
381
403
  concrete_model = model._meta.concrete_model
382
404
  cache_key = (
383
405
  f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}.{exclude_filter_disabled}"
384
406
  )
407
+ list_cache_key = f"{cache_key}.list"
408
+ if not get_queryset:
409
+ listing = cache.get(list_cache_key)
410
+ if listing is not None:
411
+ return listing
385
412
  queryset = cache.get(cache_key)
386
413
  if queryset is None:
387
414
  content_type = ContentType.objects.get_for_model(concrete_model)
@@ -389,6 +416,10 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
389
416
  if exclude_filter_disabled:
390
417
  queryset = queryset.exclude(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED)
391
418
  cache.set(cache_key, queryset)
419
+ if not get_queryset:
420
+ listing = list(queryset)
421
+ cache.set(list_cache_key, listing)
422
+ return listing
392
423
  return queryset
393
424
 
394
425
  get_for_model.cache_key_prefix = "nautobot.extras.customfield.get_for_model"
@@ -405,6 +436,24 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
405
436
 
406
437
  keys_for_model.cache_key_prefix = "nautobot.extras.customfield.keys_for_model"
407
438
 
439
+ def populate_list_caches(self):
440
+ """Populate all caches for `get_for_model(..., get_queryset=False)` and `keys_for_model` lookups."""
441
+ queryset = self.all().prefetch_related("content_types")
442
+ cf_listings = defaultdict(lambda: defaultdict(list))
443
+ key_listings = defaultdict(list)
444
+ for cf in queryset:
445
+ for ct in cf.content_types.all():
446
+ label = f"{ct.app_label}.{ct.model}"
447
+ cf_listings[label][False].append(cf)
448
+ if cf.filter_logic != CustomFieldFilterLogicChoices.FILTER_DISABLED:
449
+ cf_listings[label][True].append(cf)
450
+ key_listings[label].append(cf.key)
451
+ for ct in ContentType.objects.all():
452
+ label = f"{ct.app_label}.{ct.model}"
453
+ cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.True.list", cf_listings[label][True])
454
+ cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.False.list", cf_listings[label][False])
455
+ cache.set(f"{self.keys_for_model.cache_key_prefix}.{label}", key_listings[label])
456
+
408
457
 
409
458
  @extras_features("webhooks")
410
459
  class CustomField(
@@ -465,8 +514,7 @@ class CustomField(
465
514
  blank=True,
466
515
  null=True,
467
516
  help_text=(
468
- "Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. "
469
- '"Foo").'
517
+ 'Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. "Foo").'
470
518
  ),
471
519
  )
472
520
  weight = models.PositiveSmallIntegerField(
@@ -100,8 +100,7 @@ class GitRepository(PrimaryModel):
100
100
 
101
101
  if self.present_in_database and self.slug != self.__initial_slug:
102
102
  raise ValidationError(
103
- f"Slug cannot be changed once set. Current slug is {self.__initial_slug}, "
104
- f"requested slug is {self.slug}"
103
+ f"Slug cannot be changed once set. Current slug is {self.__initial_slug}, requested slug is {self.slug}"
105
104
  )
106
105
 
107
106
  if not self.present_in_database:
@@ -543,6 +543,12 @@ class JobLogEntry(BaseModel):
543
543
  ordering = ["created"]
544
544
  get_latest_by = "created"
545
545
  verbose_name_plural = "job log entries"
546
+ indexes = [
547
+ models.Index(
548
+ name="extras_joblog_jr_created_idx",
549
+ fields=["job_result", "created"],
550
+ )
551
+ ]
546
552
 
547
553
 
548
554
  #
@@ -644,6 +650,7 @@ class JobResult(BaseModel, CustomFieldModel):
644
650
  help_text="Registered name of the Celery task for this job. Internal use only.",
645
651
  )
646
652
  date_created = models.DateTimeField(auto_now_add=True, db_index=True)
653
+ date_started = models.DateTimeField(null=True, blank=True, db_index=True)
647
654
  date_done = models.DateTimeField(null=True, blank=True, db_index=True)
648
655
  user = models.ForeignKey(
649
656
  to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="+", blank=True, null=True
@@ -708,7 +715,7 @@ class JobResult(BaseModel, CustomFieldModel):
708
715
  natural_key_field_names = ["id"]
709
716
 
710
717
  def __str__(self):
711
- return f"{self.name} started at {self.date_created} ({self.status})"
718
+ return f"{self.name} created at {self.date_created} ({self.status})"
712
719
 
713
720
  def as_dict(self):
714
721
  """This is required by the django-celery-results DB backend."""
@@ -730,7 +737,8 @@ class JobResult(BaseModel, CustomFieldModel):
730
737
  if not self.date_done:
731
738
  return None
732
739
 
733
- duration = self.date_done - self.date_created
740
+ # Older records may not have a date_started value, so we use date_created as a fallback.
741
+ duration = self.date_done - (self.date_started or self.date_created)
734
742
  minutes, seconds = divmod(duration.total_seconds(), 60)
735
743
 
736
744
  return f"{int(minutes)} minutes, {seconds:.2f} seconds"
@@ -748,7 +756,8 @@ class JobResult(BaseModel, CustomFieldModel):
748
756
  # Only add metrics if we have a related job model. If we are moving to a terminal state we should always
749
757
  # have a related job model, so this shouldn't be too tight of a restriction.
750
758
  if self.job_model:
751
- duration = self.date_done - self.date_created
759
+ # Older records may not have a date_started value, so we use date_created as a fallback.
760
+ duration = self.date_done - (self.date_started or self.date_created)
752
761
  JOB_RESULT_METRIC.labels(self.job_model.grouping, self.job_model.name, status).observe(
753
762
  duration.total_seconds()
754
763
  )
@@ -878,6 +887,7 @@ class JobResult(BaseModel, CustomFieldModel):
878
887
  # synchronous tasks are run before the JobResult is saved, so any fields required by
879
888
  # the job must be added before calling `apply()`
880
889
  job_result.celery_kwargs = job_celery_kwargs
890
+ job_result.date_started = timezone.now()
881
891
  job_result.save()
882
892
 
883
893
  # setup synchronous task logging