nautobot 2.4.16__py3-none-any.whl → 2.4.18__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 (434) hide show
  1. nautobot/apps/utils.py +2 -0
  2. nautobot/apps/views.py +2 -0
  3. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
  4. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
  5. nautobot/circuits/tests/integration/test_circuit.py +2 -2
  6. nautobot/circuits/views.py +32 -15
  7. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
  8. nautobot/cloud/views.py +7 -0
  9. nautobot/core/apps/__init__.py +1 -0
  10. nautobot/core/celery/__init__.py +2 -1
  11. nautobot/core/filters.py +2 -2
  12. nautobot/core/settings.py +1 -0
  13. nautobot/core/settings.yaml +9 -0
  14. nautobot/core/tables.py +21 -23
  15. nautobot/core/templates/components/breadcrumbs.html +19 -0
  16. nautobot/core/templates/components/panel/panel.html +1 -1
  17. nautobot/core/templates/generic/object_changelog.html +0 -2
  18. nautobot/core/templates/generic/object_list.html +15 -12
  19. nautobot/core/templates/generic/object_notes.html +0 -2
  20. nautobot/core/templates/generic/object_retrieve.html +16 -9
  21. nautobot/core/templates/inc/paginator.html +3 -3
  22. nautobot/core/templates/inc/table.html +2 -2
  23. nautobot/core/templatetags/helpers.py +104 -6
  24. nautobot/core/templatetags/ui_framework.py +40 -5
  25. nautobot/core/testing/filters.py +37 -21
  26. nautobot/core/testing/mixins.py +1 -1
  27. nautobot/core/testing/views.py +27 -4
  28. nautobot/core/tests/test_tables.py +43 -6
  29. nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
  30. nautobot/core/tests/test_titles.py +2 -2
  31. nautobot/core/tests/test_ui.py +14 -1
  32. nautobot/core/tests/test_views.py +45 -0
  33. nautobot/core/ui/breadcrumbs.py +13 -8
  34. nautobot/core/ui/bulk_buttons.py +53 -53
  35. nautobot/core/ui/object_detail.py +52 -9
  36. nautobot/core/ui/titles.py +9 -5
  37. nautobot/core/utils/data.py +13 -0
  38. nautobot/core/utils/deprecation.py +2 -0
  39. nautobot/core/views/__init__.py +24 -3
  40. nautobot/core/views/generic.py +42 -17
  41. nautobot/core/views/mixins.py +146 -12
  42. nautobot/core/views/utils.py +117 -0
  43. nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
  44. nautobot/dcim/models/device_component_templates.py +4 -2
  45. nautobot/dcim/models/device_components.py +3 -2
  46. nautobot/dcim/models/devices.py +4 -0
  47. nautobot/dcim/tables/__init__.py +2 -0
  48. nautobot/dcim/tables/devices.py +24 -0
  49. nautobot/dcim/tables/power.py +2 -2
  50. nautobot/dcim/templates/dcim/device/base.html +1 -11
  51. nautobot/dcim/templates/dcim/device_component.html +0 -19
  52. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -16
  53. nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
  54. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
  55. nautobot/dcim/tests/test_views.py +41 -0
  56. nautobot/dcim/views.py +169 -39
  57. nautobot/extras/filters/mixins.py +1 -1
  58. nautobot/extras/forms/forms.py +15 -0
  59. nautobot/extras/models/customfields.py +45 -9
  60. nautobot/extras/models/groups.py +10 -1
  61. nautobot/extras/models/jobs.py +2 -2
  62. nautobot/extras/plugins/views.py +18 -5
  63. nautobot/extras/tables.py +4 -2
  64. nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
  65. nautobot/extras/templates/extras/configcontext_update.html +49 -49
  66. nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
  67. nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
  68. nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
  69. nautobot/extras/templates/extras/dynamicgroup.html +2 -99
  70. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
  71. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
  72. nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
  73. nautobot/extras/templates/extras/gitrepository.html +2 -82
  74. nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
  75. nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
  76. nautobot/extras/templates/extras/gitrepository_update.html +13 -0
  77. nautobot/extras/templates/extras/inc/job_table.html +1 -1
  78. nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
  79. nautobot/extras/templates/extras/note_retrieve.html +1 -53
  80. nautobot/extras/templates/extras/plugin_detail.html +3 -7
  81. nautobot/extras/templates/extras/plugins_list.html +0 -2
  82. nautobot/extras/templates/extras/tag_retrieve.html +1 -1
  83. nautobot/extras/templates/extras/tag_update.html +14 -14
  84. nautobot/extras/templates/extras/team_retrieve.html +1 -1
  85. nautobot/extras/tests/test_dynamicgroups.py +73 -18
  86. nautobot/extras/tests/test_models.py +216 -0
  87. nautobot/extras/tests/test_views.py +7 -2
  88. nautobot/extras/urls.py +2 -94
  89. nautobot/extras/views.py +425 -430
  90. nautobot/ipam/apps.py +1 -0
  91. nautobot/ipam/jobs/__init__.py +10 -0
  92. nautobot/ipam/jobs/cleanup.py +296 -0
  93. nautobot/ipam/models.py +301 -178
  94. nautobot/ipam/querysets.py +3 -3
  95. nautobot/ipam/signals.py +6 -1
  96. nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
  97. nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
  98. nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
  99. nautobot/ipam/templates/ipam/prefix.html +0 -8
  100. nautobot/ipam/templates/ipam/prefix_list.html +1 -1
  101. nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
  102. nautobot/ipam/tests/test_api.py +5 -0
  103. nautobot/ipam/tests/test_jobs.py +454 -0
  104. nautobot/ipam/tests/test_models.py +677 -122
  105. nautobot/ipam/tests/test_querysets.py +46 -0
  106. nautobot/ipam/tests/test_views.py +40 -164
  107. nautobot/ipam/urls.py +0 -11
  108. nautobot/ipam/utils/migrations.py +1 -1
  109. nautobot/ipam/utils/testing.py +9 -4
  110. nautobot/ipam/views.py +175 -235
  111. nautobot/project-static/docs/404.html +9 -6
  112. nautobot/project-static/docs/apps/index.html +9 -6
  113. nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
  114. nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
  115. nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
  116. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
  117. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
  118. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
  119. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
  120. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
  121. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
  122. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
  123. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
  124. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
  125. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
  126. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
  127. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
  128. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
  129. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
  130. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
  131. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
  132. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
  133. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
  134. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
  135. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
  136. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
  137. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +81 -6
  138. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +73 -18
  139. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
  140. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
  141. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +402 -21
  142. nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
  143. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
  144. nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
  145. nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
  146. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
  147. nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
  148. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -9
  149. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
  150. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
  151. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
  152. nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
  153. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
  154. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
  155. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
  156. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
  157. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
  158. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
  159. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
  160. nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
  161. nautobot/project-static/docs/development/apps/api/setup.html +9 -6
  162. nautobot/project-static/docs/development/apps/api/testing.html +9 -6
  163. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
  164. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
  165. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
  166. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
  167. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
  168. nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
  169. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
  170. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
  171. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
  172. nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
  173. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
  174. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
  175. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
  176. nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
  177. nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
  178. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
  179. nautobot/project-static/docs/development/apps/index.html +9 -6
  180. nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
  181. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
  182. nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
  183. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
  184. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
  185. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
  186. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
  187. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
  188. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
  189. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
  190. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
  191. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
  192. nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
  193. nautobot/project-static/docs/development/core/application-registry.html +23 -20
  194. nautobot/project-static/docs/development/core/best-practices.html +23 -20
  195. nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
  196. nautobot/project-static/docs/development/core/caching.html +9 -6
  197. nautobot/project-static/docs/development/core/controllers.html +9 -6
  198. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
  199. nautobot/project-static/docs/development/core/generic-views.html +9 -6
  200. nautobot/project-static/docs/development/core/getting-started.html +9 -21
  201. nautobot/project-static/docs/development/core/homepage.html +12 -9
  202. nautobot/project-static/docs/development/core/index.html +9 -6
  203. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
  204. nautobot/project-static/docs/development/core/model-checklist.html +9 -6
  205. nautobot/project-static/docs/development/core/model-features.html +11 -8
  206. nautobot/project-static/docs/development/core/natural-keys.html +21 -18
  207. nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
  208. nautobot/project-static/docs/development/core/release-checklist.html +9 -6
  209. nautobot/project-static/docs/development/core/role-internals.html +9 -6
  210. nautobot/project-static/docs/development/core/settings.html +9 -6
  211. nautobot/project-static/docs/development/core/style-guide.html +32 -29
  212. nautobot/project-static/docs/development/core/templates.html +9 -6
  213. nautobot/project-static/docs/development/core/testing.html +10 -7
  214. nautobot/project-static/docs/development/core/ui-component-framework.html +42 -44
  215. nautobot/project-static/docs/development/core/user-preferences.html +9 -6
  216. nautobot/project-static/docs/development/index.html +9 -6
  217. nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
  218. nautobot/project-static/docs/development/jobs/index.html +9 -6
  219. nautobot/project-static/docs/development/jobs/installation.html +23 -20
  220. nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
  221. nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
  222. nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
  223. nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
  224. nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
  225. nautobot/project-static/docs/development/jobs/testing.html +14 -11
  226. nautobot/project-static/docs/index.html +9 -6
  227. nautobot/project-static/docs/objects.inv +0 -0
  228. nautobot/project-static/docs/overview/application_stack.html +9 -6
  229. nautobot/project-static/docs/overview/design_philosophy.html +9 -6
  230. nautobot/project-static/docs/release-notes/index.html +9 -6
  231. nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
  232. nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
  233. nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
  234. nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
  235. nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
  236. nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
  237. nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
  238. nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
  239. nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
  240. nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
  241. nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
  242. nautobot/project-static/docs/release-notes/version-2.4.html +489 -6
  243. nautobot/project-static/docs/search/search_index.json +1 -1
  244. nautobot/project-static/docs/sitemap.xml +301 -301
  245. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  246. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
  247. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
  248. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
  249. nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
  250. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
  251. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -8
  252. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
  253. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
  254. nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
  255. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
  256. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
  257. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
  258. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
  259. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
  260. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
  261. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
  262. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
  263. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
  264. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
  265. nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
  266. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
  267. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
  268. nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
  269. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
  270. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
  271. nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
  272. nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
  273. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
  274. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
  275. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
  276. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
  277. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
  278. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
  279. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
  280. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
  281. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
  282. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
  283. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
  284. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
  285. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
  286. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
  287. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
  288. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
  289. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
  290. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
  291. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
  292. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
  293. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
  294. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
  295. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
  339. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
  340. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
  341. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
  342. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
  343. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
  344. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
  345. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
  346. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
  347. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
  348. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
  349. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
  350. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
  351. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
  352. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
  353. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
  354. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
  355. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
  356. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
  357. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
  358. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
  359. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
  360. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
  361. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
  362. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
  363. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
  364. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
  365. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
  366. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
  367. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
  368. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
  369. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
  370. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
  371. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
  372. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
  373. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
  374. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
  375. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
  376. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
  377. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
  378. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
  379. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
  380. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
  381. nautobot/project-static/docs/user-guide/index.html +9 -6
  382. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
  383. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
  384. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
  385. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
  386. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
  387. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
  388. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
  389. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
  390. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
  391. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
  392. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
  393. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
  394. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
  395. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
  396. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
  397. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
  398. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
  399. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
  400. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
  401. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
  402. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
  403. nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
  404. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
  405. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
  406. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
  407. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
  408. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
  409. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
  410. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
  411. nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
  412. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
  413. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
  414. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
  415. nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
  416. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
  417. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
  418. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
  419. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
  420. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
  421. nautobot/project-static/fonts/UFL.txt +96 -96
  422. nautobot/project-static/img/nautobot_icon.svg +32 -34
  423. nautobot/project-static/js/forms.js +35 -2
  424. nautobot/project-static/js/table_sorting_indicator.js +0 -2
  425. nautobot/virtualization/filters.py +7 -0
  426. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/METADATA +8 -8
  427. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/RECORD +431 -421
  428. nautobot/core/templates/inc/breadcrumbs.html +0 -14
  429. nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
  430. nautobot/project-static/docs/requirements.txt +0 -14
  431. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/LICENSE.txt +0 -0
  432. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/NOTICE +0 -0
  433. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/WHEEL +0 -0
  434. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/entry_points.txt +0 -0
@@ -8,6 +8,7 @@ import urllib.parse
8
8
  from django.apps import apps
9
9
  from django.conf import settings
10
10
  from django.contrib.contenttypes.models import ContentType
11
+ from django.core.cache import cache
11
12
  from django.core.files.uploadedfile import SimpleUploadedFile
12
13
  from django.test import override_settings, RequestFactory
13
14
  from django.test.utils import override_script_prefix
@@ -23,6 +24,7 @@ from nautobot.core.testing.utils import extract_page_body
23
24
  from nautobot.core.utils.permissions import get_permission_for_model
24
25
  from nautobot.core.views import NautobotMetricsView
25
26
  from nautobot.core.views.mixins import GetReturnURLMixin
27
+ from nautobot.core.views.utils import METRICS_CACHE_KEY
26
28
  from nautobot.dcim.models.locations import Location
27
29
  from nautobot.extras.choices import CustomFieldTypeChoices
28
30
  from nautobot.extras.models import FileProxy, Status
@@ -566,6 +568,49 @@ class MetricsViewTestCase(TestCase):
566
568
  metric_names_with_app.remove(test_metric_name)
567
569
  self.assertSetEqual(metric_names_with_app, metric_names_without_app)
568
570
 
571
+ def test_enabled_metrics_cache_disabled(self):
572
+ """Assert that when cache is disabled the cache enabled function doesn't get called."""
573
+ with mock.patch("nautobot.core.views.utils.generate_latest_with_cache") as mock_generate_latest_with_cache:
574
+ self.query_and_parse_metrics()
575
+ self.assertTrue(mock_generate_latest_with_cache.call_count == 0)
576
+
577
+ @override_settings(METRICS_EXPERIMENTAL_CACHING_DURATION=30)
578
+ def test_enabled_metrics_cache_enabled(self):
579
+ """Assert that multiple calls to metrics with caching returns expected response."""
580
+ test_metric_name = "nautobot_example_metric_count"
581
+ metrics_with_app = self.query_and_parse_metrics()
582
+ metrics_with_app_cached = self.query_and_parse_metrics()
583
+ metric_names_with_app = {metric.name for metric in metrics_with_app}
584
+ metric_names_with_app_cached = {metric.name for metric in metrics_with_app_cached}
585
+ self.assertIn(test_metric_name, metric_names_with_app)
586
+ self.assertIn(test_metric_name, metric_names_with_app_cached)
587
+
588
+ # In some circumstances (e.g. if this test is run first) metrics_with_app may not have
589
+ # metrics from Django like total view counts. Since metrics_with_app_cached is called
590
+ # second, it should have everything that metrics_with_app has (plus potentially more).
591
+ # We at least want to ensure the cached version has everything the non-cached version has.
592
+ self.assertTrue(
593
+ metric_names_with_app.issubset(metric_names_with_app_cached),
594
+ msg="Cached metrics should be a superset of non-cached metrics.",
595
+ )
596
+ with mock.patch("nautobot.core.views.generate_latest_with_cache") as mock_generate_latest_with_cache:
597
+ self.query_and_parse_metrics()
598
+ self.query_and_parse_metrics()
599
+ self.assertEqual(mock_generate_latest_with_cache.call_count, 2)
600
+
601
+ from example_app.metrics import metric_example
602
+
603
+ cache.delete(METRICS_CACHE_KEY) # Ensure we start with a clean cache for the next part of the test
604
+ # Assert that the metric function only gets called once even though we scrape metrics twice
605
+
606
+ mock_metric_function = mock.Mock(name="mock_metric_function", side_effect=metric_example)
607
+ with mock.patch.dict("nautobot.core.views.registry", app_metrics=[mock_metric_function]):
608
+ self.query_and_parse_metrics()
609
+ first_call_count = mock_metric_function.call_count
610
+ self.query_and_parse_metrics()
611
+ second_call_count = mock_metric_function.call_count
612
+ self.assertEqual(first_call_count, second_call_count)
613
+
569
614
 
570
615
  class AuthenticateMetricsTestCase(APITestCase):
571
616
  def test_metrics_authentication(self):
@@ -192,9 +192,14 @@ class ViewNameBreadcrumbItem(BaseBreadcrumbItem):
192
192
 
193
193
  def get_label(self, context: Context) -> str:
194
194
  if self.label_from_view_name:
195
- model = get_model_for_view_name(self.get_view_name(context))
196
- if model is not None:
197
- return model._meta.verbose_name_plural
195
+ try:
196
+ model = get_model_for_view_name(self.get_view_name(context))
197
+ if model is not None:
198
+ return model._meta.verbose_name_plural
199
+ except ValueError:
200
+ # `get_model_for_view_name` is not working properly with some proper paths like "home"
201
+ # and because by default we're trying to resolve label by using `list_url` this error may occur in some apps
202
+ pass
198
203
  return super().get_label(context)
199
204
 
200
205
  def get_view_name(self, context: Context) -> Optional[str]:
@@ -401,6 +406,7 @@ class Breadcrumbs:
401
406
  # Default breadcrumb if view defines `list_url` in the Context
402
407
  ViewNameBreadcrumbItem(
403
408
  view_name_key="list_url",
409
+ label_key="title",
404
410
  label_from_view_name=True,
405
411
  should_render=lambda context: context.get("list_url") is not None,
406
412
  ),
@@ -411,7 +417,7 @@ class Breadcrumbs:
411
417
  def __init__(
412
418
  self,
413
419
  items: BreadcrumbItemsType = None,
414
- template: str = "inc/breadcrumbs.html",
420
+ template: str = "components/breadcrumbs.html",
415
421
  ):
416
422
  """
417
423
  Initialize the Breadcrumbs configuration.
@@ -447,7 +453,7 @@ class Breadcrumbs:
447
453
  Returns:
448
454
  (list[tuple[str, str]]): A list of (url, label) tuples representing breadcrumb entries.
449
455
  """
450
- action = context.get("view_action", "list")
456
+ action = context.get("view_action", "")
451
457
  detail = context.get("detail", False)
452
458
  items = self.get_items_for_action(self.items, action, detail)
453
459
  return [item.as_pair(context) for item in items if item.should_render(context)]
@@ -457,7 +463,7 @@ class Breadcrumbs:
457
463
  Filters out all items that both label and url are None or empty str.
458
464
 
459
465
  Args:
460
- items (list[tuple[str, str]]): breadcrumb items pair.s
466
+ items (list[tuple[str, str]]): breadcrumb items pairs.
461
467
  context (Context): The view or template context.
462
468
 
463
469
  Returns:
@@ -481,8 +487,7 @@ class Breadcrumbs:
481
487
  @staticmethod
482
488
  def get_items_for_action(items: BreadcrumbItemsType, action: str, detail: bool) -> list[BaseBreadcrumbItem]:
483
489
  """
484
- Get the breadcrumb items for a specific action, with fallback to 'detail' if not found
485
- and to asterisk (*) if present.
490
+ Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
486
491
 
487
492
  Args:
488
493
  items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
@@ -1,53 +1,53 @@
1
- from nautobot.core.choices import ButtonActionColorChoices
2
- from nautobot.core.ui import object_detail
3
-
4
-
5
- class BaseBulkButton(object_detail.FormButton):
6
- """Base class for bulk action buttons."""
7
-
8
- action = None
9
- color = None
10
- icon = None
11
- label = None
12
- weight = None
13
-
14
- def __init__(self, *, form_id: str, model, **kwargs):
15
- model_name = model.__name__.lower()
16
- app_label = model._meta.app_label
17
- link_name = f"{app_label}:{model_name}_bulk_{self.action}"
18
-
19
- super().__init__(
20
- link_name=link_name,
21
- link_includes_pk=False,
22
- label=self.label,
23
- color=self.color,
24
- icon=self.icon,
25
- size="xs",
26
- form_id=form_id,
27
- weight=self.weight,
28
- **kwargs,
29
- )
30
-
31
-
32
- class BulkRenameButton(BaseBulkButton):
33
- action = "rename"
34
- color = ButtonActionColorChoices.RENAME
35
- icon = "mdi-pencil"
36
- label = "Rename"
37
- weight = 200
38
-
39
-
40
- class BulkEditButton(BaseBulkButton):
41
- action = "edit"
42
- color = ButtonActionColorChoices.EDIT
43
- icon = "mdi-pencil"
44
- label = "Edit"
45
- weight = 300
46
-
47
-
48
- class BulkDeleteButton(BaseBulkButton):
49
- action = "delete"
50
- color = ButtonActionColorChoices.DELETE
51
- icon = "mdi-trash-can-outline"
52
- label = "Delete"
53
- weight = 400
1
+ from nautobot.core.choices import ButtonActionColorChoices
2
+ from nautobot.core.ui import object_detail
3
+
4
+
5
+ class BaseBulkButton(object_detail.FormButton):
6
+ """Base class for bulk action buttons."""
7
+
8
+ action = None
9
+ color = None
10
+ icon = None
11
+ label = None
12
+ weight = None
13
+
14
+ def __init__(self, *, form_id: str, model, **kwargs):
15
+ model_name = model.__name__.lower()
16
+ app_label = model._meta.app_label
17
+ link_name = f"{app_label}:{model_name}_bulk_{self.action}"
18
+
19
+ super().__init__(
20
+ link_name=link_name,
21
+ link_includes_pk=False,
22
+ label=self.label,
23
+ color=self.color,
24
+ icon=self.icon,
25
+ size="xs",
26
+ form_id=form_id,
27
+ weight=self.weight,
28
+ **kwargs,
29
+ )
30
+
31
+
32
+ class BulkRenameButton(BaseBulkButton):
33
+ action = "rename"
34
+ color = ButtonActionColorChoices.RENAME
35
+ icon = "mdi-pencil"
36
+ label = "Rename"
37
+ weight = 200
38
+
39
+
40
+ class BulkEditButton(BaseBulkButton):
41
+ action = "edit"
42
+ color = ButtonActionColorChoices.EDIT
43
+ icon = "mdi-pencil"
44
+ label = "Edit"
45
+ weight = 300
46
+
47
+
48
+ class BulkDeleteButton(BaseBulkButton):
49
+ action = "delete"
50
+ color = ButtonActionColorChoices.DELETE
51
+ icon = "mdi-trash-can-outline"
52
+ label = "Delete"
53
+ weight = 400
@@ -9,6 +9,7 @@ from django.contrib.contenttypes.models import ContentType
9
9
  from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
10
10
  from django.db import models
11
11
  from django.db.models import CharField, JSONField, Q, URLField
12
+ from django.db.models.constants import LOOKUP_SEP
12
13
  from django.db.models.fields.related import ManyToManyField
13
14
  from django.template import Context
14
15
  from django.template.defaultfilters import truncatechars
@@ -459,7 +460,7 @@ class DistinctViewTab(Tab):
459
460
  return format_html(
460
461
  "{} {}",
461
462
  self.label,
462
- render_to_string("utilities/templatetags/badge.html", badge(related_obj_count)),
463
+ render_to_string("utilities/templatetags/badge.html", badge(related_obj_count, True)),
463
464
  )
464
465
  except AttributeError:
465
466
  logger.warning(
@@ -704,6 +705,7 @@ class ObjectsTablePanel(Panel):
704
705
  footer_content_template_path="components/panel/footer_content_table.html",
705
706
  footer_buttons=None,
706
707
  form_id=None,
708
+ include_paginator=False,
707
709
  **kwargs,
708
710
  ):
709
711
  """Instantiate an ObjectsTable panel.
@@ -752,6 +754,7 @@ class ObjectsTablePanel(Panel):
752
754
  footer_buttons (list, optional): A list of Button or FormButton components to render in the panel footer.
753
755
  These buttons typically perform actions like bulk delete, edit, or custom form submission.
754
756
  form_id (str, optional): A unique ID for this table's form; used to set the `data-form-id` attribute on each `FormButton`.
757
+ include_paginator (bool, optional): If True, renders a paginator in the panel footer.
755
758
  """
756
759
  if context_table_key and any(
757
760
  [
@@ -796,6 +799,7 @@ class ObjectsTablePanel(Panel):
796
799
  self.tab_id = tab_id
797
800
  self.footer_buttons = footer_buttons
798
801
  self.form_id = form_id
802
+ self.include_paginator = include_paginator
799
803
 
800
804
  super().__init__(
801
805
  body_wrapper_template_path=body_wrapper_template_path,
@@ -953,6 +957,7 @@ class ObjectsTablePanel(Panel):
953
957
  "footer_buttons": self.footer_buttons,
954
958
  "form_id": self.form_id,
955
959
  "more_queryset_count": more_queryset_count,
960
+ "include_paginator": self.include_paginator,
956
961
  "show_table_config_button": self.show_table_config_button,
957
962
  }
958
963
 
@@ -967,6 +972,7 @@ class KeyValueTablePanel(Panel):
967
972
  context_data_key=None,
968
973
  hide_if_unset=(),
969
974
  value_transforms=None,
975
+ key_transforms=None,
970
976
  body_wrapper_template_path="components/panel/body_wrapper_key_value_table.html",
971
977
  **kwargs,
972
978
  ):
@@ -985,6 +991,8 @@ class KeyValueTablePanel(Panel):
985
991
 
986
992
  - `[render_markdown, placeholder]` - render the given text as Markdown, or render a placeholder if blank
987
993
  - `[humanize_speed, placeholder]` - convert the given kbps value to Mbps or Gbps for display
994
+ key_transforms (dict, optional): A mapping of original field names to custom display names to be used when rendering keys
995
+ For example: {'content_types': 'Content Type'}.
988
996
  """
989
997
  if data and context_data_key:
990
998
  raise ValueError("The data and context_data_key parameters are mutually exclusive")
@@ -992,6 +1000,7 @@ class KeyValueTablePanel(Panel):
992
1000
  self.context_data_key = context_data_key or "data"
993
1001
  self.hide_if_unset = hide_if_unset
994
1002
  self.value_transforms = value_transforms or {}
1003
+ self.key_transforms = key_transforms or {}
995
1004
  super().__init__(body_wrapper_template_path=body_wrapper_template_path, **kwargs)
996
1005
 
997
1006
  def should_render(self, context: Context):
@@ -1172,6 +1181,7 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1172
1181
  self,
1173
1182
  *,
1174
1183
  fields="__all__",
1184
+ additional_fields=(),
1175
1185
  exclude_fields=(),
1176
1186
  context_object_key=None,
1177
1187
  ignore_nonexistent_fields=False,
@@ -1185,6 +1195,11 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1185
1195
  fields (str, list): The ordered list of fields to display, or `"__all__"` to display fields automatically.
1186
1196
  Note that ManyToMany fields and reverse relations are **not** included in `"__all__"` at this time, nor
1187
1197
  are any hidden fields, nor the specially handled `id`, `created`, `last_updated` fields on most models.
1198
+ When a list is specified, it may include model `@property` attributes and nested lookups (if desired) in
1199
+ addition to concrete model fields; when using `fields="__all__"`, such additional attributes and lookups
1200
+ may be specified with the `additional_fields` parameter.
1201
+ additional_fields (list): Only relevant if `fields == "__all__"`, in which case it can specify additional
1202
+ non-default fields to include such as reverse relations, `@property` attributes, nested lookups, etc.
1188
1203
  exclude_fields (list): Only relevant if `fields == "__all__"`, in which case it excludes the given fields.
1189
1204
  context_object_key (str): The key in the render context that will contain the object to derive fields from.
1190
1205
  ignore_nonexistent_fields (bool): If True, `fields` is permitted to include field names that don't actually
@@ -1193,6 +1208,11 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1193
1208
  (see `render_label()`).
1194
1209
  """
1195
1210
  self.fields = fields
1211
+ if additional_fields and fields != "__all__":
1212
+ raise ValueError("additional_fields may only be used in combination with fields='__all__'")
1213
+ self.additional_fields = additional_fields
1214
+ if exclude_fields and fields != "__all__":
1215
+ raise ValueError("exclude_fields may only be used in combination with fields='__all__'")
1196
1216
  self.exclude_fields = exclude_fields
1197
1217
  self.context_object_key = context_object_key
1198
1218
  self.ignore_nonexistent_fields = ignore_nonexistent_fields
@@ -1206,6 +1226,7 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1206
1226
 
1207
1227
  def render_value(self, key, value, context: Context):
1208
1228
  obj = get_obj_from_context(context, self.context_object_key)
1229
+ # TODO: handle nested keys, e.g. device_type__device_family
1209
1230
  try:
1210
1231
  field_instance = obj._meta.get_field(key)
1211
1232
  except FieldDoesNotExist:
@@ -1226,8 +1247,18 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1226
1247
  if isinstance(field_instance, JSONField):
1227
1248
  return format_html("<pre>{}</pre>", render_json(value))
1228
1249
 
1229
- if isinstance(field_instance, ManyToManyField) and field_instance.related_model == ContentType:
1230
- return render_content_types(value)
1250
+ if isinstance(field_instance, ManyToManyField):
1251
+ if field_instance.related_model == ContentType:
1252
+ return render_content_types(value)
1253
+ # TODO: this would be nice but it's probably too error-prone in general:
1254
+ # return render_m2m(
1255
+ # value.all(),
1256
+ # (
1257
+ # reverse(get_route_for_model(field_instance.related_model, "list")) + "?" +
1258
+ # obj._meta.verbose_name_plural.lower().replace(" ", "_") + "=" + str(obj.pk)
1259
+ # ),
1260
+ # key,
1261
+ # )
1231
1262
 
1232
1263
  if isinstance(field_instance, CharField) and hasattr(obj, f"get_{key}_display"):
1233
1264
  # For example, Secret.provider -> Secret.get_provider_display()
@@ -1269,6 +1300,8 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1269
1300
  # TODO: apply a default ordering "smarter" than declaration order? Alphabetical? By field type?
1270
1301
  # TODO: allow model to specify an alternative field ordering?
1271
1302
 
1303
+ fields += self.additional_fields
1304
+
1272
1305
  data = {}
1273
1306
 
1274
1307
  if isinstance(instance, TreeModel) and (self.fields == "__all__" or "_hierarchy" in self.fields):
@@ -1279,9 +1312,14 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1279
1312
  if field_name in self.exclude_fields:
1280
1313
  continue
1281
1314
  try:
1282
- field_value = getattr(instance, field_name)
1283
- except ObjectDoesNotExist:
1284
- field_value = None
1315
+ field_value = instance
1316
+ # Handle nested lookups, e.g. device_type__device_family
1317
+ for token in field_name.split(LOOKUP_SEP):
1318
+ try:
1319
+ field_value = getattr(field_value, token)
1320
+ except ObjectDoesNotExist:
1321
+ field_value = None
1322
+ break
1285
1323
  except AttributeError:
1286
1324
  if self.ignore_nonexistent_fields:
1287
1325
  continue
@@ -1297,6 +1335,10 @@ class ObjectFieldsPanel(KeyValueTablePanel):
1297
1335
 
1298
1336
  def render_key(self, key, value, context: Context):
1299
1337
  """Render the `verbose_name` of the model field whose name corresponds to the given key, if applicable."""
1338
+
1339
+ if key in self.key_transforms:
1340
+ return self.key_transforms[key]
1341
+
1300
1342
  instance = get_obj_from_context(context, self.context_object_key)
1301
1343
 
1302
1344
  if instance is not None:
@@ -1912,7 +1954,8 @@ class _ObjectDetailContactsTab(Tab):
1912
1954
  "{} {}",
1913
1955
  self.label,
1914
1956
  render_to_string(
1915
- "utilities/templatetags/badge.html", badge(get_obj_from_context(context).associated_contacts.count())
1957
+ "utilities/templatetags/badge.html",
1958
+ badge(get_obj_from_context(context).associated_contacts.count(), True),
1916
1959
  ),
1917
1960
  )
1918
1961
 
@@ -1956,7 +1999,7 @@ class _ObjectDetailGroupsTab(Tab):
1956
1999
  "{} {}",
1957
2000
  self.label,
1958
2001
  render_to_string(
1959
- "utilities/templatetags/badge.html", badge(get_obj_from_context(context).dynamic_groups.count())
2002
+ "utilities/templatetags/badge.html", badge(get_obj_from_context(context).dynamic_groups.count(), True)
1960
2003
  ),
1961
2004
  )
1962
2005
 
@@ -2003,6 +2046,6 @@ class _ObjectDetailMetadataTab(Tab):
2003
2046
  self.label,
2004
2047
  render_to_string(
2005
2048
  "utilities/templatetags/badge.html",
2006
- badge(get_obj_from_context(context).associated_object_metadata.count()),
2049
+ badge(get_obj_from_context(context).associated_object_metadata.count(), True),
2007
2050
  ),
2008
2051
  )
@@ -1,9 +1,10 @@
1
- from typing import Literal, Optional
1
+ from typing import Literal, Optional, Union
2
2
 
3
3
  from django.template import Context, Template
4
4
  from django.utils.html import strip_tags
5
5
 
6
6
  DEFAULT_TITLES: dict[str, str] = {
7
+ "*": "{{ verbose_name_plural|bettertitle }}",
7
8
  "list": "{{ verbose_name_plural|bettertitle }}",
8
9
  "detail": "{{ object.display|default:object }}",
9
10
  "retrieve": "{{ object.display|default:object }}",
@@ -59,7 +60,7 @@ class Titles:
59
60
  if template_plugins:
60
61
  self.template_plugins.extend(template_plugins)
61
62
 
62
- def render(self, context: Context, mode: ModeType = "html") -> str:
63
+ def render(self, context: Union[dict, Context], mode: ModeType = "html") -> str:
63
64
  """
64
65
  Renders the title based on given context and current action.
65
66
 
@@ -68,12 +69,15 @@ class Titles:
68
69
  Make sure that needed context variables are in context and needed plugins are loaded.
69
70
 
70
71
  Args:
71
- context (Context): Render context.
72
+ context (Union[dict, Context]): Render context.
72
73
  mode (ModeType): Rendering mode: "html" or "plain".
73
74
 
74
75
  Returns:
75
76
  (str): HTML fragment or plain text, depending on `mode`.
76
77
  """
78
+ if isinstance(context, dict):
79
+ context = Context(context)
80
+
77
81
  with context.update(self.get_extra_context(context)):
78
82
  template_str = self.get_template_str(context)
79
83
  template = Template(self.template_plugins_str + template_str)
@@ -92,7 +96,7 @@ class Titles:
92
96
  Returns:
93
97
  str: The template string for the current action, or an empty string if not found.
94
98
  """
95
- action = context.get("view_action", "list")
99
+ action = context.get("view_action", "")
96
100
 
97
101
  template_str = self.titles.get(action)
98
102
  if template_str:
@@ -102,7 +106,7 @@ class Titles:
102
106
  if detail:
103
107
  return self.titles.get("detail", "")
104
108
 
105
- return ""
109
+ return self.titles.get("*", "")
106
110
 
107
111
  @property
108
112
  def template_plugins_str(self) -> str:
@@ -92,6 +92,19 @@ def merge_dicts_without_collision(d1, d2):
92
92
  return {**d1, **d2}
93
93
 
94
94
 
95
+ def validate_jinja2(template_code):
96
+ """
97
+ Parse a Jinja2 template to validate its syntax. Returns True if the template is valid.
98
+
99
+ Raises:
100
+ jinja2.TemplateSyntaxError: If the template is syntactically invalid.
101
+ """
102
+ rendering_engine = engines["jinja"]
103
+ rendering_engine.env.parse(template_code)
104
+
105
+ return True
106
+
107
+
95
108
  def render_jinja2(template_code, context):
96
109
  """
97
110
  Render a Jinja2 template with the provided context. Return the rendered content.
@@ -1,5 +1,6 @@
1
1
  """Utilities for handling deprecation of code and features."""
2
2
 
3
+ from functools import wraps
3
4
  import logging
4
5
  import traceback
5
6
  import warnings
@@ -51,6 +52,7 @@ def method_deprecated(message):
51
52
  """Decorator to mark a method as deprecated with a custom message about what to call instead."""
52
53
 
53
54
  def decorate(method):
55
+ @wraps(method)
54
56
  def decorated_method(*args, **kwargs):
55
57
  warnings.warn(
56
58
  f"Method `{method.__name__}` is deprecated, and will be removed in a future Nautobot release. "
@@ -15,6 +15,7 @@ from django.contrib import messages
15
15
  from django.contrib.auth.decorators import permission_required
16
16
  from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
17
17
  from django.contrib.contenttypes.models import ContentType
18
+ from django.core.cache import cache
18
19
  from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
19
20
  from django.shortcuts import get_object_or_404, render
20
21
  from django.template import loader, RequestContext, Template
@@ -51,6 +52,11 @@ from nautobot.core.releases import get_latest_release
51
52
  from nautobot.core.utils.config import get_settings_or_config
52
53
  from nautobot.core.utils.lookup import get_route_for_model
53
54
  from nautobot.core.utils.permissions import get_permission_for_model
55
+ from nautobot.core.views.utils import (
56
+ generate_latest_with_cache,
57
+ is_metrics_experimental_caching_enabled,
58
+ METRICS_CACHE_KEY,
59
+ )
54
60
  from nautobot.extras.forms import GraphQLQueryForm
55
61
  from nautobot.extras.models import FileProxy, GraphQLQuery, Status
56
62
  from nautobot.extras.registry import registry
@@ -435,8 +441,17 @@ class NautobotAppMetricsCollector(Collector):
435
441
  def collect(self):
436
442
  """Collect metrics from plugins."""
437
443
  start = time.time()
438
- for metric_generator in registry["app_metrics"]:
439
- yield from metric_generator()
444
+ cached_lines = cache.get(METRICS_CACHE_KEY)
445
+ if not is_metrics_experimental_caching_enabled() or not cached_lines:
446
+ # If caching is disabled or no cache is found, generate metrics
447
+ for metric_generator in registry["app_metrics"]:
448
+ yield from metric_generator()
449
+ else:
450
+ # We stash the cached lines on the instance of the collector so that we can
451
+ # avoid a potential race condition where the cache expires between
452
+ # the time we check for it and the time we go to use it
453
+ # in generate_latest_with_cache()
454
+ self.local_cache = cached_lines
440
455
  gauge = GaugeMetricFamily("nautobot_app_metrics_processing_ms", "Time in ms to generate the app metrics")
441
456
  duration = time.time() - start
442
457
  gauge.add_metric([], format(duration * 1000, ".5f"))
@@ -485,7 +500,13 @@ class NautobotMetricsView(APIView):
485
500
  except ValueError:
486
501
  # Collector already registered, we are running without multiprocessing
487
502
  pass
488
- metrics_page = generate_latest(prometheus_registry)
503
+
504
+ if is_metrics_experimental_caching_enabled():
505
+ # Use the vendored version of generate_latest with Caching support
506
+ metrics_page = generate_latest_with_cache(prometheus_registry)
507
+ else:
508
+ # Use the original version of generate_latest to generate the metrics
509
+ metrics_page = generate_latest(prometheus_registry)
489
510
  return Response(metrics_page, content_type=CONTENT_TYPE_LATEST)
490
511
 
491
512