nautobot 2.3.0b1__py3-none-any.whl → 2.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (399) hide show
  1. nautobot/cloud/factory.py +2 -0
  2. nautobot/cloud/filters.py +3 -0
  3. nautobot/cloud/forms.py +8 -2
  4. nautobot/cloud/migrations/0001_initial.py +1 -1
  5. nautobot/cloud/models.py +1 -2
  6. nautobot/cloud/tables.py +1 -17
  7. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
  8. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +11 -0
  9. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +4 -0
  10. nautobot/cloud/tests/test_filters.py +12 -0
  11. nautobot/cloud/tests/test_views.py +17 -0
  12. nautobot/cloud/views.py +1 -1
  13. nautobot/core/celery/__init__.py +5 -2
  14. nautobot/core/celery/schedulers.py +18 -0
  15. nautobot/core/filters.py +15 -1
  16. nautobot/core/forms/forms.py +10 -2
  17. nautobot/core/graphql/generators.py +2 -2
  18. nautobot/core/graphql/schema.py +6 -14
  19. nautobot/core/jobs/__init__.py +4 -1
  20. nautobot/core/management/commands/generate_test_data.py +2 -2
  21. nautobot/core/models/__init__.py +2 -2
  22. nautobot/core/settings.py +13 -2
  23. nautobot/core/settings.yaml +19 -5
  24. nautobot/core/tables.py +4 -1
  25. nautobot/core/templates/generic/object_retrieve.html +6 -6
  26. nautobot/core/templates/home.html +4 -3
  27. nautobot/core/templates/inc/computed_fields/panel_data.html +36 -24
  28. nautobot/core/templates/inc/object_details_advanced_panel.html +1 -1
  29. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  30. nautobot/core/templatetags/buttons.py +1 -1
  31. nautobot/core/testing/filters.py +12 -1
  32. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  33. nautobot/core/tests/test_jobs.py +74 -1
  34. nautobot/core/views/__init__.py +1 -1
  35. nautobot/core/views/generic.py +1 -1
  36. nautobot/core/views/mixins.py +1 -1
  37. nautobot/core/views/utils.py +11 -9
  38. nautobot/dcim/factory.py +7 -4
  39. nautobot/dcim/filters/__init__.py +4 -0
  40. nautobot/dcim/forms.py +24 -0
  41. nautobot/dcim/migrations/0061_module_models.py +1 -0
  42. nautobot/dcim/models/device_components.py +7 -0
  43. nautobot/dcim/models/devices.py +18 -19
  44. nautobot/dcim/models/racks.py +0 -1
  45. nautobot/dcim/tables/devices.py +24 -10
  46. nautobot/dcim/tables/devicetypes.py +1 -1
  47. nautobot/dcim/templates/dcim/device/base.html +1 -1
  48. nautobot/dcim/templates/dcim/device.html +15 -3
  49. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  50. nautobot/dcim/templates/dcim/moduletype_retrieve.html +17 -0
  51. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +15 -3
  52. nautobot/dcim/tests/test_api.py +2 -0
  53. nautobot/dcim/tests/test_filters.py +14 -7
  54. nautobot/dcim/tests/test_forms.py +54 -0
  55. nautobot/dcim/tests/test_models.py +40 -1
  56. nautobot/dcim/tests/test_views.py +45 -2
  57. nautobot/dcim/utils.py +9 -6
  58. nautobot/dcim/views.py +4 -1
  59. nautobot/extras/api/serializers.py +2 -1
  60. nautobot/extras/api/views.py +7 -59
  61. nautobot/extras/factory.py +50 -12
  62. nautobot/extras/filters/__init__.py +18 -3
  63. nautobot/extras/forms/base.py +10 -4
  64. nautobot/extras/forms/forms.py +7 -0
  65. nautobot/extras/forms/mixins.py +2 -2
  66. nautobot/extras/homepage.py +12 -2
  67. nautobot/extras/jobs.py +2 -2
  68. nautobot/extras/management/__init__.py +3 -0
  69. nautobot/extras/migrations/0111_metadata.py +4 -4
  70. nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
  71. nautobot/extras/migrations/0115_scheduledjob_time_zone.py +23 -0
  72. nautobot/extras/models/customfields.py +54 -0
  73. nautobot/extras/models/jobs.py +105 -9
  74. nautobot/extras/models/metadata.py +18 -18
  75. nautobot/extras/models/models.py +2 -0
  76. nautobot/extras/signals.py +14 -1
  77. nautobot/extras/tables.py +77 -18
  78. nautobot/extras/templates/extras/computedfield.html +4 -0
  79. nautobot/extras/templates/extras/job_detail.html +11 -0
  80. nautobot/extras/templates/extras/scheduledjob.html +13 -2
  81. nautobot/extras/tests/test_api.py +33 -27
  82. nautobot/extras/tests/test_filters.py +57 -1
  83. nautobot/extras/tests/test_jobs.py +2 -2
  84. nautobot/extras/tests/test_models.py +319 -19
  85. nautobot/extras/tests/test_views.py +26 -5
  86. nautobot/extras/utils.py +35 -6
  87. nautobot/extras/views.py +35 -51
  88. nautobot/ipam/api/views.py +9 -2
  89. nautobot/ipam/choices.py +17 -0
  90. nautobot/ipam/factory.py +6 -0
  91. nautobot/ipam/filters.py +2 -2
  92. nautobot/ipam/forms.py +6 -4
  93. nautobot/ipam/migrations/0048_vrf_status.py +23 -0
  94. nautobot/ipam/migrations/0049_vrf_data_migration.py +25 -0
  95. nautobot/ipam/models.py +11 -20
  96. nautobot/ipam/querysets.py +26 -0
  97. nautobot/ipam/tables.py +7 -2
  98. nautobot/ipam/templates/ipam/vrf.html +4 -0
  99. nautobot/ipam/templates/ipam/vrf_edit.html +1 -0
  100. nautobot/ipam/tests/test_api.py +33 -3
  101. nautobot/ipam/tests/test_models.py +89 -2
  102. nautobot/ipam/tests/test_views.py +3 -0
  103. nautobot/ipam/views.py +10 -15
  104. nautobot/project-static/css/base.css +7 -0
  105. nautobot/project-static/docs/404.html +18 -18
  106. nautobot/project-static/docs/apps/index.html +18 -18
  107. nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
  108. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  109. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  110. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
  111. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
  112. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +66 -18
  113. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
  114. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
  115. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
  116. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
  117. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
  118. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +66 -18
  119. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +34 -18
  120. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +82 -63
  121. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +75 -111
  122. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
  123. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +34 -18
  124. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +161 -18
  125. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
  126. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
  127. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
  128. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
  129. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
  130. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
  131. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +21 -19
  132. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +34 -18
  133. nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
  134. nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
  135. nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
  136. nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
  137. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
  138. nautobot/project-static/docs/development/apps/api/models/index.html +33 -22
  139. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
  140. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
  141. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
  142. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
  143. nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
  144. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
  145. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
  146. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
  147. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
  148. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
  149. nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
  150. nautobot/project-static/docs/development/apps/api/setup.html +18 -18
  151. nautobot/project-static/docs/development/apps/api/testing.html +18 -18
  152. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
  153. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
  154. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
  155. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
  156. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
  157. nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
  158. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
  159. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
  160. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
  161. nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
  162. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
  163. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
  164. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
  165. nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
  166. nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
  167. nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
  168. nautobot/project-static/docs/development/apps/index.html +18 -18
  169. nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
  170. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
  171. nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
  172. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
  173. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
  174. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
  175. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
  176. nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
  177. nautobot/project-static/docs/development/core/application-registry.html +18 -18
  178. nautobot/project-static/docs/development/core/best-practices.html +18 -18
  179. nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
  180. nautobot/project-static/docs/development/core/caching.html +18 -18
  181. nautobot/project-static/docs/development/core/controllers.html +18 -18
  182. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
  183. nautobot/project-static/docs/development/core/generic-views.html +18 -18
  184. nautobot/project-static/docs/development/core/getting-started.html +18 -18
  185. nautobot/project-static/docs/development/core/homepage.html +18 -18
  186. nautobot/project-static/docs/development/core/index.html +29 -18
  187. nautobot/project-static/docs/development/core/model-checklist.html +26 -20
  188. nautobot/project-static/docs/development/core/model-features.html +18 -18
  189. nautobot/project-static/docs/development/core/natural-keys.html +18 -18
  190. nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
  191. nautobot/project-static/docs/development/core/release-checklist.html +18 -18
  192. nautobot/project-static/docs/development/core/role-internals.html +18 -18
  193. nautobot/project-static/docs/development/core/settings.html +18 -18
  194. nautobot/project-static/docs/development/core/style-guide.html +19 -19
  195. nautobot/project-static/docs/development/core/templates.html +18 -18
  196. nautobot/project-static/docs/development/core/testing.html +18 -18
  197. nautobot/project-static/docs/development/core/user-preferences.html +18 -18
  198. nautobot/project-static/docs/development/index.html +18 -18
  199. nautobot/project-static/docs/development/jobs/index.html +393 -379
  200. nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
  201. nautobot/project-static/docs/index.html +9032 -13
  202. nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
  203. nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
  204. nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
  205. nautobot/project-static/docs/objects.inv +0 -0
  206. nautobot/project-static/docs/overview/application_stack.html +18 -18
  207. nautobot/project-static/docs/overview/design_philosophy.html +20 -20
  208. nautobot/project-static/docs/overview/index.html +13 -9032
  209. nautobot/project-static/docs/release-notes/index.html +252 -19
  210. nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
  211. nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
  212. nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
  213. nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
  214. nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
  215. nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
  216. nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
  217. nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
  218. nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
  219. nautobot/project-static/docs/release-notes/version-2.2.html +248 -111
  220. nautobot/project-static/docs/release-notes/version-2.3.html +775 -91
  221. nautobot/project-static/docs/requirements.txt +3 -3
  222. nautobot/project-static/docs/search/search_index.json +1 -1
  223. nautobot/project-static/docs/sitemap.xml +278 -278
  224. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  225. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
  226. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
  227. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
  228. nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
  229. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +55 -23
  230. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
  231. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
  232. nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
  233. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +22 -18
  234. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
  235. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
  236. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
  237. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
  238. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
  239. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
  240. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
  241. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
  242. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +69 -82
  243. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
  244. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -52
  245. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +80 -87
  246. nautobot/project-static/docs/user-guide/administration/installation/services.html +37 -44
  247. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
  248. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
  249. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
  250. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
  251. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
  252. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +76 -24
  253. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
  254. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
  255. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
  256. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
  257. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
  258. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
  259. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
  260. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
  261. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
  262. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
  263. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
  264. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
  265. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
  266. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
  267. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
  268. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
  269. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
  270. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
  271. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
  272. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
  273. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
  274. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
  316. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +62 -18
  317. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
  318. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
  319. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
  320. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
  321. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
  322. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
  323. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
  324. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
  325. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
  326. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
  327. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
  328. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
  329. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
  330. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
  331. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
  332. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
  333. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
  334. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
  335. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
  336. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
  337. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
  338. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
  339. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
  340. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
  341. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
  342. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
  343. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
  344. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
  345. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
  346. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
  347. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
  348. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
  349. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
  350. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
  351. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
  352. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
  353. nautobot/project-static/docs/user-guide/index.html +18 -18
  354. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
  355. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
  356. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
  357. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
  358. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
  359. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
  360. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
  361. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
  362. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
  363. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
  364. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
  365. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
  366. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
  367. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
  368. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
  369. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
  370. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
  371. nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
  372. nautobot/project-static/docs/user-guide/platform-functionality/{metadata.html → objectmetadata.html} +197 -84
  373. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
  374. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
  375. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
  376. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
  377. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
  378. nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
  379. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
  380. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
  381. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
  382. nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
  383. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
  384. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
  385. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
  386. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
  387. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
  388. nautobot/project-static/js/homepage_layout.js +3 -0
  389. nautobot/tenancy/templates/tenancy/tenant.html +4 -4
  390. nautobot/virtualization/models.py +0 -2
  391. nautobot/virtualization/tables.py +2 -5
  392. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/METADATA +3 -3
  393. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/RECORD +397 -393
  394. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  395. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  396. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/LICENSE.txt +0 -0
  397. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/NOTICE +0 -0
  398. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/WHEEL +0 -0
  399. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/entry_points.txt +0 -0
@@ -4,7 +4,6 @@ import contextlib
4
4
  from datetime import timedelta
5
5
  import logging
6
6
 
7
- from celery import schedules
8
7
  from celery.exceptions import NotRegistered
9
8
  from celery.utils.log import get_logger, LoggingProxy
10
9
  from django.conf import settings
@@ -16,7 +15,9 @@ from django.db.models import signals
16
15
  from django.utils import timezone
17
16
  from django.utils.functional import cached_property
18
17
  from django_celery_beat.clockedschedule import clocked
18
+ from django_celery_beat.tzcrontab import TzAwareCrontab
19
19
  from prometheus_client import Histogram
20
+ from timezone_field import TimeZoneField
20
21
 
21
22
  from nautobot.core.celery import (
22
23
  app,
@@ -456,6 +457,8 @@ class JobLogEntry(BaseModel):
456
457
  log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
457
458
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
458
459
 
460
+ is_metadata_associable_model = False
461
+
459
462
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
460
463
 
461
464
  def __str__(self):
@@ -933,6 +936,9 @@ class ScheduledJob(BaseModel):
933
936
  verbose_name="Start Datetime",
934
937
  help_text="Datetime when the schedule should begin triggering the task to run",
935
938
  )
939
+ # Django always stores DateTimeField as UTC internally, but we want scheduled jobs to respect DST and similar,
940
+ # so we need to store the time zone the job was scheduled under as well.
941
+ time_zone = TimeZoneField(default=timezone.get_default_timezone_name)
936
942
  # todoindex:
937
943
  enabled = models.BooleanField(
938
944
  default=True,
@@ -1003,12 +1009,15 @@ class ScheduledJob(BaseModel):
1003
1009
  def __str__(self):
1004
1010
  return f"{self.name}: {self.interval}"
1005
1011
 
1012
+ class Meta:
1013
+ ordering = ["name"]
1014
+
1006
1015
  def save(self, *args, **kwargs):
1007
1016
  self.queue = self.queue or ""
1008
1017
  # make sure non-valid crontab doesn't get saved
1009
1018
  if self.interval == JobExecutionType.TYPE_CUSTOM:
1010
1019
  try:
1011
- self.get_crontab(self.crontab)
1020
+ self.get_crontab(self.crontab, tz=self.time_zone)
1012
1021
  except Exception as e:
1013
1022
  raise ValidationError({"crontab": e})
1014
1023
  if not self.enabled:
@@ -1053,7 +1062,7 @@ class ScheduledJob(BaseModel):
1053
1062
  return timezone.now() + timedelta(seconds=15)
1054
1063
 
1055
1064
  @classmethod
1056
- def get_crontab(cls, crontab):
1065
+ def get_crontab(cls, crontab, tz=None):
1057
1066
  """
1058
1067
  Wrapper method translates crontab syntax to Celery crontab.
1059
1068
 
@@ -1066,25 +1075,112 @@ class ScheduledJob(BaseModel):
1066
1075
 
1067
1076
  No support for Last (L), Weekday (W), Number symbol (#), Question mark (?), and special @ strings.
1068
1077
  """
1078
+ if not tz:
1079
+ tz = timezone.get_default_timezone()
1069
1080
  minute, hour, day_of_month, month_of_year, day_of_week = crontab.split(" ")
1070
- return schedules.crontab(
1081
+
1082
+ return TzAwareCrontab(
1071
1083
  minute=minute,
1072
1084
  hour=hour,
1073
1085
  day_of_month=day_of_month,
1074
1086
  month_of_year=month_of_year,
1075
1087
  day_of_week=day_of_week,
1088
+ tz=tz,
1089
+ )
1090
+
1091
+ @classmethod
1092
+ def create_schedule(
1093
+ cls,
1094
+ job_model,
1095
+ user,
1096
+ name=None,
1097
+ start_time=None,
1098
+ interval=JobExecutionType.TYPE_IMMEDIATELY,
1099
+ crontab="",
1100
+ profile=False,
1101
+ approval_required=False,
1102
+ task_queue=None,
1103
+ **job_kwargs,
1104
+ ):
1105
+ """
1106
+ Schedule a job with the specified parameters.
1107
+
1108
+ This method creates a schedule for a job to be executed at a specific time
1109
+ or interval. It handles immediate execution, custom start times, and
1110
+ crontab-based scheduling.
1111
+
1112
+ Parameters:
1113
+ job_model (JobModel): The job model instance.
1114
+ user (User): The user who is scheduling the job.
1115
+ name (str, optional): The name of the scheduled job. Defaults to None.
1116
+ start_time (datetime, optional): The start time for the job. Defaults to None.
1117
+ interval (JobExecutionType, optional): The interval type for the job execution.
1118
+ Defaults to JobExecutionType.TYPE_IMMEDIATELY.
1119
+ crontab (str, optional): The crontab string for the schedule. Defaults to "".
1120
+ profile (bool, optional): Flag indicating whether to profile the job. Defaults to False.
1121
+ approval_required (bool, optional): Flag indicating if approval is required. Defaults to False.
1122
+ task_queue (str, optional): The task queue for the job. Defaults to None, which will use the configured default celery queue.
1123
+ **job_kwargs: Additional keyword arguments to pass to the job.
1124
+
1125
+ Returns:
1126
+ ScheduledJob instance
1127
+ """
1128
+
1129
+ if interval == JobExecutionType.TYPE_IMMEDIATELY:
1130
+ start_time = timezone.localtime()
1131
+ name = name or f"{job_model.name} - {start_time}"
1132
+ elif interval == JobExecutionType.TYPE_CUSTOM:
1133
+ if start_time is None:
1134
+ # "start_time" is checked against models.ScheduledJob.earliest_possible_time()
1135
+ # which returns timezone.now() + timedelta(seconds=15)
1136
+ start_time = timezone.localtime() + timedelta(seconds=20)
1137
+
1138
+ celery_kwargs = {
1139
+ "nautobot_job_profile": profile,
1140
+ "queue": task_queue,
1141
+ }
1142
+ if job_model.soft_time_limit > 0:
1143
+ celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
1144
+ if job_model.time_limit > 0:
1145
+ celery_kwargs["time_limit"] = job_model.time_limit
1146
+
1147
+ # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
1148
+ #
1149
+ # We pass in task and job_model here partly for forward/backward compatibility logic, and
1150
+ # part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
1151
+ # FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
1152
+ # null) you still have a bit of context on the ScheduledJob as to what it was originally
1153
+ # scheduled for.
1154
+ scheduled_job = cls(
1155
+ name=name,
1156
+ task=job_model.class_path,
1157
+ job_model=job_model,
1158
+ start_time=start_time,
1159
+ time_zone=start_time.tzinfo,
1160
+ description=f"Nautobot job {name} scheduled by {user} for {start_time}",
1161
+ kwargs=job_kwargs,
1162
+ celery_kwargs=celery_kwargs,
1163
+ interval=interval,
1164
+ one_off=(interval == JobExecutionType.TYPE_FUTURE),
1165
+ user=user,
1166
+ approval_required=approval_required,
1167
+ crontab=crontab,
1168
+ queue=task_queue,
1076
1169
  )
1170
+ scheduled_job.validated_save()
1171
+ return scheduled_job
1077
1172
 
1078
1173
  def to_cron(self):
1079
- t = self.start_time
1174
+ tz = self.time_zone
1175
+ t = self.start_time.astimezone(tz)
1080
1176
  if self.interval == JobExecutionType.TYPE_HOURLY:
1081
- return schedules.crontab(minute=t.minute)
1177
+ return TzAwareCrontab(minute=t.minute, tz=tz)
1082
1178
  elif self.interval == JobExecutionType.TYPE_DAILY:
1083
- return schedules.crontab(minute=t.minute, hour=t.hour)
1179
+ return TzAwareCrontab(minute=t.minute, hour=t.hour, tz=tz)
1084
1180
  elif self.interval == JobExecutionType.TYPE_WEEKLY:
1085
- return schedules.crontab(minute=t.minute, hour=t.hour, day_of_week=t.strftime("%w"))
1181
+ return TzAwareCrontab(minute=t.minute, hour=t.hour, day_of_week=t.strftime("%w"), tz=tz)
1086
1182
  elif self.interval == JobExecutionType.TYPE_CUSTOM:
1087
- return self.get_crontab(self.crontab)
1183
+ return self.get_crontab(self.crontab, tz=tz)
1088
1184
  raise ValueError(f"I do not know to convert {self.interval} to a Cronjob!")
1089
1185
 
1090
1186
 
@@ -61,7 +61,7 @@ class MetadataType(PrimaryModel):
61
61
 
62
62
  objects = MetadataTypeManager()
63
63
  clone_fields = ["data_type"]
64
- documentation_static_path = "docs/user-guide/platform-functionality/metadata.html"
64
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
65
65
 
66
66
  class Meta:
67
67
  ordering = ["name"]
@@ -106,7 +106,7 @@ class MetadataChoice(ChangeLoggedModel, BaseModel):
106
106
  weight = models.PositiveSmallIntegerField(default=100, help_text="Higher weights appear later in the list")
107
107
  is_metadata_associable_model = False
108
108
 
109
- documentation_static_path = "docs/user-guide/platform-functionality/metadata.html"
109
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
110
110
 
111
111
  class Meta:
112
112
  ordering = ["metadata_type", "weight", "value"]
@@ -166,19 +166,19 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
166
166
  metadata_type = models.ForeignKey(
167
167
  to=MetadataType,
168
168
  on_delete=models.PROTECT,
169
- related_name="object_metadatas",
169
+ related_name="object_metadata",
170
170
  )
171
171
  contact = models.ForeignKey(
172
172
  to=Contact,
173
173
  on_delete=models.PROTECT,
174
- related_name="object_metadatas",
174
+ related_name="object_metadata",
175
175
  blank=True,
176
176
  null=True,
177
177
  )
178
178
  team = models.ForeignKey(
179
179
  to=Team,
180
180
  on_delete=models.PROTECT,
181
- related_name="object_metadatas",
181
+ related_name="object_metadata",
182
182
  blank=True,
183
183
  null=True,
184
184
  )
@@ -186,6 +186,7 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
186
186
  base_field=models.CharField(
187
187
  max_length=CHARFIELD_MAX_LENGTH,
188
188
  ),
189
+ blank=True,
189
190
  help_text="List of scoped fields, only direct fields on the model",
190
191
  )
191
192
  _value = models.JSONField(
@@ -199,7 +200,7 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
199
200
 
200
201
  objects = ObjectMetadataManager()
201
202
  natural_key_field_names = ["pk"]
202
- documentation_static_path = "docs/user-guide/platform-functionality/metadata.html"
203
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
203
204
 
204
205
  class Meta:
205
206
  ordering = ["metadata_type"]
@@ -361,12 +362,17 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
361
362
  raise ValidationError("Date values must be in the format YYYY-MM-DD.")
362
363
  # Validate datetime
363
364
  elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_DATETIME:
364
- acceptable_datetime_formats = [
365
- "YYYY-MM-DDTHH:MM:SS",
366
- "YYYY-MM-DDTHH:MM:SS(+,-)zzzz",
367
- "YYYY-MM-DDTHH:MM:SS(+,-)zz:zz",
368
- ]
369
- if not isinstance(value, datetime):
365
+ if isinstance(value, datetime):
366
+ # check if datetime object has tzinfo
367
+ if value.tzinfo is None:
368
+ value = value.replace(tzinfo=timezone.utc) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
369
+ value = value.replace(microsecond=0).isoformat() # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
370
+ else:
371
+ acceptable_datetime_formats = [
372
+ "YYYY-MM-DDTHH:MM:SS",
373
+ "YYYY-MM-DDTHH:MM:SS(+,-)zzzz",
374
+ "YYYY-MM-DDTHH:MM:SS(+,-)zz:zz",
375
+ ]
370
376
  try:
371
377
  datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
372
378
  except ValueError:
@@ -380,12 +386,6 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
380
386
  )
381
387
  except TypeError:
382
388
  raise ValidationError("Value must be a datetime or str object.")
383
- else:
384
- # if value is a datetime object
385
- # check if datetime object has tzinfo
386
- if value.tzinfo is None:
387
- value = value.replace(tzinfo=timezone.utc)
388
- value = value.replace(microsecond=0).isoformat()
389
389
  # Validate selected choice
390
390
  elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_SELECT:
391
391
  if value not in self.metadata_type.choices.values_list("value", flat=True):
@@ -822,6 +822,8 @@ class Note(ChangeLoggedModel, BaseModel):
822
822
  note = models.TextField()
823
823
  objects = BaseManager.from_queryset(NotesQuerySet)()
824
824
 
825
+ is_metadata_associable_model = False
826
+
825
827
  class Meta:
826
828
  ordering = ["created"]
827
829
  unique_together = [["assigned_object_type", "assigned_object_id", "user_name", "created"]]
@@ -13,7 +13,7 @@ from django.core.cache import cache
13
13
  from django.core.exceptions import ValidationError
14
14
  from django.core.files.storage import get_storage_class
15
15
  from django.db import transaction
16
- from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete, pre_save
16
+ from django.db.models.signals import m2m_changed, post_delete, post_migrate, post_save, pre_delete, pre_save
17
17
  from django.dispatch import receiver
18
18
  from django.utils import timezone
19
19
  from django_prometheus.models import model_deletes, model_inserts, model_updates
@@ -289,6 +289,19 @@ def _handle_deleted_object(sender, instance, **kwargs):
289
289
  model_deletes.labels(instance._meta.model_name).inc()
290
290
 
291
291
 
292
+ #
293
+ # Content types
294
+ #
295
+
296
+
297
+ @receiver(post_migrate)
298
+ def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs):
299
+ """Clear various content-type caches after a migration."""
300
+ with contextlib.suppress(redis.exceptions.ConnectionError):
301
+ cache.delete("nautobot.extras.utils.change_logged_models_queryset")
302
+ cache.delete_pattern("nautobot.extras.utils.FeatureQuery.*")
303
+
304
+
292
305
  #
293
306
  # Custom fields
294
307
  #
nautobot/extras/tables.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from django.conf import settings
2
- from django.utils.html import format_html
2
+ from django.utils.html import format_html, format_html_join
3
3
  import django_tables2 as tables
4
4
  from django_tables2.utils import Accessor
5
5
  from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
@@ -107,6 +107,11 @@ GITREPOSITORY_BUTTONS = """
107
107
 
108
108
  JOB_BUTTONS = """
109
109
  <a href="{% url 'extras:job' pk=record.pk %}" class="btn btn-default btn-xs" title="Details"><i class="mdi mdi-information-outline" aria-hidden="true"></i></a>
110
+ <a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="btn btn-default btn-xs" title="Job Results"><i class="mdi mdi-format-list-bulleted" aria-hidden="true"></i></a>
111
+ """
112
+
113
+ SCHEDULED_JOB_BUTTONS = """
114
+ <a href="{% url 'extras:jobresult_list' %}?scheduled_job={{ record.name | urlencode }}" class="btn btn-default btn-xs" title="Job Results"><i class="mdi mdi-format-list-bulleted" aria-hidden="true"></i></a>
110
115
  """
111
116
 
112
117
  OBJECTCHANGE_OBJECT = """
@@ -715,7 +720,10 @@ class JobTable(BaseTable):
715
720
  return render_markdown(value)
716
721
 
717
722
  def render_name(self, value):
718
- return format_html('<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}', value)
723
+ return format_html(
724
+ '<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
725
+ value,
726
+ )
719
727
 
720
728
  class Meta(BaseTable.Meta):
721
729
  model = JobModel
@@ -831,6 +839,10 @@ class JobResultTable(BaseTable):
831
839
  orderable=False,
832
840
  attrs={"td": {"class": "text-nowrap report-stats"}},
833
841
  )
842
+ scheduled_job = tables.Column(
843
+ linkify=True,
844
+ verbose_name="Scheduled Job",
845
+ )
834
846
  actions = tables.TemplateColumn(
835
847
  template_code="""
836
848
  {% load helpers %}
@@ -880,6 +892,7 @@ class JobResultTable(BaseTable):
880
892
  "date_created",
881
893
  "name",
882
894
  "job_model",
895
+ "scheduled_job",
883
896
  "duration",
884
897
  "date_done",
885
898
  "user",
@@ -887,7 +900,16 @@ class JobResultTable(BaseTable):
887
900
  "summary",
888
901
  "actions",
889
902
  )
890
- default_columns = ("pk", "date_created", "name", "job_model", "user", "status", "summary", "actions")
903
+ default_columns = (
904
+ "pk",
905
+ "date_created",
906
+ "name",
907
+ "job_model",
908
+ "user",
909
+ "status",
910
+ "summary",
911
+ "actions",
912
+ )
891
913
 
892
914
 
893
915
  class JobButtonTable(BaseTable):
@@ -957,10 +979,8 @@ class MetadataTypeTable(BaseTable):
957
979
  class ObjectMetadataTable(BaseTable):
958
980
  pk = ToggleColumn()
959
981
  metadata_type = tables.Column(linkify=True)
960
- assigned_object = tables.TemplateColumn(template_code=ASSIGNED_OBJECT, orderable=False)
961
- actions = ButtonsColumn(
962
- ObjectMetadata,
963
- buttons=("delete"),
982
+ assigned_object = tables.TemplateColumn(
983
+ template_code=ASSIGNED_OBJECT, verbose_name="Assigned object", orderable=False
964
984
  )
965
985
  # This is needed so that render_value method below does not skip itself
966
986
  # when metadata_type.data_type is TYPE_CONTACT_TEAM and we need it to display either contact or team
@@ -974,7 +994,6 @@ class ObjectMetadataTable(BaseTable):
974
994
  "metadata_type",
975
995
  "scoped_fields",
976
996
  "value",
977
- "actions",
978
997
  )
979
998
  default_columns = (
980
999
  "pk",
@@ -982,11 +1001,12 @@ class ObjectMetadataTable(BaseTable):
982
1001
  "scoped_fields",
983
1002
  "value",
984
1003
  "metadata_type",
985
- "actions",
986
1004
  )
987
1005
 
988
- def render_scoped_fields(self, record):
989
- return render_json(record.scoped_fields, pretty_print=True)
1006
+ def render_scoped_fields(self, value):
1007
+ if not value:
1008
+ return "(all fields)"
1009
+ return format_html_join(", ", "<code>{}</code>", ([v] for v in sorted(value)))
990
1010
 
991
1011
  def render_value(self, record):
992
1012
  if record.value is not None and record.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_JSON:
@@ -1027,16 +1047,37 @@ class NoteTable(BaseTable):
1027
1047
 
1028
1048
  class ScheduledJobTable(BaseTable):
1029
1049
  pk = ToggleColumn()
1030
- name = tables.LinkColumn()
1050
+ name = tables.Column(linkify=True)
1031
1051
  job_model = tables.Column(verbose_name="Job", linkify=True)
1032
1052
  interval = tables.Column(verbose_name="Execution Type")
1033
- start_time = tables.Column(verbose_name="First Run")
1034
- last_run_at = tables.Column(verbose_name="Most Recent Run")
1053
+ start_time = tables.DateTimeColumn(verbose_name="First Run", format=settings.SHORT_DATETIME_FORMAT)
1054
+ last_run_at = tables.DateTimeColumn(verbose_name="Most Recent Run", format=settings.SHORT_DATETIME_FORMAT)
1055
+ crontab = tables.Column()
1035
1056
  total_run_count = tables.Column(verbose_name="Total Run Count")
1057
+ actions = ButtonsColumn(ScheduledJob, buttons=("delete"), prepend_template=SCHEDULED_JOB_BUTTONS)
1036
1058
 
1037
1059
  class Meta(BaseTable.Meta):
1038
1060
  model = ScheduledJob
1039
- fields = ("pk", "name", "job_model", "interval", "start_time", "last_run_at")
1061
+ fields = (
1062
+ "pk",
1063
+ "name",
1064
+ "total_run_count",
1065
+ "job_model",
1066
+ "interval",
1067
+ "start_time",
1068
+ "last_run_at",
1069
+ "crontab",
1070
+ "time_zone",
1071
+ "actions",
1072
+ )
1073
+ default_columns = (
1074
+ "pk",
1075
+ "name",
1076
+ "job_model",
1077
+ "interval",
1078
+ "last_run_at",
1079
+ "actions",
1080
+ )
1040
1081
 
1041
1082
 
1042
1083
  class ScheduledJobApprovalQueueTable(BaseTable):
@@ -1108,7 +1149,15 @@ class RelationshipAssociationTable(BaseTable):
1108
1149
 
1109
1150
  class Meta(BaseTable.Meta):
1110
1151
  model = RelationshipAssociation
1111
- fields = ("pk", "relationship", "source_type", "source", "destination_type", "destination", "actions")
1152
+ fields = (
1153
+ "pk",
1154
+ "relationship",
1155
+ "source_type",
1156
+ "source",
1157
+ "destination_type",
1158
+ "destination",
1159
+ "actions",
1160
+ )
1112
1161
  default_columns = ("pk", "relationship", "source", "destination", "actions")
1113
1162
 
1114
1163
 
@@ -1224,7 +1273,15 @@ class TagTable(BaseTable):
1224
1273
 
1225
1274
  class Meta(BaseTable.Meta):
1226
1275
  model = Tag
1227
- fields = ("pk", "name", "items", "color", "content_types", "description", "actions")
1276
+ fields = (
1277
+ "pk",
1278
+ "name",
1279
+ "items",
1280
+ "color",
1281
+ "content_types",
1282
+ "description",
1283
+ "actions",
1284
+ )
1228
1285
 
1229
1286
 
1230
1287
  class TaggedItemTable(BaseTable):
@@ -1304,7 +1361,9 @@ class WebhookTable(BaseTable):
1304
1361
  class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
1305
1362
  pk = ToggleColumn()
1306
1363
  contact_type = tables.TemplateColumn(
1307
- CONTACT_OR_TEAM_ICON, verbose_name="Type", attrs={"td": {"style": "width:20px;"}}
1364
+ CONTACT_OR_TEAM_ICON,
1365
+ verbose_name="Type",
1366
+ attrs={"td": {"style": "width:20px;"}},
1308
1367
  )
1309
1368
  name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
1310
1369
  contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team.phone", verbose_name="Phone")
@@ -20,6 +20,10 @@
20
20
  <td>Key</td>
21
21
  <td><span>{{ object.key }}</span></td>
22
22
  </tr>
23
+ <tr>
24
+ <td>Grouping</td>
25
+ <td>{{ object.grouping | placeholder }}</td>
26
+ </tr>
23
27
  <tr>
24
28
  <td>Description</td>
25
29
  <td><span>{{ object.description|placeholder }}</span></td>
@@ -115,6 +115,17 @@
115
115
  <td>{{ object.enabled | render_boolean }}</td>
116
116
  <td></td>
117
117
  </tr>
118
+ <tr>
119
+ <td>Job Results</td>
120
+ <td>
121
+ {% if object.job_results.exists %}
122
+ <a href="{% url 'extras:jobresult_list' %}?job_model={{ object.name | urlencode }}">{{ object.job_results.count }}</a>
123
+ {% else %}
124
+ {{ None|placeholder }}
125
+ {% endif %}
126
+ </td>
127
+ <td></td>
128
+ </tr>
118
129
  </table>
119
130
  </div>
120
131
  {% endblock content_left_page %}
@@ -4,6 +4,7 @@
4
4
  {% load helpers %}
5
5
  {% load perms %}
6
6
  {% load plugins %}
7
+ {% load tz %}
7
8
 
8
9
  {% block buttons %}
9
10
  {% plugin_buttons object %}
@@ -94,13 +95,23 @@
94
95
  <tr>
95
96
  <td>Start Time</td>
96
97
  <td>
97
- {{ object.start_time }}
98
+ {{ object.start_time|timezone:object.time_zone|date:'Y-m-d H:i:s T' }}
99
+ {% if default_time_zone != object.time_zone %}
100
+ <br>{{ object.start_time|timezone:default_time_zone|date:'Y-m-d H:i:s T' }}
101
+ {% endif %}
98
102
  </td>
99
103
  </tr>
100
104
  <tr>
101
105
  <td>Last Run At</td>
102
106
  <td>
103
- {{ object.last_run_at|placeholder }}
107
+ {% if object.last_run_at %}
108
+ {{ object.last_run_at|timezone:object.time_zone|date:'Y-m-d H:i:s T' }}
109
+ {% if default_time_zone != object.time_zone %}
110
+ <br>{{ object.last_run_at|timezone:default_time_zone|date:'Y-m-d H:i:s T' }}
111
+ {% endif %}
112
+ {% else %}
113
+ {{ object.last_run_at|placeholder }}
114
+ {% endif %}
104
115
  </td>
105
116
  </tr>
106
117
  <tr>