nautobot 2.2.8__py3-none-any.whl → 2.2.9__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 (299) hide show
  1. nautobot/core/filters.py +15 -1
  2. nautobot/core/graphql/generators.py +2 -2
  3. nautobot/core/graphql/schema.py +2 -3
  4. nautobot/core/jobs/__init__.py +4 -1
  5. nautobot/core/settings.py +13 -2
  6. nautobot/core/settings.yaml +14 -0
  7. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  8. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  9. nautobot/core/tests/test_jobs.py +82 -2
  10. nautobot/core/tests/test_templatetags_netutils.py +3 -3
  11. nautobot/dcim/models/device_components.py +7 -0
  12. nautobot/dcim/models/devices.py +4 -0
  13. nautobot/dcim/tables/devices.py +19 -4
  14. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  15. nautobot/dcim/tests/test_models.py +31 -0
  16. nautobot/dcim/tests/test_views.py +13 -0
  17. nautobot/dcim/views.py +8 -2
  18. nautobot/extras/api/views.py +7 -59
  19. nautobot/extras/homepage.py +12 -2
  20. nautobot/extras/jobs.py +2 -2
  21. nautobot/extras/models/jobs.py +81 -0
  22. nautobot/extras/signals.py +14 -1
  23. nautobot/extras/tables.py +36 -5
  24. nautobot/extras/templates/extras/job_detail.html +11 -0
  25. nautobot/extras/tests/test_views.py +21 -0
  26. nautobot/extras/utils.py +34 -5
  27. nautobot/extras/views.py +20 -46
  28. nautobot/ipam/models.py +9 -12
  29. nautobot/ipam/tests/test_models.py +3 -2
  30. nautobot/ipam/views.py +2 -8
  31. nautobot/project-static/css/base.css +1 -0
  32. nautobot/project-static/docs/404.html +2 -2
  33. nautobot/project-static/docs/apps/index.html +2 -2
  34. nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
  35. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
  36. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
  37. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
  38. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
  39. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
  40. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
  41. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
  42. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
  43. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
  44. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
  45. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
  46. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
  47. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
  48. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
  49. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
  50. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
  51. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
  52. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
  53. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
  54. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
  55. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
  56. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +4 -2
  57. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
  58. nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
  59. nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
  60. nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
  61. nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
  62. nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
  63. nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
  64. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
  65. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
  66. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
  67. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
  68. nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
  69. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
  70. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
  71. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
  72. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
  73. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
  74. nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
  75. nautobot/project-static/docs/development/apps/api/setup.html +2 -2
  76. nautobot/project-static/docs/development/apps/api/testing.html +2 -2
  77. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
  78. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
  79. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
  80. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
  81. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
  82. nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
  83. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
  84. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
  85. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
  86. nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
  87. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
  88. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
  89. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
  90. nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
  91. nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
  92. nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
  93. nautobot/project-static/docs/development/apps/index.html +2 -2
  94. nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
  95. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  96. nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
  97. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
  98. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
  99. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
  100. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
  101. nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
  102. nautobot/project-static/docs/development/core/application-registry.html +2 -2
  103. nautobot/project-static/docs/development/core/best-practices.html +2 -2
  104. nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
  105. nautobot/project-static/docs/development/core/caching.html +2 -2
  106. nautobot/project-static/docs/development/core/controllers.html +2 -2
  107. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
  108. nautobot/project-static/docs/development/core/generic-views.html +2 -2
  109. nautobot/project-static/docs/development/core/getting-started.html +2 -2
  110. nautobot/project-static/docs/development/core/homepage.html +2 -2
  111. nautobot/project-static/docs/development/core/index.html +13 -2
  112. nautobot/project-static/docs/development/core/model-checklist.html +2 -2
  113. nautobot/project-static/docs/development/core/model-features.html +2 -2
  114. nautobot/project-static/docs/development/core/natural-keys.html +2 -2
  115. nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
  116. nautobot/project-static/docs/development/core/release-checklist.html +2 -2
  117. nautobot/project-static/docs/development/core/role-internals.html +2 -2
  118. nautobot/project-static/docs/development/core/settings.html +2 -2
  119. nautobot/project-static/docs/development/core/style-guide.html +2 -2
  120. nautobot/project-static/docs/development/core/templates.html +2 -2
  121. nautobot/project-static/docs/development/core/testing.html +2 -2
  122. nautobot/project-static/docs/development/core/user-preferences.html +2 -2
  123. nautobot/project-static/docs/development/index.html +2 -2
  124. nautobot/project-static/docs/development/jobs/index.html +377 -363
  125. nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
  126. nautobot/project-static/docs/index.html +8228 -13
  127. nautobot/project-static/docs/overview/application_stack.html +2 -2
  128. nautobot/project-static/docs/overview/design_philosophy.html +4 -4
  129. nautobot/project-static/docs/overview/index.html +13 -8228
  130. nautobot/project-static/docs/release-notes/index.html +2 -2
  131. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  132. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  133. nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
  134. nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
  135. nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
  136. nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
  137. nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
  138. nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
  139. nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
  140. nautobot/project-static/docs/release-notes/version-2.2.html +232 -95
  141. nautobot/project-static/docs/search/search_index.json +1 -1
  142. nautobot/project-static/docs/sitemap.xml +260 -260
  143. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  144. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
  145. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
  146. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
  147. nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
  148. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +34 -2
  149. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +2 -2
  150. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
  151. nautobot/project-static/docs/user-guide/administration/guides/caching.html +2 -2
  152. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +6 -2
  153. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +2 -2
  154. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
  155. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
  156. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
  157. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
  158. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  159. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
  160. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
  161. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
  162. nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
  163. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
  164. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
  165. nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
  166. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +2 -2
  167. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +2 -2
  168. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +2 -2
  169. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
  170. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
  171. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +60 -8
  172. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
  173. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
  174. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
  175. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
  176. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
  177. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
  180. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
  181. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
  182. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
  183. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
  184. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
  185. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
  186. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
  224. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
  225. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
  226. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
  227. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
  228. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
  229. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
  230. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
  231. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
  232. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
  233. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
  234. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
  235. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
  236. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
  237. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
  238. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
  239. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
  240. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
  241. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
  242. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
  243. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
  244. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
  245. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
  246. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
  247. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
  248. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
  249. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
  250. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
  251. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
  252. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
  253. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
  254. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
  255. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
  256. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
  257. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
  258. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
  259. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
  260. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
  261. nautobot/project-static/docs/user-guide/index.html +2 -2
  262. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
  263. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
  264. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
  265. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
  266. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
  267. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
  268. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
  269. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
  270. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
  271. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
  272. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
  273. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
  274. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
  275. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
  276. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
  277. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
  278. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
  279. nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
  280. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
  281. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
  282. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
  283. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
  284. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
  285. nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
  286. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
  287. nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
  288. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
  289. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
  290. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
  291. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
  292. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
  293. nautobot/virtualization/tables.py +2 -5
  294. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/METADATA +2 -2
  295. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/RECORD +299 -299
  296. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
  297. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
  298. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
  299. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/entry_points.txt +0 -0
nautobot/core/filters.py CHANGED
@@ -7,9 +7,11 @@ from django import forms as django_forms
7
7
  from django.conf import settings
8
8
  from django.db import models
9
9
  from django.forms.utils import ErrorDict, ErrorList
10
+ from django.utils.encoding import force_str
11
+ from django.utils.text import capfirst
10
12
  import django_filters
11
13
  from django_filters.constants import EMPTY_VALUES
12
- from django_filters.utils import get_model_field, resolve_field
14
+ from django_filters.utils import get_model_field, label_for_filter, resolve_field, verbose_lookup_expr
13
15
  from drf_spectacular.types import OpenApiTypes
14
16
  from drf_spectacular.utils import extend_schema_field
15
17
 
@@ -681,6 +683,18 @@ class BaseFilterSet(django_filters.FilterSet):
681
683
  # Of course setting the negation of the existing filter's exclude attribute handles both cases
682
684
  new_filter.exclude = not filter_field.exclude
683
685
 
686
+ # If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
687
+ # so we have to do it.
688
+ if filter_field.label and filter_field.label != label_for_filter(
689
+ cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
690
+ ):
691
+ # Lightly adjusted from label_for_filter() implementation:
692
+ verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
693
+ if isinstance(lookup_expr, str):
694
+ verbose_expression.append(verbose_lookup_expr(lookup_expr))
695
+ verbose_expression = [force_str(part) for part in verbose_expression if part]
696
+ new_filter.label = capfirst(" ".join(verbose_expression))
697
+
684
698
  magic_filters[new_filter_name] = new_filter
685
699
 
686
700
  return magic_filters
@@ -64,8 +64,8 @@ def generate_filter_resolver(schema_type, resolver_name, field_name):
64
64
  """
65
65
  filterset_class = schema_type._meta.filterset_class
66
66
 
67
- def resolve_filter(self, *args, **kwargs):
68
- if not filterset_class:
67
+ def resolve_filter(self, info, **kwargs):
68
+ if not filterset_class or not kwargs:
69
69
  return getattr(self, field_name).all()
70
70
 
71
71
  # Inverse of substitution logic from get_filtering_args_from_filterset() - transform "_type" back to "type"
@@ -370,10 +370,9 @@ def extend_schema_type_relationships(schema_type, model):
370
370
  """Extend the schema type with attributes and resolvers corresponding
371
371
  to the relationships associated with this model."""
372
372
 
373
- ct = ContentType.objects.get_for_model(model)
374
373
  relationships_by_side = {
375
- "source": Relationship.objects.filter(source_type=ct),
376
- "destination": Relationship.objects.filter(destination_type=ct),
374
+ "source": Relationship.objects.get_for_model_source(model),
375
+ "destination": Relationship.objects.get_for_model_destination(model),
377
376
  }
378
377
 
379
378
  prefix = ""
@@ -1,3 +1,4 @@
1
+ import codecs
1
2
  import contextlib
2
3
  from io import BytesIO
3
4
 
@@ -282,7 +283,9 @@ class ImportObjects(Job):
282
283
  if not csv_data and not csv_file:
283
284
  raise RunJobTaskFailed("Either csv_data or csv_file must be provided")
284
285
  if csv_file:
285
- csv_bytes = csv_file
286
+ # data_encoding is utf-8 and file_encoding is utf-8-sig
287
+ # Bytes read from the original file are decoded according to file_encoding, and the result is encoded using data_encoding.
288
+ csv_bytes = codecs.EncodedFile(csv_file, "utf-8", "utf-8-sig")
286
289
  else:
287
290
  csv_bytes = BytesIO(csv_data.encode("utf-8"))
288
291
 
nautobot/core/settings.py CHANGED
@@ -907,13 +907,24 @@ CELERY_TASK_TRACK_STARTED = True
907
907
  # If enabled, a `task-sent` event will be sent for every task so tasks can be tracked before they're consumed by a worker.
908
908
  CELERY_TASK_SEND_SENT_EVENT = True
909
909
 
910
+ # How many tasks a worker is allowed to reserve for its own consumption and execution.
911
+ # If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
912
+ # For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
913
+ # Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
914
+ # so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
915
+ # until the long-running task completes.
916
+ # https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
917
+ CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
918
+
910
919
  # If enabled stdout and stderr of running jobs will be redirected to the task logger.
911
920
  CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
912
921
 
913
- # The log level of log messages generated by redirected job stdout and stderr. Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
922
+ # The log level of log messages generated by redirected job stdout and stderr.
923
+ # Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
914
924
  CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
915
925
 
916
- # Send task-related events so that tasks can be monitored using tools like flower. Sets the default value for the workers -E argument.
926
+ # Send task-related events so that tasks can be monitored using tools like flower.
927
+ # Sets the default value for the workers -E argument.
917
928
  CELERY_WORKER_SEND_TASK_EVENTS = True
918
929
 
919
930
  # Default celery queue name that will be used by workers and tasks if no queue is specified
@@ -426,6 +426,20 @@ properties:
426
426
  see_also:
427
427
  "`CELERY_TASK_SOFT_TIME_LIMIT`": "#celery_task_soft_time_limit"
428
428
  type: "integer"
429
+ CELERY_WORKER_PREFETCH_MULTIPLIER:
430
+ default: 4
431
+ description: "How many tasks a worker is allowed to reserve for its own consumption and execution."
432
+ details: >-
433
+ If set to `0` (not recommended) a single worker can reserve all tasks even if other workers are free.
434
+ For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
435
+ Conversely, for long-running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to `1`
436
+ so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
437
+ until the long-running task completes.
438
+ environment_variable: "NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER"
439
+ see_also:
440
+ "Celery documentation": "https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits"
441
+ type: "integer"
442
+ version_added: "2.2.9"
429
443
  CELERY_WORKER_PROMETHEUS_PORTS:
430
444
  default: []
431
445
  description: "Ports for Prometheus metric HTTP server running on the celery worker(s)."
@@ -282,6 +282,15 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
282
282
  # CELERY_TASK_SOFT_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_SOFT_TIME_LIMIT", str(5 * 60)))
283
283
  # CELERY_TASK_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_TIME_LIMIT", str(10 * 60)))
284
284
 
285
+ # How many tasks a worker is allowed to reserve for its own consumption and execution.
286
+ # If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
287
+ # For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
288
+ # Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
289
+ # so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
290
+ # until the long-running task completes.
291
+ # https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
292
+ # CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
293
+
285
294
  # Ports for prometheus metric HTTP server running on the celery worker.
286
295
  # Normally this should be set to a single port, unless you have multiple workers running on a single machine, i.e.
287
296
  # sharing the same available ports. In that case you need to specify a range of ports greater than or equal to the
@@ -294,6 +303,12 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
294
303
  # int(value) for value in os.getenv("NAUTOBOT_CELERY_WORKER_PROMETHEUS_PORTS").split(",")
295
304
  # ]
296
305
 
306
+ # If enabled stdout and stderr of running jobs will be redirected to the task logger.
307
+ # CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
308
+
309
+ # The log level of log messages generated by redirected job stdout and stderr.
310
+ # Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
311
+ # CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
297
312
 
298
313
  # Number of days to retain changelog entries. Set to 0 to retain changes indefinitely. Defaults to 90 if not set here.
299
314
  #
@@ -24,7 +24,7 @@ class StaticMediaFailureTestCase(SeleniumTestCase):
24
24
  reverse("graphql"),
25
25
  reverse("api_docs"),
26
26
  "/admin/",
27
- "/static/docs/overview/index.html",
27
+ "/static/docs/index.html",
28
28
  ]
29
29
  for url in test_urls:
30
30
  with self.subTest(test_url=url):
@@ -1,12 +1,22 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from django.contrib.contenttypes.models import ContentType
4
+ from django.core.files.base import ContentFile
4
5
  import yaml
5
6
 
6
7
  from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
7
- from nautobot.dcim.models import DeviceType, Location, LocationType, Manufacturer
8
+ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
8
9
  from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
9
- from nautobot.extras.models import Contact, ContactAssociation, ExportTemplate, JobLogEntry, Role, Status
10
+ from nautobot.extras.models import (
11
+ Contact,
12
+ ContactAssociation,
13
+ ExportTemplate,
14
+ FileProxy,
15
+ JobLogEntry,
16
+ Role,
17
+ Status,
18
+ )
19
+ from nautobot.ipam.models import Prefix
10
20
  from nautobot.users.models import ObjectPermission
11
21
 
12
22
 
@@ -204,6 +214,76 @@ class ImportObjectsTestCase(TransactionTestCase):
204
214
  )
205
215
  self.assertEqual(4, Status.objects.filter(name__startswith="test_status").count())
206
216
 
217
+ def test_csv_import_with_utf_8_with_bom_encoding(self):
218
+ """
219
+ A superuser running the job with a .csv file with utf_8 with bom encoding should successfully create all specified objects.
220
+ Test for bug fix https://github.com/nautobot/nautobot/issues/5812 and https://github.com/nautobot/nautobot/issues/5985
221
+ """
222
+
223
+ status = Status.objects.get(name="Active").pk
224
+ content = f"prefix,status\n192.168.1.1/32,{status}"
225
+ content = content.encode("utf-8-sig")
226
+ filename = "test.csv"
227
+ csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
228
+ job_result = create_job_result_and_run_job(
229
+ "nautobot.core.jobs",
230
+ "ImportObjects",
231
+ content_type=ContentType.objects.get_for_model(Prefix).pk,
232
+ csv_file=csv_file.id,
233
+ )
234
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
235
+ self.assertFalse(
236
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
237
+ )
238
+ self.assertFalse(
239
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
240
+ )
241
+ self.assertEqual(
242
+ 1, Prefix.objects.filter(status=Status.objects.get(name="Active"), prefix="192.168.1.1/32").count()
243
+ )
244
+ mfr = Manufacturer.objects.create(name="Test Cisco Manufacturer")
245
+ device_type = DeviceType.objects.create(
246
+ manufacturer=mfr,
247
+ model="Cisco CSR1000v",
248
+ u_height=0,
249
+ )
250
+ location_type = LocationType.objects.create(name="Test Location Type")
251
+ location_type.content_types.set([ContentType.objects.get_for_model(Device)])
252
+ location = Location.objects.create(
253
+ name="Device Location",
254
+ location_type=location_type,
255
+ status=Status.objects.get_for_model(Location).first(),
256
+ )
257
+ role = Role.objects.create(name="Device Status")
258
+ role.content_types.set([ContentType.objects.get_for_model(Device)])
259
+ content = "\n".join(
260
+ [
261
+ "serial,asset_tag,device_type,location,status,name,role",
262
+ f"1021C4,CA211,{device_type.pk},{location.pk},{status},Test-AC-01,{role}",
263
+ f"1021C5,CA212,{device_type.pk},{location.pk},{status},Test-AC-02,{role}",
264
+ ]
265
+ )
266
+ content = content.encode("utf-8-sig")
267
+ filename = "test.csv"
268
+ csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
269
+ job_result = create_job_result_and_run_job(
270
+ "nautobot.core.jobs",
271
+ "ImportObjects",
272
+ content_type=ContentType.objects.get_for_model(Device).pk,
273
+ csv_file=csv_file.id,
274
+ )
275
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
276
+ self.assertFalse(
277
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
278
+ )
279
+ self.assertFalse(
280
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
281
+ )
282
+ device_1 = Device.objects.get(name="Test-AC-01")
283
+ device_2 = Device.objects.get(name="Test-AC-02")
284
+ self.assertEqual(device_1.serial, "1021C4")
285
+ self.assertEqual(device_2.serial, "1021C5")
286
+
207
287
  def test_csv_import_bad_row(self):
208
288
  """A row of incorrect data should fail validation for that object but import all others successfully if `roll_back_if_error` is False."""
209
289
  csv_data = self.csv_data.split("\n")
@@ -20,15 +20,15 @@ class NautobotTemplateTagsNetutilsTest(TestCase):
20
20
  i = 1
21
21
  for param_name, param in signature.parameters.items():
22
22
  template_string += f" {param_name}="
23
- if param.annotation == str:
23
+ if param.annotation is str:
24
24
  template_string += f'"{i}"'
25
- elif param.annotation == bool:
25
+ elif param.annotation is bool:
26
26
  template_string += "True"
27
27
  elif param.annotation in (int, float):
28
28
  template_string += str(i)
29
29
  elif param.annotation in (list, tuple):
30
30
  template_string += "[]"
31
- elif param.annotation == dict:
31
+ elif param.annotation is dict:
32
32
  template_string += "{}"
33
33
  else:
34
34
  template_string += "None"
@@ -24,6 +24,7 @@ from nautobot.dcim.choices import (
24
24
  PowerOutletFeedLegChoices,
25
25
  PowerOutletTypeChoices,
26
26
  PowerPortTypeChoices,
27
+ SubdeviceRoleChoices,
27
28
  )
28
29
  from nautobot.dcim.constants import (
29
30
  NONCONNECTABLE_IFACE_TYPES,
@@ -1019,6 +1020,12 @@ class DeviceBay(ComponentModel):
1019
1020
  "installed_device": f"Cannot install the specified device; device is already installed in {current_bay}"
1020
1021
  }
1021
1022
  )
1023
+ if self.installed_device.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_CHILD:
1024
+ raise ValidationError(
1025
+ {
1026
+ "installed_device": f'Cannot install device "{self.installed_device}"; device-type "{self.installed_device.device_type}" subdevice_role is not "child".'
1027
+ }
1028
+ )
1022
1029
 
1023
1030
  @property
1024
1031
  def parent(self):
@@ -1065,6 +1065,10 @@ class DeviceRedundancyGroup(PrimaryModel):
1065
1065
  def devices_sorted(self):
1066
1066
  return self.devices.order_by("device_redundancy_group_priority")
1067
1067
 
1068
+ @property
1069
+ def controllers_sorted(self):
1070
+ return self.controllers.order_by("name")
1071
+
1068
1072
  def __str__(self):
1069
1073
  return self.name
1070
1074
 
@@ -938,17 +938,32 @@ class VirtualChassisTable(BaseTable):
938
938
  class DeviceRedundancyGroupTable(BaseTable):
939
939
  pk = ToggleColumn()
940
940
  name = tables.Column(linkify=True)
941
- device_count = tables.TemplateColumn(
942
- template_code=LINKED_RECORD_COUNT,
941
+ device_count = LinkedCountColumn(
942
+ viewname="dcim:device_list",
943
+ url_params={"device_redundancy_group": "pk"},
943
944
  verbose_name="Devices",
944
945
  )
946
+ controller_count = LinkedCountColumn(
947
+ viewname="dcim:controller_list",
948
+ url_params={"controller_device_redundancy_group": "pk"},
949
+ verbose_name="Controllers",
950
+ )
945
951
  secrets_group = tables.Column(linkify=True)
946
952
  tags = TagColumn(url_name="dcim:deviceredundancygroup_list")
947
953
 
948
954
  class Meta(BaseTable.Meta):
949
955
  model = DeviceRedundancyGroup
950
- fields = ("pk", "name", "status", "failover_strategy", "device_count", "secrets_group", "tags")
951
- default_columns = ("pk", "name", "status", "failover_strategy", "device_count")
956
+ fields = (
957
+ "pk",
958
+ "name",
959
+ "status",
960
+ "failover_strategy",
961
+ "controller_count",
962
+ "device_count",
963
+ "secrets_group",
964
+ "tags",
965
+ )
966
+ default_columns = ("pk", "name", "status", "failover_strategy", "controller_count", "device_count")
952
967
 
953
968
 
954
969
  #
@@ -45,6 +45,12 @@
45
45
  {% endblock content_right_page %}
46
46
 
47
47
  {% block content_full_width_page %}
48
+ <div class="panel panel-default">
49
+ <div class="panel-heading">
50
+ <strong>Controllers</strong>
51
+ </div>
52
+ {% include 'responsive_table.html' with table=controllers_table %}
53
+ </div>
48
54
  <div class="panel panel-default">
49
55
  <div class="panel-heading">
50
56
  <strong>Devices</strong>
@@ -1264,6 +1264,37 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
1264
1264
  self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
1265
1265
 
1266
1266
 
1267
+ class DeviceBayTestCase(ModelTestCases.BaseModelTestCase):
1268
+ model = DeviceBay
1269
+
1270
+ def setUp(self):
1271
+ self.devices = Device.objects.filter(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
1272
+ devicetype = DeviceType.objects.create(
1273
+ manufacturer=self.devices[0].device_type.manufacturer,
1274
+ model="TestDeviceType1",
1275
+ u_height=0,
1276
+ subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
1277
+ )
1278
+ child_device = Device.objects.create(
1279
+ device_type=devicetype,
1280
+ role=self.devices[0].role,
1281
+ name="TestDevice1",
1282
+ status=self.devices[0].status,
1283
+ location=self.devices[0].location,
1284
+ )
1285
+ DeviceBay.objects.create(device=self.devices[0], name="Device Bay 1", installed_device=child_device)
1286
+
1287
+ def test_assigning_installed_device(self):
1288
+ server = Device.objects.exclude(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD).last()
1289
+ bay = DeviceBay(device=self.devices[1], name="Device Bay Err", installed_device=server)
1290
+ with self.assertRaises(ValidationError) as err:
1291
+ bay.validated_save()
1292
+ self.assertIn(
1293
+ f'Cannot install device "{server}"; device-type "{server.device_type}" subdevice_role is not "child".',
1294
+ str(err.exception),
1295
+ )
1296
+
1297
+
1267
1298
  class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
1268
1299
  model = DeviceTypeToSoftwareImageFile
1269
1300
 
@@ -1789,6 +1789,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1789
1789
  sorted(interface_ips),
1790
1790
  )
1791
1791
 
1792
+ with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
1793
+ assign_ip_form_data["pk"] = []
1794
+ assign_ip_request = {
1795
+ "path": reverse("ipam:ipaddress_assign")
1796
+ + f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
1797
+ "data": post_data(assign_ip_form_data),
1798
+ }
1799
+ response = self.client.post(**assign_ip_request, follow=True)
1800
+ self.assertHttpStatus(response, 200)
1801
+ self.assertIn(
1802
+ "Please select at least one IP Address from the table.", response.content.decode(response.charset)
1803
+ )
1804
+
1792
1805
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1793
1806
  def test_device_rearports(self):
1794
1807
  device = Device.objects.first()
nautobot/dcim/views.py CHANGED
@@ -3029,8 +3029,11 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
3029
3029
  form_class = forms.DeviceRedundancyGroupForm
3030
3030
  queryset = (
3031
3031
  DeviceRedundancyGroup.objects.select_related("status")
3032
- .prefetch_related("devices")
3033
- .annotate(device_count=count_related(Device, "device_redundancy_group"))
3032
+ .prefetch_related("controllers", "devices")
3033
+ .annotate(
3034
+ device_count=count_related(Device, "device_redundancy_group"),
3035
+ controller_count=count_related(Controller, "controller_device_redundancy_group"),
3036
+ )
3034
3037
  )
3035
3038
  serializer_class = serializers.DeviceRedundancyGroupSerializer
3036
3039
  table_class = tables.DeviceRedundancyGroupTable
@@ -3043,6 +3046,9 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
3043
3046
  devices_table = tables.DeviceTable(devices)
3044
3047
  devices_table.columns.show("device_redundancy_group_priority")
3045
3048
  context["devices_table"] = devices_table
3049
+ controllers = instance.controllers_sorted.restrict(request.user)
3050
+ controllers_table = tables.ControllerTable(controllers)
3051
+ context["controllers_table"] = controllers_table
3046
3052
  return context
3047
3053
 
3048
3054
 
@@ -1,5 +1,3 @@
1
- from datetime import timedelta
2
-
3
1
  from django.conf import settings
4
2
  from django.contrib.contenttypes.models import ContentType
5
3
  from django.forms import ValidationError as FormsValidationError
@@ -449,59 +447,6 @@ class ImageAttachmentViewSet(ModelViewSet):
449
447
  #
450
448
 
451
449
 
452
- def _create_schedule(serializer, data, job_model, user, approval_required, task_queue=None):
453
- """
454
- This is an internal function to create a scheduled job from API data.
455
- It has to handle both once-offs (i.e. of type TYPE_FUTURE) and interval
456
- jobs.
457
- """
458
- type_ = serializer["interval"]
459
- if type_ == JobExecutionType.TYPE_IMMEDIATELY:
460
- time = timezone.now()
461
- name = serializer.get("name") or f"{job_model.name} - {time}"
462
- elif type_ == JobExecutionType.TYPE_CUSTOM:
463
- time = serializer.get("start_time") # doing .get("key", "default") returns None instead of "default"
464
- if time is None:
465
- # "start_time" is checked against models.ScheduledJob.earliest_possible_time()
466
- # which returns timezone.now() + timedelta(seconds=15)
467
- time = timezone.now() + timedelta(seconds=20)
468
- name = serializer["name"]
469
- else:
470
- time = serializer["start_time"]
471
- name = serializer["name"]
472
- crontab = serializer.get("crontab", "")
473
-
474
- celery_kwargs = {
475
- "nautobot_job_profile": False,
476
- "queue": task_queue,
477
- }
478
-
479
- # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
480
- #
481
- # We pass in task and job_model here partly for forward/backward compatibility logic, and
482
- # part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
483
- # FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
484
- # null) you still have a bit of context on the ScheduledJob as to what it was originally
485
- # scheduled for.
486
- scheduled_job = ScheduledJob(
487
- name=name,
488
- task=job_model.class_path,
489
- job_model=job_model,
490
- start_time=time,
491
- description=f"Nautobot job {name} scheduled by {user} for {time}",
492
- kwargs=data,
493
- celery_kwargs=celery_kwargs,
494
- interval=type_,
495
- one_off=(type_ == JobExecutionType.TYPE_FUTURE),
496
- user=user,
497
- approval_required=approval_required,
498
- crontab=crontab,
499
- queue=task_queue,
500
- )
501
- scheduled_job.validated_save()
502
- return scheduled_job
503
-
504
-
505
450
  class JobViewSetBase(
506
451
  NautobotAPIVersionMixin,
507
452
  # note no CreateModelMixin
@@ -701,13 +646,16 @@ class JobViewSetBase(
701
646
 
702
647
  # Try to create a ScheduledJob, or...
703
648
  if schedule_data:
704
- schedule = _create_schedule(
705
- schedule_data,
706
- job_class.serialize_data(cleaned_data),
649
+ schedule = ScheduledJob.create_schedule(
707
650
  job_model,
708
651
  request.user,
709
- approval_required,
652
+ name=schedule_data.get("name"),
653
+ start_time=schedule_data.get("start_time"),
654
+ interval=schedule_data.get("interval"),
655
+ crontab=schedule_data.get("crontab", ""),
656
+ approval_required=approval_required,
710
657
  task_queue=input_serializer.validated_data.get("task_queue", None),
658
+ **job_class.serialize_data(cleaned_data),
711
659
  )
712
660
  else:
713
661
  schedule = None
@@ -7,14 +7,24 @@ def get_job_results(request):
7
7
  """Callback function to collect job history for panel."""
8
8
  return (
9
9
  JobResult.objects.filter(status__in=JobResultStatusChoices.READY_STATES)
10
- .defer("result")
10
+ .restrict(request.user, "view")
11
+ .only("id", "name", "status", "date_done", "user")
11
12
  .order_by("-date_done")[:10]
12
13
  )
13
14
 
14
15
 
15
16
  def get_changelog(request):
16
17
  """Callback function to collect changelog for panel."""
17
- return ObjectChange.objects.restrict(request.user, "view")[:15]
18
+ return ObjectChange.objects.restrict(request.user, "view").only(
19
+ "id",
20
+ "action",
21
+ "changed_object",
22
+ "changed_object_id",
23
+ "changed_object_type",
24
+ "object_repr",
25
+ "user_name",
26
+ "time",
27
+ )[:15]
18
28
 
19
29
 
20
30
  layout = (
nautobot/extras/jobs.py CHANGED
@@ -20,7 +20,7 @@ from django.conf import settings
20
20
  from django.contrib.auth import get_user_model
21
21
  from django.core.exceptions import ObjectDoesNotExist
22
22
  from django.core.files.base import ContentFile
23
- from django.core.files.uploadedfile import InMemoryUploadedFile
23
+ from django.core.files.uploadedfile import UploadedFile
24
24
  from django.core.validators import RegexValidator
25
25
  from django.db.models import Model
26
26
  from django.db.models.query import QuerySet
@@ -539,7 +539,7 @@ class BaseJob:
539
539
  elif isinstance(value, Model):
540
540
  return_data[field_name] = value.pk
541
541
  # FileVar (Save each FileVar as a FileProxy)
542
- elif isinstance(value, InMemoryUploadedFile):
542
+ elif isinstance(value, UploadedFile):
543
543
  return_data[field_name] = BaseJob._save_file_to_proxy(value)
544
544
  # IPAddressVar, IPAddressWithMaskVar, IPNetworkVar
545
545
  elif isinstance(value, netaddr.ip.BaseIP):