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
nautobot/cloud/factory.py CHANGED
@@ -24,6 +24,7 @@ class CloudAccountFactory(PrimaryModelFactory):
24
24
  has_description = NautobotBoolIterator()
25
25
  description = factory.Maybe("has_description", factory.Faker("sentence"), "")
26
26
  provider = random_instance(Manufacturer, allow_null=False)
27
+ # TODO: once SecretsGroupFactory is implemented:
27
28
  # has_secrets_group = NautobotBoolIterator()
28
29
  # secrets_group = factory.Maybe(
29
30
  # "has_secrets_group",
@@ -40,6 +41,7 @@ class CloudResourceTypeFactory(PrimaryModelFactory):
40
41
  name = factory.LazyAttributeSequence(lambda o, n: f"{o.provider.name} CloudResourceType {n + 1}")
41
42
  has_description = NautobotBoolIterator()
42
43
  description = factory.Maybe("has_description", factory.Faker("sentence"), "")
44
+ # TODO: if we add a `config_schema` here, then CloudNetworkFactory and CloudServiceFactory have to follow it...
43
45
 
44
46
  @factory.post_generation
45
47
  def content_types(self, create, extracted, **kwargs):
nautobot/cloud/filters.py CHANGED
@@ -74,6 +74,8 @@ class CloudNetworkFilterSet(NautobotFilterSet):
74
74
  filter_predicates={
75
75
  "name": "icontains",
76
76
  "description": "icontains",
77
+ "parent__name": "icontains",
78
+ "parent__description": "icontains",
77
79
  "cloud_account__name": "icontains",
78
80
  "cloud_account__description": "icontains",
79
81
  "cloud_resource_type__name": "icontains",
@@ -96,6 +98,7 @@ class CloudNetworkFilterSet(NautobotFilterSet):
96
98
  queryset=models.CloudNetwork.objects.all(),
97
99
  label="Parent cloud network (name or ID)",
98
100
  )
101
+ prefixes = django_filters.ModelMultipleChoiceFilter(queryset=Prefix.objects.all())
99
102
 
100
103
  class Meta:
101
104
  model = models.CloudNetwork
nautobot/cloud/forms.py CHANGED
@@ -26,7 +26,7 @@ class CloudAccountForm(NautobotModelForm):
26
26
  queryset=Manufacturer.objects.all(),
27
27
  help_text="The Manufacturer instance which represents the Cloud Provider",
28
28
  )
29
- secrets_group = DynamicModelChoiceField(queryset=SecretsGroup.objects.all())
29
+ secrets_group = DynamicModelChoiceField(queryset=SecretsGroup.objects.all(), required=False)
30
30
 
31
31
  class Meta:
32
32
  model = CloudAccount
@@ -94,6 +94,7 @@ class CloudNetworkForm(NautobotModelForm):
94
94
  )
95
95
  parent = DynamicModelChoiceField(
96
96
  queryset=CloudNetwork.objects.all(),
97
+ query_params={"parent__isnull": True},
97
98
  required=False,
98
99
  )
99
100
  namespace = DynamicModelChoiceField(queryset=Namespace.objects.all(), required=False)
@@ -187,7 +188,12 @@ class CloudNetworkFilterForm(NautobotFilterForm):
187
188
  cloud_account = DynamicModelMultipleChoiceField(
188
189
  queryset=CloudAccount.objects.all(), to_field_name="name", required=False
189
190
  )
190
- parent = DynamicModelMultipleChoiceField(queryset=CloudNetwork.objects.all(), to_field_name="name", required=False)
191
+ parent = DynamicModelMultipleChoiceField(
192
+ queryset=CloudNetwork.objects.all(),
193
+ query_params={"parent__isnull": True},
194
+ to_field_name="name",
195
+ required=False,
196
+ )
191
197
  tags = TagFilterField(model)
192
198
 
193
199
 
@@ -271,7 +271,7 @@ class Migration(migrations.Migration):
271
271
  field=models.ForeignKey(
272
272
  blank=True,
273
273
  null=True,
274
- on_delete=django.db.models.deletion.SET_NULL,
274
+ on_delete=django.db.models.deletion.PROTECT,
275
275
  related_name="children",
276
276
  to="cloud.cloudnetwork",
277
277
  ),
nautobot/cloud/models.py CHANGED
@@ -14,7 +14,6 @@ from nautobot.extras.utils import FeatureQuery, extras_features
14
14
  @extras_features(
15
15
  "custom_links",
16
16
  "custom_validators",
17
- "dynamic_groups",
18
17
  "export_templates",
19
18
  "graphql",
20
19
  "webhooks",
@@ -137,7 +136,7 @@ class CloudNetwork(CloudResourceTypeMixin, PrimaryModel):
137
136
  description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
138
137
  cloud_account = models.ForeignKey(to=CloudAccount, on_delete=models.PROTECT, related_name="cloud_networks")
139
138
  parent = models.ForeignKey(
140
- to="cloud.CloudNetwork", on_delete=models.SET_NULL, blank=True, null=True, related_name="children"
139
+ to="cloud.CloudNetwork", on_delete=models.PROTECT, blank=True, null=True, related_name="children"
141
140
  )
142
141
  prefixes = models.ManyToManyField(
143
142
  blank=True,
nautobot/cloud/tables.py CHANGED
@@ -7,9 +7,8 @@ from nautobot.core.tables import (
7
7
  TagColumn,
8
8
  ToggleColumn,
9
9
  )
10
- from nautobot.tenancy.tables import TenantColumn
11
10
 
12
- from .models import CloudAccount, CloudNetwork, CloudNetworkPrefixAssignment, CloudResourceType, CloudService
11
+ from .models import CloudAccount, CloudNetwork, CloudResourceType, CloudService
13
12
 
14
13
 
15
14
  class CloudAccountTable(BaseTable):
@@ -95,21 +94,6 @@ class CloudNetworkTable(BaseTable):
95
94
  )
96
95
 
97
96
 
98
- class CloudNetworkPrefixAssignmentTable(BaseTable):
99
- cloud_network = tables.Column(
100
- verbose_name="Cloud Network",
101
- linkify=lambda record: record.cloud_network.get_absolute_url(),
102
- accessor="cloud_network.name",
103
- )
104
- prefix = tables.Column(linkify=True)
105
- rd = tables.Column(accessor="vrf.rd", verbose_name="RD")
106
- tenant = TenantColumn(accessor="vrf.tenant")
107
-
108
- class Meta(BaseTable.Meta):
109
- model = CloudNetworkPrefixAssignment
110
- fields = ("cloud_network", "prefix", "rd", "tenant")
111
-
112
-
113
97
  class CloudResourceTypeTable(BaseTable):
114
98
  pk = ToggleColumn()
115
99
  name = tables.Column(linkify=True)
@@ -100,13 +100,7 @@
100
100
  </tr>
101
101
  <tr>
102
102
  <td>Parent</td>
103
- <td>
104
- {% if object.parent %}
105
- {{ object.parent|hyperlinked_object }}
106
- {% else %}
107
- <span class="text-muted">&mdash;</span>
108
- {% endif %}
109
- </td>
103
+ <td>{{ object.parent|hyperlinked_object }}</td>
110
104
  </tr>
111
105
  <tr>
112
106
  <td>Description</td>
@@ -60,6 +60,17 @@
60
60
  </div>
61
61
  {% endblock content_left_page %}
62
62
 
63
+ {% block content_right_page %}
64
+ <div class="panel panel-default">
65
+ <div class="panel-heading">
66
+ <strong>Config Schema</strong>
67
+ </div>
68
+ <div class="panel-body">
69
+ <pre>{{ object.config_schema|render_json }}</pre>
70
+ </div>
71
+ </div>
72
+ {% endblock content_right_page %}
73
+
63
74
  {% block extra_tab_content %}
64
75
  {% if networks_count %}
65
76
  <div id="networks" role="tabpanel" class="tab-pane {% if not active_tab and not request.GET.tab or request.GET.tab == "networks" %}active{% else %}fade{% endif %}">
@@ -49,6 +49,10 @@
49
49
  <td>Cloud Resource Type</td>
50
50
  <td>{{ object.cloud_resource_type|hyperlinked_object }}</td>
51
51
  </tr>
52
+ <tr>
53
+ <td>Description</td>
54
+ <td>{{ object.description|placeholder }}</td>
55
+ </tr>
52
56
  </table>
53
57
  </div>
54
58
  {% endblock content_left_page %}
@@ -65,6 +65,18 @@ class CloudNetworkTestCase(FilterTestCases.NameOnlyFilterTestCase):
65
65
  ("parent", "parent__id"),
66
66
  ("parent", "parent__name"),
67
67
  ]
68
+ exclude_q_filter_predicates = [
69
+ "parent__name",
70
+ "parent__description",
71
+ ]
72
+
73
+ def _get_relevant_filterset_queryset(self, queryset, *filter_params):
74
+ queryset = super()._get_relevant_filterset_queryset(queryset, *filter_params)
75
+ if "name" in filter_params or "description" in filter_params:
76
+ # Only select an instance with no children as otherwise the search will also match the children,
77
+ # due to `parent__name` and `parent__description` also being search parameters
78
+ queryset = queryset.filter(children__isnull=True)
79
+ return queryset
68
80
 
69
81
 
70
82
  class CloudNetworkPrefixAssignmentTestCase(FilterTestCases.FilterTestCase):
@@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType
2
2
 
3
3
  from nautobot.cloud.models import CloudAccount, CloudNetwork, CloudResourceType, CloudService
4
4
  from nautobot.core.testing import ViewTestCases
5
+ from nautobot.core.testing.utils import post_data
5
6
  from nautobot.dcim.models import Manufacturer
6
7
  from nautobot.extras.models import SecretsGroup, Tag
7
8
 
@@ -36,6 +37,22 @@ class CloudAccountTestCase(ViewTestCases.PrimaryObjectViewTestCase):
36
37
  "comments": "New comments",
37
38
  }
38
39
 
40
+ def test_post_without_secrets_group(self):
41
+ """Assert Secrets Group form field is not required: Fix for https://github.com/nautobot/nautobot/issues/6096"""
42
+ self.add_permissions("cloud.add_cloudaccount", "dcim.view_manufacturer")
43
+ form_data = {
44
+ "name": "New Cloud Account 2",
45
+ "account_number": "8928371982311",
46
+ "provider": Manufacturer.objects.first().pk,
47
+ "description": "A new cloud account",
48
+ }
49
+ request = {
50
+ "path": self._get_url("add"),
51
+ "data": post_data(form_data),
52
+ }
53
+ self.assertHttpStatus(self.client.post(**request), 302)
54
+ self.assertTrue(CloudAccount.objects.filter(name="New Cloud Account 2").exists())
55
+
39
56
 
40
57
  class CloudNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
41
58
  model = CloudNetwork
nautobot/cloud/views.py CHANGED
@@ -59,7 +59,7 @@ class CloudNetworkUIViewSet(NautobotUIViewSet):
59
59
  context = super().get_extra_context(request, instance)
60
60
  if self.action == "retrieve":
61
61
  prefixes = instance.prefixes.restrict(request.user, "view")
62
- prefixes_table = PrefixTable(prefixes.select_related("namespace"))
62
+ prefixes_table = PrefixTable(prefixes.select_related("namespace"), hide_hierarchy_ui=True)
63
63
  prefixes_table.columns.hide("location_count")
64
64
  prefixes_table.columns.hide("vlan")
65
65
 
@@ -8,7 +8,7 @@ from celery import Celery, shared_task, signals
8
8
  from celery.app.log import TaskFormatter
9
9
  from celery.utils.log import get_logger
10
10
  from django.conf import settings
11
- from django.db.utils import ProgrammingError
11
+ from django.db.utils import OperationalError, ProgrammingError
12
12
  from django.utils.functional import SimpleLazyObject
13
13
  from django.utils.module_loading import import_string
14
14
  from kombu.serialization import register
@@ -55,7 +55,10 @@ def import_jobs(sender=None, **kwargs):
55
55
 
56
56
  try:
57
57
  _import_jobs_from_git_repositories()
58
- except ProgrammingError: # Database not ready yet, as may be the case on initial startup and migration
58
+ except (
59
+ OperationalError, # Database not present, as may be the case when running pylint-nautobot
60
+ ProgrammingError, # Database not ready yet, as may be the case on initial startup and migration
61
+ ):
59
62
  pass
60
63
 
61
64
 
@@ -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
  """
nautobot/core/filters.py CHANGED
@@ -6,9 +6,11 @@ from django import forms as django_forms
6
6
  from django.conf import settings
7
7
  from django.db import models
8
8
  from django.forms.utils import ErrorDict, ErrorList
9
+ from django.utils.encoding import force_str
10
+ from django.utils.text import capfirst
9
11
  import django_filters
10
12
  from django_filters.constants import EMPTY_VALUES
11
- from django_filters.utils import get_model_field, resolve_field
13
+ from django_filters.utils import get_model_field, label_for_filter, resolve_field, verbose_lookup_expr
12
14
  from drf_spectacular.types import OpenApiTypes
13
15
  from drf_spectacular.utils import extend_schema_field
14
16
 
@@ -720,6 +722,18 @@ class BaseFilterSet(django_filters.FilterSet):
720
722
  # Of course setting the negation of the existing filter's exclude attribute handles both cases
721
723
  new_filter.exclude = not filter_field.exclude
722
724
 
725
+ # If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
726
+ # so we have to do it.
727
+ if filter_field.label and filter_field.label != label_for_filter(
728
+ cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
729
+ ):
730
+ # Lightly adjusted from label_for_filter() implementation:
731
+ verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
732
+ if isinstance(lookup_expr, str):
733
+ verbose_expression.append(verbose_lookup_expr(lookup_expr))
734
+ verbose_expression = [force_str(part) for part in verbose_expression if part]
735
+ new_filter.label = capfirst(" ".join(verbose_expression))
736
+
723
737
  magic_filters[new_filter_name] = new_filter
724
738
 
725
739
  return magic_filters
@@ -59,6 +59,13 @@ class AddressFieldMixin(forms.ModelForm):
59
59
  class BootstrapMixin(forms.BaseForm):
60
60
  """
61
61
  Add the base Bootstrap CSS classes to form elements.
62
+
63
+ Note that this only applies to form fields that are:
64
+
65
+ 1. statically defined on the form class at declaration time, or
66
+ 2. dynamically added to the form at init time by a class **later in the MRO than this mixin**.
67
+
68
+ If a class earlier in the MRO adds its own fields, it will have to ensure that the widgets are correctly configured.
62
69
  """
63
70
 
64
71
  def __init__(self, *args, **kwargs):
@@ -73,8 +80,9 @@ class BootstrapMixin(forms.BaseForm):
73
80
 
74
81
  for field in self.fields.values():
75
82
  if field.widget.__class__ not in exempt_widgets:
76
- css = field.widget.attrs.get("class", "")
77
- field.widget.attrs["class"] = " ".join([css, "form-control"]).strip()
83
+ css_classes = field.widget.attrs.get("class", "")
84
+ if "form-control" not in css_classes:
85
+ field.widget.attrs["class"] = " ".join([css_classes, "form-control"]).strip()
78
86
  if field.required and not isinstance(field.widget, forms.FileInput):
79
87
  field.widget.attrs["required"] = "required"
80
88
  if "placeholder" not in field.widget.attrs:
@@ -64,8 +64,8 @@ def generate_filter_resolver(schema_type, resolver_name, field_name):
64
64
  """
65
65
  filterset_class = schema_type._meta.filterset_class
66
66
 
67
- def resolve_filter(self, *args, **kwargs):
68
- if not filterset_class:
67
+ def resolve_filter(self, info, **kwargs):
68
+ if not filterset_class or not kwargs:
69
69
  return getattr(self, field_name).all()
70
70
 
71
71
  # Inverse of substitution logic from get_filtering_args_from_filterset() - transform "_type" back to "type"
@@ -4,6 +4,7 @@ from collections import OrderedDict
4
4
  import logging
5
5
 
6
6
  from django.conf import settings
7
+ from django.contrib.contenttypes.fields import GenericRelation
7
8
  from django.contrib.contenttypes.models import ContentType
8
9
  from django.core.validators import ValidationError
9
10
  from django.db.models import ManyToManyField
@@ -205,8 +206,7 @@ def extend_schema_type_filter(schema_type, model):
205
206
  (DjangoObjectType): The extended schema_type object
206
207
  """
207
208
  for field in model._meta.get_fields():
208
- # Check whether attribute is a ManyToOne or ManyToMany field
209
- if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel)):
209
+ if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel, GenericRelation)):
210
210
  continue
211
211
  # OneToOneRel is a subclass of ManyToOneRel, but we don't want to treat it as a list
212
212
  if isinstance(field, OneToOneRel):
@@ -374,16 +374,9 @@ def extend_schema_type_config_context(schema_type, model):
374
374
 
375
375
  def extend_schema_type_global_features(schema_type, model):
376
376
  """
377
- Extend schema_type object to have attributes and resolvers for global features (contacts, dynamic groups, etc.).
377
+ Extend schema_type object to have attributes and resolvers for global features (dynamic groups, etc.).
378
378
  """
379
- if getattr(model, "is_contact_associable_model", False):
380
-
381
- def resolve_associated_contacts(self, args):
382
- return self.associated_contacts.all()
383
-
384
- setattr(schema_type, "resolve_associated_contacts", resolve_associated_contacts)
385
- schema_type._meta.fields["associated_contacts"] = graphene.Field.mounted(graphene.List(ContactAssociationType))
386
-
379
+ # associated_contacts and associated_object_metadata are handled elsewhere by extend_schema_type_filter()
387
380
  if getattr(model, "is_dynamic_group_associable_model", False):
388
381
 
389
382
  def resolve_dynamic_groups(self, args):
@@ -399,10 +392,9 @@ def extend_schema_type_relationships(schema_type, model):
399
392
  """Extend the schema type with attributes and resolvers corresponding
400
393
  to the relationships associated with this model."""
401
394
 
402
- ct = ContentType.objects.get_for_model(model)
403
395
  relationships_by_side = {
404
- "source": Relationship.objects.filter(source_type=ct),
405
- "destination": Relationship.objects.filter(destination_type=ct),
396
+ "source": Relationship.objects.get_for_model_source(model),
397
+ "destination": Relationship.objects.get_for_model_destination(model),
406
398
  }
407
399
 
408
400
  prefix = ""
@@ -1,3 +1,4 @@
1
+ import codecs
1
2
  import contextlib
2
3
  from io import BytesIO
3
4
 
@@ -296,7 +297,9 @@ class ImportObjects(Job):
296
297
  if not csv_data and not csv_file:
297
298
  raise RunJobTaskFailed("Either csv_data or csv_file must be provided")
298
299
  if csv_file:
299
- csv_bytes = csv_file
300
+ # data_encoding is utf-8 and file_encoding is utf-8-sig
301
+ # Bytes read from the original file are decoded according to file_encoding, and the result is encoded using data_encoding.
302
+ csv_bytes = codecs.EncodedFile(csv_file, "utf-8", "utf-8-sig")
300
303
  else:
301
304
  csv_bytes = BytesIO(csv_data.encode("utf-8"))
302
305
 
@@ -329,6 +329,8 @@ class Command(BaseCommand):
329
329
  )
330
330
  _create_batch(MetadataChoiceFactory, 100)
331
331
  _create_batch(ObjectChangeFactory, 100)
332
+ _create_batch(JobResultFactory, 20)
333
+ _create_batch(JobLogEntryFactory, 100)
332
334
  _create_batch(ObjectMetadataFactory, 100)
333
335
  _create_batch(
334
336
  ObjectMetadataFactory,
@@ -344,8 +346,6 @@ class Command(BaseCommand):
344
346
  has_contact=False,
345
347
  description="with teams",
346
348
  )
347
- _create_batch(JobResultFactory, 20)
348
- _create_batch(JobLogEntryFactory, 100)
349
349
 
350
350
  def handle(self, *args, **options):
351
351
  if options["flush"]:
@@ -54,11 +54,11 @@ class BaseModel(models.Model):
54
54
  is_saved_view_model = False # SavedViewMixin overrides this to default True
55
55
  is_cloud_resource_type_model = False # CloudResourceTypeMixin overrides this to default True
56
56
 
57
- associated_object_metadatas = GenericRelation(
57
+ associated_object_metadata = GenericRelation(
58
58
  "extras.ObjectMetadata",
59
59
  content_type_field="assigned_object_type",
60
60
  object_id_field="assigned_object_id",
61
- related_query_name="associated_object_metadatas_%(app_label)s_%(class)s", # e.g. 'associated_object_metadatas_dcim_device'
61
+ related_query_name="associated_object_metadata_%(app_label)s_%(class)s", # e.g. 'associated_object_metadata_dcim_device'
62
62
  )
63
63
 
64
64
  class Meta:
nautobot/core/settings.py CHANGED
@@ -892,13 +892,24 @@ CELERY_TASK_TRACK_STARTED = True
892
892
  # If enabled, a `task-sent` event will be sent for every task so tasks can be tracked before they're consumed by a worker.
893
893
  CELERY_TASK_SEND_SENT_EVENT = True
894
894
 
895
+ # How many tasks a worker is allowed to reserve for its own consumption and execution.
896
+ # If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
897
+ # For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
898
+ # Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
899
+ # so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
900
+ # until the long-running task completes.
901
+ # https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
902
+ CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
903
+
895
904
  # If enabled stdout and stderr of running jobs will be redirected to the task logger.
896
905
  CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
897
906
 
898
- # The log level of log messages generated by redirected job stdout and stderr. Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
907
+ # The log level of log messages generated by redirected job stdout and stderr.
908
+ # Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
899
909
  CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
900
910
 
901
- # Send task-related events so that tasks can be monitored using tools like flower. Sets the default value for the workers -E argument.
911
+ # Send task-related events so that tasks can be monitored using tools like flower.
912
+ # Sets the default value for the workers -E argument.
902
913
  CELERY_WORKER_SEND_TASK_EVENTS = True
903
914
 
904
915
  # Default celery queue name that will be used by workers and tasks if no queue is specified
@@ -426,6 +426,20 @@ properties:
426
426
  see_also:
427
427
  "`CELERY_TASK_SOFT_TIME_LIMIT`": "#celery_task_soft_time_limit"
428
428
  type: "integer"
429
+ CELERY_WORKER_PREFETCH_MULTIPLIER:
430
+ default: 4
431
+ description: "How many tasks a worker is allowed to reserve for its own consumption and execution."
432
+ details: >-
433
+ If set to `0` (not recommended) a single worker can reserve all tasks even if other workers are free.
434
+ For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
435
+ Conversely, for long-running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to `1`
436
+ so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
437
+ until the long-running task completes.
438
+ environment_variable: "NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER"
439
+ see_also:
440
+ "Celery documentation": "https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits"
441
+ type: "integer"
442
+ version_added: "2.2.9"
429
443
  CELERY_WORKER_PROMETHEUS_PORTS:
430
444
  default: []
431
445
  description: "Ports for Prometheus metric HTTP server running on the celery worker(s)."
@@ -468,8 +482,8 @@ properties:
468
482
  If enabling indefinite changelog retention, it is recommended to periodically delete old entries.
469
483
  Otherwise, the database may eventually exceed capacity.
470
484
 
471
- +/- 2.2.0
472
- As of Nautobot 2.2.0, changelog cleanup does not run automatically. Use the `Cleanup of ObjectChange records`
485
+ +/- 2.3.0
486
+ As of Nautobot 2.3.0, changelog cleanup does not run automatically. Use the `Cleanup of ObjectChange records`
473
487
  system Job to handle changelog cleanup; you may schedule this to run automatically like any other Job if
474
488
  desired. The `CHANGELOG_RETENTION` setting provides a default age cutoff for the Job but may be overridden
475
489
  at runtime if desired.
@@ -1816,9 +1830,9 @@ properties:
1816
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).
1817
1831
  details: |-
1818
1832
  !!! warning
1819
- Scheduled jobs will run in the time zone configured in this setting. If you change this setting from the
1820
- default UTC, you must change it on the Celery Beat server and all Nautobot web servers or your scheduled
1821
- 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.
1822
1836
  environment_variable: "NAUTOBOT_TIME_ZONE"
1823
1837
  see_also:
1824
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"},
@@ -155,6 +155,9 @@ class BaseTable(django_tables2.Table):
155
155
  continue
156
156
  if isinstance(column.column, LinkedCountColumn):
157
157
  column_model = lookup.get_model_for_view_name(column.column.viewname)
158
+ if column_model is None:
159
+ logger.error("Couldn't find model for %s", column.column.viewname)
160
+ continue
158
161
  reverse_lookup = column.column.reverse_lookup or next(iter(column.column.url_params.keys()))
159
162
  count_fields.append((column.name, column_model, reverse_lookup))
160
163
  continue
@@ -106,10 +106,10 @@
106
106
  {% endwith %}
107
107
  {% endif %}
108
108
  {% if object.is_metadata_associable_model and perms.extras.view_objectmetadata %}
109
- {% with object.associated_object_metadatas.count as om_count %}
109
+ {% with object.associated_object_metadata.count as om_count %}
110
110
  {% if om_count %}
111
- <li role="presentation"{% if request.GET.tab == 'object_metadatas' %} class="active"{% endif %}>
112
- <a href="{{ object.get_absolute_url }}#object_metadatas" onclick="switch_tab(this.href)" aria-controls="object_metadatas" role="tab" data-toggle="tab">
111
+ <li role="presentation"{% if request.GET.tab == 'object_metadata' %} class="active"{% endif %}>
112
+ <a href="{{ object.get_absolute_url }}#object_metadata" onclick="switch_tab(this.href)" aria-controls="object_metadata" role="tab" data-toggle="tab">
113
113
  Object Metadata {% badge om_count %}
114
114
  </a>
115
115
  </li>
@@ -134,7 +134,7 @@
134
134
  <div class="row">
135
135
  <div class="col-md-6">
136
136
  {% block content_left_page %}{% endblock content_left_page %}
137
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields_advanced_ui=False %}
137
+ {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields=object.get_computed_fields_grouping_basic computed_fields_advanced_ui=False %}
138
138
  {% include 'inc/relationships_panel.html' %}
139
139
  {% include 'extras/inc/tags_panel.html' %}
140
140
  {% plugin_left_page object %}
@@ -246,7 +246,7 @@
246
246
  </div>
247
247
  {% endif %}
248
248
  {% if object.is_metadata_associable_model and perms.extras.view_objectmetadata %}
249
- <div id="object_metadatas" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadatas' %}active{% else %}fade{% endif %}">
249
+ <div id="object_metadata" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadata' %}active{% else %}fade{% endif %}">
250
250
  <div class="row">
251
251
  <div class="col-md-12">
252
252
  <form method="post">
@@ -259,7 +259,7 @@
259
259
  </div>
260
260
  </div>
261
261
  <div class="table-responsive">
262
- {% render_table associated_object_metadatas_table 'inc/table.html' %}
262
+ {% render_table associated_object_metadata_table 'inc/table.html' %}
263
263
  </div>
264
264
  </div>
265
265
  </form>
@@ -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 %}