nautobot 2.3.1__py3-none-any.whl → 2.3.3__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.
Files changed (339) hide show
  1. nautobot/core/celery/schedulers.py +18 -0
  2. nautobot/core/settings.yaml +3 -3
  3. nautobot/core/tables.py +1 -1
  4. nautobot/core/templates/home.html +4 -3
  5. nautobot/core/templatetags/buttons.py +1 -1
  6. nautobot/core/tests/runner.py +27 -9
  7. nautobot/core/tests/test_utils.py +13 -0
  8. nautobot/core/utils/lookup.py +7 -1
  9. nautobot/core/views/utils.py +3 -3
  10. nautobot/dcim/factory.py +3 -3
  11. nautobot/dcim/tables/devices.py +7 -7
  12. nautobot/dcim/templates/dcim/device.html +12 -0
  13. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +12 -0
  14. nautobot/dcim/utils.py +9 -6
  15. nautobot/extras/api/serializers.py +2 -0
  16. nautobot/extras/context_managers.py +11 -4
  17. nautobot/extras/filters/__init__.py +14 -2
  18. nautobot/extras/forms/forms.py +6 -0
  19. nautobot/extras/forms/mixins.py +2 -2
  20. nautobot/extras/jobs.py +0 -1
  21. nautobot/extras/management/__init__.py +3 -0
  22. nautobot/extras/migrations/0115_scheduledjob_time_zone.py +23 -0
  23. nautobot/extras/models/groups.py +4 -1
  24. nautobot/extras/models/jobs.py +24 -11
  25. nautobot/extras/tables.py +34 -4
  26. nautobot/extras/templates/extras/scheduledjob.html +13 -2
  27. nautobot/extras/tests/test_api.py +17 -18
  28. nautobot/extras/tests/test_context_managers.py +33 -14
  29. nautobot/extras/tests/test_dynamicgroups.py +11 -0
  30. nautobot/extras/tests/test_filters.py +57 -1
  31. nautobot/extras/tests/test_models.py +304 -1
  32. nautobot/extras/tests/test_views.py +4 -2
  33. nautobot/extras/views.py +7 -0
  34. nautobot/ipam/api/views.py +9 -2
  35. nautobot/ipam/choices.py +17 -0
  36. nautobot/ipam/factory.py +6 -0
  37. nautobot/ipam/filters.py +1 -1
  38. nautobot/ipam/forms.py +5 -3
  39. nautobot/ipam/migrations/0048_vrf_status.py +23 -0
  40. nautobot/ipam/migrations/0049_vrf_data_migration.py +25 -0
  41. nautobot/ipam/models.py +6 -0
  42. nautobot/ipam/tables.py +3 -2
  43. nautobot/ipam/templates/ipam/vrf.html +4 -0
  44. nautobot/ipam/templates/ipam/vrf_edit.html +1 -0
  45. nautobot/ipam/tests/test_api.py +44 -3
  46. nautobot/ipam/tests/test_views.py +3 -0
  47. nautobot/project-static/css/base.css +6 -0
  48. nautobot/project-static/docs/404.html +23 -23
  49. nautobot/project-static/docs/apps/index.html +25 -25
  50. nautobot/project-static/docs/apps/nautobot-apps.html +24 -24
  51. nautobot/project-static/docs/assets/javascripts/bundle.56dfad97.min.js +16 -0
  52. nautobot/project-static/docs/assets/javascripts/bundle.56dfad97.min.js.map +7 -0
  53. nautobot/project-static/docs/assets/javascripts/workers/{search.b8dbb3d2.min.js → search.07f07601.min.js} +1 -1
  54. nautobot/project-static/docs/assets/javascripts/workers/{search.b8dbb3d2.min.js.map → search.07f07601.min.js.map} +1 -1
  55. nautobot/project-static/docs/assets/stylesheets/main.35f28582.min.css +1 -0
  56. nautobot/project-static/docs/assets/stylesheets/main.35f28582.min.css.map +1 -0
  57. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +26 -26
  58. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +26 -26
  59. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +58 -58
  60. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +32 -31
  61. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +31 -31
  62. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +25 -25
  63. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +25 -25
  64. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +30 -30
  65. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +34 -34
  66. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +41 -41
  67. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +48 -48
  68. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +92 -92
  69. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +41 -41
  70. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +85 -85
  71. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +84 -84
  72. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +26 -26
  73. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +28 -28
  74. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +40 -40
  75. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +78 -78
  76. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +77 -77
  77. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +26 -26
  78. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +80 -80
  79. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +67 -67
  80. nautobot/project-static/docs/development/apps/api/configuration-view.html +25 -25
  81. nautobot/project-static/docs/development/apps/api/database-backend-config.html +25 -25
  82. nautobot/project-static/docs/development/apps/api/models/django-admin.html +25 -25
  83. nautobot/project-static/docs/development/apps/api/models/global-search.html +25 -25
  84. nautobot/project-static/docs/development/apps/api/models/graphql.html +25 -25
  85. nautobot/project-static/docs/development/apps/api/models/index.html +25 -25
  86. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +25 -25
  87. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +25 -25
  88. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +25 -25
  89. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +25 -25
  90. nautobot/project-static/docs/development/apps/api/platform-features/index.html +25 -25
  91. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +25 -25
  92. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +25 -25
  93. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +25 -25
  94. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +25 -25
  95. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +25 -25
  96. nautobot/project-static/docs/development/apps/api/prometheus.html +25 -25
  97. nautobot/project-static/docs/development/apps/api/setup.html +25 -25
  98. nautobot/project-static/docs/development/apps/api/testing.html +25 -25
  99. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +25 -25
  100. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +25 -25
  101. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +25 -25
  102. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +25 -25
  103. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +25 -25
  104. nautobot/project-static/docs/development/apps/api/views/base-template.html +25 -25
  105. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +25 -25
  106. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +25 -25
  107. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +25 -25
  108. nautobot/project-static/docs/development/apps/api/views/index.html +25 -25
  109. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +25 -25
  110. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +25 -25
  111. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +25 -25
  112. nautobot/project-static/docs/development/apps/api/views/notes.html +25 -25
  113. nautobot/project-static/docs/development/apps/api/views/rest-api.html +25 -25
  114. nautobot/project-static/docs/development/apps/api/views/urls.html +25 -25
  115. nautobot/project-static/docs/development/apps/index.html +25 -25
  116. nautobot/project-static/docs/development/apps/migration/code-updates.html +25 -25
  117. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +25 -25
  118. nautobot/project-static/docs/development/apps/migration/from-v1.html +25 -25
  119. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +25 -25
  120. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +25 -25
  121. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +25 -25
  122. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +25 -25
  123. nautobot/project-static/docs/development/apps/porting-from-netbox.html +25 -25
  124. nautobot/project-static/docs/development/core/application-registry.html +25 -25
  125. nautobot/project-static/docs/development/core/best-practices.html +25 -25
  126. nautobot/project-static/docs/development/core/bootstrap-ui.html +25 -25
  127. nautobot/project-static/docs/development/core/caching.html +25 -25
  128. nautobot/project-static/docs/development/core/controllers.html +25 -25
  129. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +25 -25
  130. nautobot/project-static/docs/development/core/generic-views.html +25 -25
  131. nautobot/project-static/docs/development/core/getting-started.html +25 -25
  132. nautobot/project-static/docs/development/core/homepage.html +25 -25
  133. nautobot/project-static/docs/development/core/index.html +25 -25
  134. nautobot/project-static/docs/development/core/model-checklist.html +25 -25
  135. nautobot/project-static/docs/development/core/model-features.html +25 -25
  136. nautobot/project-static/docs/development/core/natural-keys.html +25 -25
  137. nautobot/project-static/docs/development/core/navigation-menu.html +25 -25
  138. nautobot/project-static/docs/development/core/release-checklist.html +25 -25
  139. nautobot/project-static/docs/development/core/role-internals.html +25 -25
  140. nautobot/project-static/docs/development/core/settings.html +25 -25
  141. nautobot/project-static/docs/development/core/style-guide.html +25 -25
  142. nautobot/project-static/docs/development/core/templates.html +25 -25
  143. nautobot/project-static/docs/development/core/testing.html +25 -25
  144. nautobot/project-static/docs/development/core/user-preferences.html +25 -25
  145. nautobot/project-static/docs/development/index.html +25 -25
  146. nautobot/project-static/docs/development/jobs/index.html +25 -25
  147. nautobot/project-static/docs/development/jobs/migration/from-v1.html +25 -25
  148. nautobot/project-static/docs/index.html +30 -30
  149. nautobot/project-static/docs/overview/application_stack.html +31 -31
  150. nautobot/project-static/docs/overview/design_philosophy.html +25 -25
  151. nautobot/project-static/docs/release-notes/index.html +25 -25
  152. nautobot/project-static/docs/release-notes/version-1.0.html +25 -25
  153. nautobot/project-static/docs/release-notes/version-1.1.html +25 -25
  154. nautobot/project-static/docs/release-notes/version-1.2.html +25 -25
  155. nautobot/project-static/docs/release-notes/version-1.3.html +25 -25
  156. nautobot/project-static/docs/release-notes/version-1.4.html +25 -25
  157. nautobot/project-static/docs/release-notes/version-1.5.html +25 -25
  158. nautobot/project-static/docs/release-notes/version-1.6.html +25 -25
  159. nautobot/project-static/docs/release-notes/version-2.0.html +25 -25
  160. nautobot/project-static/docs/release-notes/version-2.1.html +25 -25
  161. nautobot/project-static/docs/release-notes/version-2.2.html +25 -25
  162. nautobot/project-static/docs/release-notes/version-2.3.html +318 -61
  163. nautobot/project-static/docs/search/search_index.json +1 -1
  164. nautobot/project-static/docs/sitemap.xml +271 -542
  165. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  166. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +25 -25
  167. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +25 -25
  168. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +25 -25
  169. nautobot/project-static/docs/user-guide/administration/configuration/index.html +25 -25
  170. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +28 -28
  171. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +25 -25
  172. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +25 -25
  173. nautobot/project-static/docs/user-guide/administration/guides/caching.html +25 -25
  174. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +25 -25
  175. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +25 -25
  176. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +25 -25
  177. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +25 -25
  178. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +25 -25
  179. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +25 -25
  180. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +25 -25
  181. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +25 -25
  182. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +25 -25
  183. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +25 -25
  184. nautobot/project-static/docs/user-guide/administration/installation/index.html +25 -25
  185. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +25 -25
  186. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +25 -25
  187. nautobot/project-static/docs/user-guide/administration/installation/services.html +25 -25
  188. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +25 -25
  189. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +25 -25
  190. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +25 -25
  191. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +25 -25
  192. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +25 -25
  193. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +25 -25
  194. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +25 -25
  195. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +25 -25
  196. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +25 -25
  197. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +25 -25
  198. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +25 -25
  199. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +25 -25
  200. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +25 -25
  201. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +25 -25
  202. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +25 -25
  203. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +25 -25
  204. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +25 -25
  205. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +25 -25
  206. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +25 -25
  207. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +25 -25
  208. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +25 -25
  209. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +25 -25
  210. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +25 -25
  211. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +25 -25
  212. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +25 -25
  213. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +25 -25
  214. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +25 -25
  215. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +25 -25
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +25 -25
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +25 -25
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +25 -25
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +25 -25
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +25 -25
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +25 -25
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +25 -25
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +25 -25
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +25 -25
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +25 -25
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +25 -25
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +25 -25
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +25 -25
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +25 -25
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +25 -25
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +25 -25
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +25 -25
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +25 -25
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +25 -25
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +25 -25
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +25 -25
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +25 -25
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +25 -25
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +25 -25
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +25 -25
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +25 -25
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +25 -25
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +25 -25
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +25 -25
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +25 -25
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +25 -25
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +25 -25
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +25 -25
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +25 -25
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +25 -25
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +25 -25
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +25 -25
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +25 -25
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +25 -25
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +25 -25
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +25 -25
  257. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +25 -25
  258. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +25 -25
  259. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +25 -25
  260. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +25 -25
  261. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +25 -25
  262. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +25 -25
  263. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +25 -25
  264. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +25 -25
  265. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +25 -25
  266. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +25 -25
  267. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +25 -25
  268. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +25 -25
  269. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +25 -25
  270. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +25 -25
  271. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +25 -25
  272. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +25 -25
  273. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +25 -25
  274. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +25 -25
  275. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +25 -25
  276. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +25 -25
  277. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +25 -25
  278. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +25 -25
  279. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +25 -25
  280. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +25 -25
  281. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +25 -25
  282. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +25 -25
  283. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +25 -25
  284. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +25 -25
  285. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +25 -25
  286. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +25 -25
  287. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +25 -25
  288. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +25 -25
  289. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +25 -25
  290. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +25 -25
  291. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +25 -25
  292. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +25 -25
  293. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +25 -25
  294. nautobot/project-static/docs/user-guide/index.html +25 -25
  295. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +25 -25
  296. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +25 -25
  297. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +25 -25
  298. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +25 -25
  299. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +25 -25
  300. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +25 -25
  301. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +25 -25
  302. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +25 -25
  303. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +25 -25
  304. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +25 -25
  305. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +25 -25
  306. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +25 -25
  307. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +25 -25
  308. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +25 -25
  309. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +25 -25
  310. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +25 -25
  311. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +25 -25
  312. nautobot/project-static/docs/user-guide/platform-functionality/note.html +25 -25
  313. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +25 -25
  314. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +25 -25
  315. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +25 -25
  316. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +25 -25
  317. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +25 -25
  318. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +25 -25
  319. nautobot/project-static/docs/user-guide/platform-functionality/role.html +25 -25
  320. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +25 -25
  321. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +25 -25
  322. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +25 -25
  323. nautobot/project-static/docs/user-guide/platform-functionality/status.html +25 -25
  324. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +25 -25
  325. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +25 -25
  326. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +25 -25
  327. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +25 -25
  328. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +25 -25
  329. nautobot/project-static/js/homepage_layout.js +3 -0
  330. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/METADATA +4 -4
  331. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/RECORD +335 -332
  332. nautobot/project-static/docs/assets/javascripts/bundle.fe8b6f2b.min.js +0 -29
  333. nautobot/project-static/docs/assets/javascripts/bundle.fe8b6f2b.min.js.map +0 -7
  334. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +0 -1
  335. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +0 -1
  336. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/LICENSE.txt +0 -0
  337. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/NOTICE +0 -0
  338. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/WHEEL +0 -0
  339. {nautobot-2.3.1.dist-info → nautobot-2.3.3.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Mapping
2
+ from datetime import datetime
2
3
  import logging
3
4
  from pathlib import Path
4
5
 
@@ -20,7 +21,10 @@ class NautobotScheduleEntry(ModelEntry):
20
21
 
21
22
  def __init__(self, model, app=None):
22
23
  """Initialize the model entry."""
24
+ # copy-paste from django_celery_beat.schedulers
23
25
  self.app = app or current_app._get_current_object()
26
+
27
+ # Nautobot-specific logic
24
28
  self.name = f"{model.name}_{model.pk}"
25
29
  self.task = "nautobot.extras.jobs.run_job"
26
30
  try:
@@ -33,6 +37,8 @@ class NautobotScheduleEntry(ModelEntry):
33
37
  except (TypeError, ValueError) as exc:
34
38
  logger.exception("Removing schedule %s for argument deserialization error: %s", self.name, exc)
35
39
  self._disable(model)
40
+
41
+ # copy-paste from django_celery_beat.schedulers
36
42
  try:
37
43
  self.schedule = model.schedule
38
44
  except model.DoesNotExist:
@@ -42,6 +48,7 @@ class NautobotScheduleEntry(ModelEntry):
42
48
  )
43
49
  self._disable(model)
44
50
 
51
+ # Nautobot-specific logic
45
52
  self.options = {"nautobot_job_scheduled_job_id": model.id, "headers": {}}
46
53
 
47
54
  if model.user:
@@ -65,14 +72,25 @@ class NautobotScheduleEntry(ModelEntry):
65
72
  if isinstance(model.celery_kwargs, Mapping):
66
73
  self.options.update(model.celery_kwargs)
67
74
 
75
+ # copy-paste from django_celery_beat.schedulers
68
76
  self.total_run_count = model.total_run_count
69
77
  self.model = model
70
78
 
71
79
  if not model.last_run_at:
72
80
  model.last_run_at = self._default_now()
81
+ # if last_run_at is not set and
82
+ # model.start_time last_run_at should be in way past.
83
+ # This will trigger the job to run at start_time
84
+ # and avoid the heap block.
85
+ if model.start_time:
86
+ model.last_run_at = model.last_run_at - datetime.timedelta(days=365 * 30)
73
87
 
74
88
  self.last_run_at = model.last_run_at
75
89
 
90
+ def _default_now(self):
91
+ """Instead of using self.app.timezone, use the timezone specific to this schedule entry."""
92
+ return datetime.now(self.model.time_zone)
93
+
76
94
 
77
95
  class NautobotDatabaseScheduler(DatabaseScheduler):
78
96
  """
@@ -1830,9 +1830,9 @@ properties:
1830
1830
  The time zone Nautobot will use when dealing with dates and times. It is recommended to use UTC time unless you have a specific need to use a local time zone. Please see the [list of available time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
1831
1831
  details: |-
1832
1832
  !!! warning
1833
- Scheduled jobs will run in the time zone configured in this setting. If you change this setting from the
1834
- default UTC, you must change it on the Celery Beat server and all Nautobot web servers or your scheduled
1835
- jobs may run in the wrong time zone.
1833
+ Scheduled jobs will default to running in the time zone configured in this setting.
1834
+ If you change this setting from the default UTC, it must be set consistently on the Celery Beat server
1835
+ and all Nautobot web servers, or else your scheduled jobs may run in the wrong time zone.
1836
1836
  environment_variable: "NAUTOBOT_TIME_ZONE"
1837
1837
  see_also:
1838
1838
  "Time Zones documentation": "./time-zones.md"
nautobot/core/tables.py CHANGED
@@ -49,7 +49,7 @@ class BaseTable(django_tables2.Table):
49
49
  # Add custom field columns
50
50
  model = self._meta.model
51
51
 
52
- if model.is_dynamic_group_associable_model:
52
+ if getattr(model, "is_dynamic_group_associable_model", False):
53
53
  self.base_columns["dynamic_group_count"] = LinkedCountColumn(
54
54
  viewname="extras:dynamicgroup_list",
55
55
  url_params={"member_id": "pk"},
@@ -39,10 +39,11 @@
39
39
  {% for panel_name, panel_details in registry.homepage_layout.panels.items %}
40
40
  {% if request.user|has_one_or_more_perms:panel_details.permissions %}
41
41
  <div class="panel panel-default" id="{{ panel_name|slugify }}" style="break-inside: avoid" data-panel-weight="{{ panel_details.weight }}">
42
- <div class="panel-heading">
43
- <strong>{{ panel_name }}</strong><span id="toggle-homepanel-{{ panel_name|slugify }}" class="glyphicon glyphicon-chevron-down collapse-icon" type="button" data-toggle="collapse" data-target="#homepanel-{{ panel_name|slugify }}" aria-expanded="false" aria-controls="homepanel-{{ panel_name|slugify }}"></span>
44
- </div>
45
42
  {% with cookie_key='homepanel-'|add:panel_name|slugify %}
43
+ <div class="panel-heading">
44
+ <strong>{{ panel_name }}</strong>
45
+ <span id="collapse-icon-{{ panel_name|slugify }}" class="glyphicon glyphicon-chevron-down collapse-icon{% if request.COOKIES|default:''|get_item:cookie_key|default:'False' == 'False' %} rotated180{% endif %}" type="button" data-toggle="collapse" data-target="#homepanel-{{ panel_name|slugify }}" aria-expanded="false" aria-controls="homepanel-{{ panel_name|slugify }}"></span>
46
+ </div>
46
47
  <div class="list-group collapse{% if request.COOKIES|default:''|get_item:cookie_key|default:'False' == 'False' %} in{% endif %} collapsible-div" id="homepanel-{{ panel_name|slugify }}" >
47
48
  {% endwith %}
48
49
  {% if panel_details.rendered_html %}
@@ -165,7 +165,7 @@ def consolidate_bulk_action_buttons(context):
165
165
 
166
166
  render_edit_button = bool(context["bulk_edit_url"] and context["permissions"]["change"])
167
167
  render_static_group_assign_button = bool(
168
- context["model"].is_dynamic_group_associable_model
168
+ getattr(context["model"], "is_dynamic_group_associable_model", False)
169
169
  and context["user"].has_perms(["extras.add_staticgroupassociation"])
170
170
  )
171
171
  render_delete_button = bool(context["bulk_delete_url"] and context["permissions"]["delete"])
@@ -1,6 +1,13 @@
1
1
  import copy
2
2
  import hashlib
3
3
 
4
+ try:
5
+ from coverage import Coverage
6
+
7
+ has_coverage = True
8
+ except ImportError:
9
+ has_coverage = False
10
+
4
11
  from django.conf import settings
5
12
  from django.core.management import call_command
6
13
  from django.db import connections
@@ -49,6 +56,15 @@ class NautobotTestRunner(DiscoverRunner):
49
56
 
50
57
  exclude_tags = ["integration"]
51
58
 
59
+ @classmethod
60
+ def add_arguments(cls, parser):
61
+ super().add_arguments(parser)
62
+ parser.add_argument(
63
+ "--cache-test-fixtures",
64
+ action="store_true",
65
+ help="Save test database to a json fixture file to re-use on subsequent tests.",
66
+ )
67
+
52
68
  def __init__(self, cache_test_fixtures=False, **kwargs):
53
69
  self.cache_test_fixtures = cache_test_fixtures
54
70
 
@@ -64,15 +80,6 @@ class NautobotTestRunner(DiscoverRunner):
64
80
 
65
81
  super().__init__(**kwargs)
66
82
 
67
- @classmethod
68
- def add_arguments(cls, parser):
69
- super().add_arguments(parser)
70
- parser.add_argument(
71
- "--cache-test-fixtures",
72
- action="store_true",
73
- help="Save test database to a json fixture file to re-use on subsequent tests.",
74
- )
75
-
76
83
  def setup_test_environment(self, **kwargs):
77
84
  super().setup_test_environment(**kwargs)
78
85
  # Remove 'testserver' that Django "helpfully" adds automatically to ALLOWED_HOSTS, masking issues like #3065
@@ -91,6 +98,13 @@ class NautobotTestRunner(DiscoverRunner):
91
98
 
92
99
  old_names = []
93
100
 
101
+ # Nautobot specific - disable coverage measurement to improve performance of (slow) database setup
102
+ cov = None
103
+ if has_coverage:
104
+ cov = Coverage.current()
105
+ if cov is not None:
106
+ cov.stop()
107
+
94
108
  for db_name, aliases in test_databases.values():
95
109
  first_alias = None
96
110
  for alias in aliases:
@@ -150,6 +164,10 @@ class NautobotTestRunner(DiscoverRunner):
150
164
  for alias in connections:
151
165
  connections[alias].force_debug_cursor = True
152
166
 
167
+ # Nautobot specific - resume test coverage measurement
168
+ if cov is not None:
169
+ cov.start()
170
+
153
171
  return old_names
154
172
 
155
173
  def teardown_databases(self, old_config, **kwargs):
@@ -234,6 +234,19 @@ class GetFooForModelTest(TestCase):
234
234
  self.assertEqual(lookup.get_model_from_name("dcim.device"), dcim_models.Device)
235
235
  self.assertEqual(lookup.get_model_from_name("dcim.location"), dcim_models.Location)
236
236
 
237
+ def test_get_model_for_view_name(self):
238
+ """
239
+ Test the util function `get_model_for_view_name` returns the appropriate Model, if the colon separated view name provided.
240
+ """
241
+ with self.subTest("Test core view."):
242
+ self.assertEqual(lookup.get_model_for_view_name("dcim:device_list"), dcim_models.Device)
243
+ with self.subTest("Test app view."):
244
+ self.assertEqual(lookup.get_model_for_view_name("plugins:example_app:examplemodel_list"), ExampleModel)
245
+ with self.subTest("Test unexpected view."):
246
+ with self.assertRaises(ValueError) as err:
247
+ lookup.get_model_for_view_name("unknown:plugins:example_app:examplemodel_list")
248
+ self.assertEqual(str(err.exception), "Unexpected View Name: unknown:plugins:example_app:examplemodel_list")
249
+
237
250
 
238
251
  class IsTaggableTest(TestCase):
239
252
  def test_is_taggable_true(self):
@@ -213,7 +213,13 @@ def get_model_for_view_name(view_name):
213
213
  Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
214
214
  If the app_label or model_name contained by the given view_name is invalid, this will return `None`.
215
215
  """
216
- app_label, model_name = view_name.split(":") # dcim, device_list
216
+ split_view_name = view_name.split(":")
217
+ if len(split_view_name) == 2:
218
+ app_label, model_name = split_view_name # dcim, device_list
219
+ elif len(split_view_name) == 3:
220
+ _, app_label, model_name = split_view_name # plugins, app_name, model_list
221
+ else:
222
+ raise ValueError(f"Unexpected View Name: {view_name}")
217
223
  model_name = model_name.split("_")[0] # device
218
224
 
219
225
  try:
@@ -337,7 +337,7 @@ def common_detail_view_context(request, instance):
337
337
  context["created_by"] = created_by
338
338
  context["last_updated_by"] = last_updated_by
339
339
 
340
- if instance.is_contact_associable_model:
340
+ if getattr(instance, "is_contact_associable_model", False):
341
341
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
342
342
  associations = instance.associated_contacts.restrict(request.user, "view").order_by("role__name")
343
343
  associations_table = AssociatedContactsTable(associations, orderable=False)
@@ -347,7 +347,7 @@ def common_detail_view_context(request, instance):
347
347
  else:
348
348
  context["associated_contacts_table"] = None
349
349
 
350
- if instance.is_dynamic_group_associable_model:
350
+ if getattr(instance, "is_dynamic_group_associable_model", False):
351
351
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
352
352
  dynamic_groups = instance.dynamic_groups.restrict(request.user, "view")
353
353
  dynamic_groups_table = DynamicGroupTable(dynamic_groups, orderable=False)
@@ -358,7 +358,7 @@ def common_detail_view_context(request, instance):
358
358
  else:
359
359
  context["associated_dynamic_groups_table"] = None
360
360
 
361
- if instance.is_metadata_associable_model:
361
+ if getattr(instance, "is_metadata_associable_model", False):
362
362
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
363
363
  object_metadata = instance.associated_object_metadata.restrict(request.user, "view").order_by(
364
364
  "metadata_type", "scoped_fields"
nautobot/dcim/factory.py CHANGED
@@ -110,7 +110,7 @@ NETWORK_DRIVERS = {
110
110
  "Palo Alto": ["paloalto_panos"],
111
111
  }
112
112
 
113
- TIME_ZONES = {timezone for timezone, _ in TimeZoneFormField().choices}
113
+ TIME_ZONES = sorted(timezone for timezone, _ in TimeZoneFormField().choices)
114
114
 
115
115
 
116
116
  # Retrieve correct rack reservation units
@@ -296,7 +296,7 @@ class DeviceTypeFactory(PrimaryModelFactory):
296
296
  while not unused_models:
297
297
  unused_models = {f"{device_type} {count}" for device_type in device_types}.difference(current_models)
298
298
  count += 1
299
- return factory.random.randgen.choice(list(unused_models))
299
+ return factory.random.randgen.choice(sorted(unused_models))
300
300
 
301
301
  has_part_number = NautobotBoolIterator()
302
302
  part_number = factory.Maybe("has_part_number", factory.Faker("ean", length=8), "")
@@ -784,7 +784,7 @@ class ModuleTypeFactory(PrimaryModelFactory):
784
784
  while not unused_models:
785
785
  unused_models = {f"{module_type} {count}" for module_type in module_types}.difference(current_models)
786
786
  count += 1
787
- return factory.random.randgen.choice(list(unused_models))
787
+ return factory.random.randgen.choice(sorted(unused_models))
788
788
 
789
789
 
790
790
  class ModuleFactory(PrimaryModelFactory):
@@ -396,7 +396,7 @@ class DeviceModuleConsolePortTable(ConsolePortTable):
396
396
  "actions",
397
397
  )
398
398
  row_attrs = {
399
- "style": cable_status_color_css,
399
+ "class": cable_status_color_css,
400
400
  }
401
401
 
402
402
 
@@ -460,7 +460,7 @@ class DeviceModuleConsoleServerPortTable(ConsoleServerPortTable):
460
460
  "actions",
461
461
  )
462
462
  row_attrs = {
463
- "style": cable_status_color_css,
463
+ "class": cable_status_color_css,
464
464
  }
465
465
 
466
466
 
@@ -535,7 +535,7 @@ class DeviceModulePowerPortTable(PowerPortTable):
535
535
  "connection",
536
536
  "actions",
537
537
  )
538
- row_attrs = {"style": cable_status_color_css}
538
+ row_attrs = {"class": cable_status_color_css}
539
539
 
540
540
 
541
541
  class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
@@ -613,7 +613,7 @@ class DeviceModulePowerOutletTable(PowerOutletTable):
613
613
  "connection",
614
614
  "actions",
615
615
  )
616
- row_attrs = {"style": cable_status_color_css}
616
+ row_attrs = {"class": cable_status_color_css}
617
617
 
618
618
 
619
619
  class BaseInterfaceTable(BaseTable):
@@ -739,7 +739,7 @@ class DeviceModuleInterfaceTable(InterfaceTable):
739
739
  "actions",
740
740
  ]
741
741
  row_attrs = {
742
- "style": cable_status_color_css,
742
+ "class": cable_status_color_css,
743
743
  "data-name": lambda record: record.name,
744
744
  }
745
745
 
@@ -815,7 +815,7 @@ class DeviceModuleFrontPortTable(FrontPortTable):
815
815
  "cable_peer",
816
816
  "actions",
817
817
  )
818
- row_attrs = {"style": cable_status_color_css}
818
+ row_attrs = {"class": cable_status_color_css}
819
819
 
820
820
 
821
821
  class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
@@ -874,7 +874,7 @@ class DeviceModuleRearPortTable(RearPortTable):
874
874
  "cable_peer",
875
875
  "actions",
876
876
  )
877
- row_attrs = {"style": cable_status_color_css}
877
+ row_attrs = {"class": cable_status_color_css}
878
878
 
879
879
 
880
880
  class DeviceBayTable(DeviceComponentTable):
@@ -406,6 +406,18 @@
406
406
  {% endif %}
407
407
  {% if object.is_dynamic_group_associable_model and perms.extras.view_dynamicgroup %}
408
408
  <div id="dynamic_groups" role="tabpanel" class="tab-pane {% if request.GET.tab == 'dynamic_groups' %}active{% else %}fade{% endif %}">
409
+ <div class="row">
410
+ <div class="col-md-12">
411
+ <div class="alert alert-warning">
412
+ Dynamic group membership is cached for performance reasons,
413
+ therefore this table may not always be up-to-date.
414
+ <br>You can refresh the membership of any specific group by navigating to it from the list below
415
+ or from the <a href="{% url 'extras:dynamicgroup_list' %}">Dynamic Groups list view</a>.
416
+ <br>You can also refresh the membership of all groups by running the
417
+ <a href="{% url 'extras:job_run_by_class_path' class_path='nautobot.core.jobs.groups.RefreshDynamicGroupCaches' %}">Refresh Dynamic Group Caches job</a>.
418
+ </div>
419
+ </div>
420
+ </div>
409
421
  <div class="row">
410
422
  <div class="col-md-12">
411
423
  <form method="post">
@@ -215,6 +215,18 @@
215
215
  {% endif %}
216
216
  {% if object.is_dynamic_group_associable_model and perms.extras.view_dynamicgroup %}
217
217
  <div id="dynamic_groups" role="tabpanel" class="tab-pane {% if request.GET.tab == 'dynamic_groups' %}active{% else %}fade{% endif %}">
218
+ <div class="row">
219
+ <div class="col-md-12">
220
+ <div class="alert alert-warning">
221
+ Dynamic group membership is cached for performance reasons,
222
+ therefore this table may not always be up-to-date.
223
+ <br>You can refresh the membership of any specific group by navigating to it from the list below
224
+ or from the <a href="{% url 'extras:dynamicgroup_list' %}">Dynamic Groups list view</a>.
225
+ <br>You can also refresh the membership of all groups by running the
226
+ <a href="{% url 'extras:job_run_by_class_path' class_path='nautobot.core.jobs.groups.RefreshDynamicGroupCaches' %}">Refresh Dynamic Group Caches job</a>.
227
+ </div>
228
+ </div>
229
+ </div>
218
230
  <div class="row">
219
231
  <div class="col-md-12">
220
232
  <form method="post">
nautobot/dcim/utils.py CHANGED
@@ -14,7 +14,7 @@ from netutils.lib_mapper import (
14
14
  SCRAPLI_LIB_MAPPER_REVERSE,
15
15
  )
16
16
 
17
- from nautobot.core.utils.color import hex_to_rgb, lighten_color, rgb_to_hex
17
+ from nautobot.core.choices import ColorChoices
18
18
  from nautobot.core.utils.config import get_settings_or_config
19
19
  from nautobot.dcim.choices import InterfaceModeChoices
20
20
  from nautobot.dcim.constants import NETUTILS_NETWORK_DRIVER_MAPPING_NAMES
@@ -55,11 +55,14 @@ def cable_status_color_css(record):
55
55
  """
56
56
  if not record.cable:
57
57
  return ""
58
- # The status colors are for use with labels and such, and tend to be quite bright.
59
- # For this function we want a much milder, mellower color suitable as a row background.
60
- base_color = record.cable.get_status_color().strip("#")
61
- lighter_color = rgb_to_hex(*lighten_color(*hex_to_rgb(base_color), 0.75))
62
- return f"background-color: #{lighter_color}"
58
+ else:
59
+ CABLE_STATUS_TO_CSS_CLASS = {
60
+ ColorChoices.COLOR_GREEN: "success",
61
+ ColorChoices.COLOR_AMBER: "warning",
62
+ ColorChoices.COLOR_CYAN: "info",
63
+ }
64
+ status_color = record.cable.get_status_color().strip("#")
65
+ return CABLE_STATUS_TO_CSS_CLASS.get(status_color, "")
63
66
 
64
67
 
65
68
  def get_network_driver_mapping_tool_names():
@@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
6
6
  from drf_spectacular.utils import extend_schema_field
7
7
  from rest_framework import serializers
8
8
  from rest_framework.validators import UniqueTogetherValidator
9
+ from timezone_field.rest_framework import TimeZoneSerializerField
9
10
 
10
11
  from nautobot.core.api import (
11
12
  BaseModelSerializer,
@@ -581,6 +582,7 @@ class JobVariableSerializer(serializers.Serializer):
581
582
 
582
583
  class ScheduledJobSerializer(BaseModelSerializer):
583
584
  # start_time = serializers.DateTimeField(format=None, required=False)
585
+ time_zone = TimeZoneSerializerField(required=False)
584
586
 
585
587
  class Meta:
586
588
  model = ScheduledJob
@@ -175,11 +175,12 @@ def web_request_context(
175
175
  :param user: User object
176
176
  :param context_detail: Optional extra details about the transaction (ex: the plugin name that initiated the change)
177
177
  :param change_id: Optional uuid object to uniquely identify the transaction. One will be generated if not supplied
178
- :param context: Optional string value of the generated change log entries' "change_context" field, defaults to ObjectChangeEventContextChoices.CONTEXT_ORM.
179
- Valid choices are in nautobot.extras.choices.ObjectChangeEventContextChoices
178
+ :param context: Optional string value of the generated change log entries' "change_context" field.
179
+ Defaults to `ObjectChangeEventContextChoices.CONTEXT_ORM`.
180
+ Valid choices are in `nautobot.extras.choices.ObjectChangeEventContextChoices`.
180
181
  :param request: Optional web request instance, one will be generated if not supplied
181
182
  """
182
- from nautobot.extras.jobs import enqueue_job_hooks # prevent circular import
183
+ from nautobot.extras.jobs import enqueue_job_hooks, get_jobs # prevent circular import
183
184
 
184
185
  valid_contexts = {
185
186
  ObjectChangeEventContextChoices.CONTEXT_JOB: JobChangeContext,
@@ -202,9 +203,15 @@ def web_request_context(
202
203
  with change_logging(change_context):
203
204
  yield request
204
205
  finally:
206
+ jobs_refreshed = False
205
207
  # enqueue jobhooks and webhooks, use change_context.change_id in case change_id was not supplied
206
208
  for object_change in ObjectChange.objects.filter(request_id=change_context.change_id).iterator():
207
- enqueue_job_hooks(object_change)
209
+ if context != ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK:
210
+ # Make sure JobHooks are up to date (once) before calling them
211
+ if not jobs_refreshed:
212
+ get_jobs(reload=True)
213
+ jobs_refreshed = True
214
+ enqueue_job_hooks(object_change)
208
215
  enqueue_webhooks(object_change)
209
216
 
210
217
 
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
6
6
  from django.db.models import Q
7
7
  import django_filters
8
8
  from drf_spectacular.utils import extend_schema_field
9
+ from timezone_field import TimeZoneField
9
10
 
10
11
  from nautobot.core.api.exceptions import SerializerNotFound
11
12
  from nautobot.core.api.utils import get_serializer_for_model
@@ -911,6 +912,7 @@ class JobResultFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
911
912
  "job_model__name": "icontains",
912
913
  "name": "icontains",
913
914
  "user__username": "icontains",
915
+ "scheduled_job__name": "icontains",
914
916
  },
915
917
  )
916
918
  job_model = NaturalKeyOrPKMultipleChoiceFilter(
@@ -922,11 +924,16 @@ class JobResultFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
922
924
  queryset=Job.objects.all(),
923
925
  label="Job (ID) - Deprecated (use job_model filter)",
924
926
  )
927
+ scheduled_job = NaturalKeyOrPKMultipleChoiceFilter(
928
+ to_field_name="name",
929
+ queryset=ScheduledJob.objects.all(),
930
+ label="Scheduled Job (name or ID)",
931
+ )
925
932
  status = django_filters.MultipleChoiceFilter(choices=JobResultStatusChoices, null_value=None)
926
933
 
927
934
  class Meta:
928
935
  model = JobResult
929
- fields = ["id", "date_created", "date_done", "name", "status", "user"]
936
+ fields = ["id", "date_created", "date_done", "name", "status", "user", "scheduled_job"]
930
937
 
931
938
 
932
939
  class JobLogEntryFilterSet(BaseFilterSet):
@@ -960,10 +967,15 @@ class ScheduledJobFilterSet(BaseFilterSet):
960
967
  queryset=Job.objects.all(),
961
968
  label="Job (ID) - Deprecated (use job_model filter)",
962
969
  )
970
+ time_zone = django_filters.MultipleChoiceFilter(
971
+ choices=[(str(obj), name) for obj, name in TimeZoneField().choices],
972
+ label="Time zone",
973
+ null_value="",
974
+ )
963
975
 
964
976
  class Meta:
965
977
  model = ScheduledJob
966
- fields = ["id", "name", "total_run_count", "start_time", "last_run_at"]
978
+ fields = ["id", "name", "total_run_count", "start_time", "last_run_at", "time_zone"]
967
979
 
968
980
 
969
981
  #
@@ -1286,6 +1286,12 @@ class JobResultFilterForm(BootstrapMixin, forms.Form):
1286
1286
  required=False,
1287
1287
  widget=StaticSelect2Multiple(),
1288
1288
  )
1289
+ scheduled_job = DynamicModelMultipleChoiceField(
1290
+ label="Scheduled Job",
1291
+ queryset=ScheduledJob.objects.all(),
1292
+ required=False,
1293
+ to_field_name="name",
1294
+ )
1289
1295
 
1290
1296
 
1291
1297
  class ScheduledJobFilterForm(BootstrapMixin, forms.Form):
@@ -174,7 +174,7 @@ class DynamicGroupModelFormMixin(forms.ModelForm):
174
174
 
175
175
  def __init__(self, *args, **kwargs):
176
176
  super().__init__(*args, **kwargs)
177
- if self._meta.model.is_dynamic_group_associable_model:
177
+ if getattr(self._meta.model, "is_dynamic_group_associable_model", False):
178
178
  self.fields["dynamic_groups"] = DynamicModelMultipleChoiceField(
179
179
  required=False,
180
180
  initial=self.instance.dynamic_groups if self.instance else None,
@@ -193,7 +193,7 @@ class DynamicGroupModelFormMixin(forms.ModelForm):
193
193
 
194
194
  def save(self, commit=True):
195
195
  obj = super().save(commit=commit)
196
- if commit and obj.is_dynamic_group_associable_model:
196
+ if commit and getattr(obj, "is_dynamic_group_associable_model", False):
197
197
  current_groups = set(obj.dynamic_groups.filter(group_type=DynamicGroupTypeChoices.TYPE_STATIC))
198
198
  for dynamic_group in set(self.cleaned_data.get("dynamic_groups")).difference(current_groups):
199
199
  dynamic_group.add_members([obj])
nautobot/extras/jobs.py CHANGED
@@ -1168,7 +1168,6 @@ def enqueue_job_hooks(object_change):
1168
1168
  job_hooks = JobHook.objects.filter(content_types=content_type, enabled=True, **{action_flag: True})
1169
1169
 
1170
1170
  # Enqueue the jobs related to the job_hooks
1171
- get_jobs(reload=True)
1172
1171
  for job_hook in job_hooks:
1173
1172
  job_model = job_hook.job
1174
1173
  if not job_model.installed or not job_model.enabled:
@@ -32,6 +32,7 @@ STATUS_CHOICESET_MAP = {
32
32
  "ipam.IPAddress": ipam_choices.IPAddressStatusChoices,
33
33
  "ipam.Prefix": ipam_choices.PrefixStatusChoices,
34
34
  "ipam.VLAN": ipam_choices.VLANStatusChoices,
35
+ "ipam.VRF": ipam_choices.VRFStatusChoices,
35
36
  "virtualization.VirtualMachine": vm_choices.VirtualMachineStatusChoices,
36
37
  "virtualization.VMInterface": vm_choices.VMInterfaceStatusChoices,
37
38
  }
@@ -48,6 +49,7 @@ STATUS_COLOR_MAP = {
48
49
  "Decommissioning": ColorChoices.COLOR_AMBER,
49
50
  "Deprecated": ColorChoices.COLOR_RED,
50
51
  "Deprovisioning": ColorChoices.COLOR_AMBER,
52
+ "Down": ColorChoices.COLOR_AMBER,
51
53
  "End-of-Life": ColorChoices.COLOR_RED,
52
54
  "Extended Support": ColorChoices.COLOR_CYAN,
53
55
  "Failed": ColorChoices.COLOR_RED,
@@ -76,6 +78,7 @@ STATUS_DESCRIPTION_MAP = {
76
78
  "Decommissioning": "Unit is being decommissioned",
77
79
  "Deprecated": "Unit has been deprecated",
78
80
  "Deprovisioning": "Circuit is being deprovisioned",
81
+ "Down": "VRF is down",
79
82
  "End-of-Life": "Unit has reached end-of-life",
80
83
  "Extended Support": "Software is in extended support",
81
84
  "Failed": "Unit has failed",
@@ -0,0 +1,23 @@
1
+ # Generated by Django 4.2.15 on 2024-08-19 13:44
2
+
3
+ from django.db import migrations
4
+ from django.utils import timezone
5
+ import timezone_field.fields
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ("extras", "0114_computedfield_grouping"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="scheduledjob",
16
+ name="time_zone",
17
+ field=timezone_field.fields.TimeZoneField(default=timezone.get_default_timezone_name),
18
+ ),
19
+ migrations.AlterModelOptions(
20
+ name="scheduledjob",
21
+ options={"ordering": ["name"]},
22
+ ),
23
+ ]
@@ -251,7 +251,10 @@ class DynamicGroup(PrimaryModel):
251
251
  # Skip filter fields that have methods defined. They are not reversible.
252
252
  if skip_method_filters and filterset_field.method is not None:
253
253
  # Don't skip method fields that also have a "generate_query_" method
254
- if hasattr(filterset, "generate_query_" + filterset_field.method):
254
+ query_attr = (
255
+ filterset_field.method.__name__ if callable(filterset_field.method) else filterset_field.method
256
+ )
257
+ if hasattr(filterset, f"generate_query_{query_attr}"):
255
258
  logger.debug(
256
259
  "Keeping %s for filterform: has a `generate_query_` filter method", filterset_field_name
257
260
  )