nautobot 2.3.0b1__py3-none-any.whl → 2.3.1__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 (380) 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/filters.py +15 -1
  15. nautobot/core/forms/forms.py +10 -2
  16. nautobot/core/graphql/generators.py +2 -2
  17. nautobot/core/graphql/schema.py +6 -14
  18. nautobot/core/jobs/__init__.py +4 -1
  19. nautobot/core/management/commands/generate_test_data.py +2 -2
  20. nautobot/core/models/__init__.py +2 -2
  21. nautobot/core/settings.py +13 -2
  22. nautobot/core/settings.yaml +16 -2
  23. nautobot/core/tables.py +3 -0
  24. nautobot/core/templates/generic/object_retrieve.html +6 -6
  25. nautobot/core/templates/inc/computed_fields/panel_data.html +36 -24
  26. nautobot/core/templates/inc/object_details_advanced_panel.html +1 -1
  27. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  28. nautobot/core/testing/filters.py +12 -1
  29. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  30. nautobot/core/tests/test_jobs.py +74 -1
  31. nautobot/core/views/__init__.py +1 -1
  32. nautobot/core/views/generic.py +1 -1
  33. nautobot/core/views/mixins.py +1 -1
  34. nautobot/core/views/utils.py +8 -6
  35. nautobot/dcim/factory.py +4 -1
  36. nautobot/dcim/filters/__init__.py +4 -0
  37. nautobot/dcim/forms.py +24 -0
  38. nautobot/dcim/migrations/0061_module_models.py +1 -0
  39. nautobot/dcim/models/device_components.py +7 -0
  40. nautobot/dcim/models/devices.py +18 -19
  41. nautobot/dcim/models/racks.py +0 -1
  42. nautobot/dcim/tables/devices.py +17 -3
  43. nautobot/dcim/tables/devicetypes.py +1 -1
  44. nautobot/dcim/templates/dcim/device/base.html +1 -1
  45. nautobot/dcim/templates/dcim/device.html +3 -3
  46. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  47. nautobot/dcim/templates/dcim/moduletype_retrieve.html +17 -0
  48. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +3 -3
  49. nautobot/dcim/tests/test_api.py +2 -0
  50. nautobot/dcim/tests/test_filters.py +14 -7
  51. nautobot/dcim/tests/test_forms.py +54 -0
  52. nautobot/dcim/tests/test_models.py +40 -1
  53. nautobot/dcim/tests/test_views.py +45 -2
  54. nautobot/dcim/views.py +4 -1
  55. nautobot/extras/api/serializers.py +0 -1
  56. nautobot/extras/api/views.py +7 -59
  57. nautobot/extras/factory.py +50 -12
  58. nautobot/extras/filters/__init__.py +4 -1
  59. nautobot/extras/forms/base.py +10 -4
  60. nautobot/extras/forms/forms.py +1 -0
  61. nautobot/extras/homepage.py +12 -2
  62. nautobot/extras/jobs.py +2 -2
  63. nautobot/extras/migrations/0111_metadata.py +4 -4
  64. nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
  65. nautobot/extras/models/customfields.py +54 -0
  66. nautobot/extras/models/jobs.py +83 -0
  67. nautobot/extras/models/metadata.py +18 -18
  68. nautobot/extras/models/models.py +2 -0
  69. nautobot/extras/signals.py +14 -1
  70. nautobot/extras/tables.py +43 -14
  71. nautobot/extras/templates/extras/computedfield.html +4 -0
  72. nautobot/extras/templates/extras/job_detail.html +11 -0
  73. nautobot/extras/tests/test_api.py +16 -9
  74. nautobot/extras/tests/test_jobs.py +2 -2
  75. nautobot/extras/tests/test_models.py +20 -18
  76. nautobot/extras/tests/test_views.py +23 -3
  77. nautobot/extras/utils.py +35 -6
  78. nautobot/extras/views.py +28 -51
  79. nautobot/ipam/filters.py +1 -1
  80. nautobot/ipam/forms.py +1 -1
  81. nautobot/ipam/models.py +9 -20
  82. nautobot/ipam/querysets.py +26 -0
  83. nautobot/ipam/tables.py +4 -0
  84. nautobot/ipam/tests/test_models.py +89 -2
  85. nautobot/ipam/views.py +10 -15
  86. nautobot/project-static/css/base.css +1 -0
  87. nautobot/project-static/docs/404.html +18 -18
  88. nautobot/project-static/docs/apps/index.html +18 -18
  89. nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
  90. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  91. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  92. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
  93. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
  94. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +66 -18
  95. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
  96. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
  97. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
  98. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
  99. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
  100. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +66 -18
  101. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +34 -18
  102. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +82 -63
  103. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +75 -111
  104. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
  105. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +34 -18
  106. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +161 -18
  107. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
  108. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
  109. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
  110. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
  111. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
  112. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
  113. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +21 -19
  114. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +34 -18
  115. nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
  116. nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
  117. nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
  118. nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
  119. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
  120. nautobot/project-static/docs/development/apps/api/models/index.html +33 -22
  121. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
  122. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
  123. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
  124. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
  125. nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
  126. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
  127. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
  128. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
  129. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
  130. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
  131. nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
  132. nautobot/project-static/docs/development/apps/api/setup.html +18 -18
  133. nautobot/project-static/docs/development/apps/api/testing.html +18 -18
  134. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
  135. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
  136. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
  137. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
  138. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
  139. nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
  140. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
  141. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
  142. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
  143. nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
  144. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
  145. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
  146. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
  147. nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
  148. nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
  149. nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
  150. nautobot/project-static/docs/development/apps/index.html +18 -18
  151. nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
  152. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
  153. nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
  154. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
  155. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
  156. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
  157. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
  158. nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
  159. nautobot/project-static/docs/development/core/application-registry.html +18 -18
  160. nautobot/project-static/docs/development/core/best-practices.html +18 -18
  161. nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
  162. nautobot/project-static/docs/development/core/caching.html +18 -18
  163. nautobot/project-static/docs/development/core/controllers.html +18 -18
  164. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
  165. nautobot/project-static/docs/development/core/generic-views.html +18 -18
  166. nautobot/project-static/docs/development/core/getting-started.html +18 -18
  167. nautobot/project-static/docs/development/core/homepage.html +18 -18
  168. nautobot/project-static/docs/development/core/index.html +29 -18
  169. nautobot/project-static/docs/development/core/model-checklist.html +26 -20
  170. nautobot/project-static/docs/development/core/model-features.html +18 -18
  171. nautobot/project-static/docs/development/core/natural-keys.html +18 -18
  172. nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
  173. nautobot/project-static/docs/development/core/release-checklist.html +18 -18
  174. nautobot/project-static/docs/development/core/role-internals.html +18 -18
  175. nautobot/project-static/docs/development/core/settings.html +18 -18
  176. nautobot/project-static/docs/development/core/style-guide.html +19 -19
  177. nautobot/project-static/docs/development/core/templates.html +18 -18
  178. nautobot/project-static/docs/development/core/testing.html +18 -18
  179. nautobot/project-static/docs/development/core/user-preferences.html +18 -18
  180. nautobot/project-static/docs/development/index.html +18 -18
  181. nautobot/project-static/docs/development/jobs/index.html +393 -379
  182. nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
  183. nautobot/project-static/docs/index.html +9032 -13
  184. nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
  185. nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
  186. nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
  187. nautobot/project-static/docs/objects.inv +0 -0
  188. nautobot/project-static/docs/overview/application_stack.html +18 -18
  189. nautobot/project-static/docs/overview/design_philosophy.html +20 -20
  190. nautobot/project-static/docs/overview/index.html +13 -9032
  191. nautobot/project-static/docs/release-notes/index.html +252 -19
  192. nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
  193. nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
  194. nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
  195. nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
  196. nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
  197. nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
  198. nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
  199. nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
  200. nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
  201. nautobot/project-static/docs/release-notes/version-2.2.html +248 -111
  202. nautobot/project-static/docs/release-notes/version-2.3.html +644 -90
  203. nautobot/project-static/docs/requirements.txt +3 -3
  204. nautobot/project-static/docs/search/search_index.json +1 -1
  205. nautobot/project-static/docs/sitemap.xml +278 -278
  206. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  207. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
  208. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
  209. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
  210. nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
  211. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +52 -20
  212. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
  213. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
  214. nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
  215. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +22 -18
  216. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
  217. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
  218. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
  219. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
  220. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
  221. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
  222. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
  223. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
  224. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +69 -82
  225. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
  226. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -52
  227. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +80 -87
  228. nautobot/project-static/docs/user-guide/administration/installation/services.html +37 -44
  229. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
  230. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
  231. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
  232. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
  233. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
  234. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +76 -24
  235. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
  236. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
  237. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
  238. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
  239. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
  240. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
  241. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
  242. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
  243. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
  244. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
  245. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
  246. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
  247. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
  248. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
  249. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
  250. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
  251. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
  252. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
  253. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
  254. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
  255. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
  256. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
  298. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +62 -18
  299. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
  300. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
  301. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
  302. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
  303. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
  304. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
  305. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
  306. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
  307. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
  308. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
  309. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
  310. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
  311. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
  312. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
  313. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
  314. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
  315. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
  316. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
  317. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
  318. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
  319. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
  320. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
  321. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
  322. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
  323. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
  324. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
  325. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
  326. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
  327. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
  328. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
  329. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
  330. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
  331. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
  332. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
  333. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
  334. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
  335. nautobot/project-static/docs/user-guide/index.html +18 -18
  336. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
  337. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
  338. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
  339. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
  340. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
  341. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
  342. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
  343. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
  344. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
  345. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
  346. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
  347. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
  348. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
  349. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
  350. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
  351. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
  352. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
  353. nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
  354. nautobot/project-static/docs/user-guide/platform-functionality/{metadata.html → objectmetadata.html} +197 -84
  355. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
  356. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
  357. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
  358. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
  359. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
  360. nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
  361. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
  362. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
  363. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
  364. nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
  365. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
  366. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
  367. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
  368. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
  369. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
  370. nautobot/tenancy/templates/tenancy/tenant.html +4 -4
  371. nautobot/virtualization/models.py +0 -2
  372. nautobot/virtualization/tables.py +2 -5
  373. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/METADATA +3 -3
  374. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/RECORD +378 -377
  375. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  376. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  377. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/LICENSE.txt +0 -0
  378. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/NOTICE +0 -0
  379. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/WHEEL +0 -0
  380. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/entry_points.txt +0 -0
@@ -72,7 +72,7 @@ class Migration(migrations.Migration):
72
72
  ("last_updated", models.DateTimeField(auto_now=True, null=True)),
73
73
  (
74
74
  "scoped_fields",
75
- nautobot.core.models.fields.JSONArrayField(base_field=models.CharField(max_length=255)),
75
+ nautobot.core.models.fields.JSONArrayField(base_field=models.CharField(max_length=255), blank=True),
76
76
  ),
77
77
  ("_value", models.JSONField(blank=True, null=True)),
78
78
  ("assigned_object_id", models.UUIDField(db_index=True)),
@@ -91,7 +91,7 @@ class Migration(migrations.Migration):
91
91
  blank=True,
92
92
  null=True,
93
93
  on_delete=django.db.models.deletion.PROTECT,
94
- related_name="object_metadatas",
94
+ related_name="object_metadata",
95
95
  to="extras.contact",
96
96
  ),
97
97
  ),
@@ -99,7 +99,7 @@ class Migration(migrations.Migration):
99
99
  "metadata_type",
100
100
  models.ForeignKey(
101
101
  on_delete=django.db.models.deletion.PROTECT,
102
- related_name="object_metadatas",
102
+ related_name="object_metadata",
103
103
  to="extras.metadatatype",
104
104
  ),
105
105
  ),
@@ -109,7 +109,7 @@ class Migration(migrations.Migration):
109
109
  blank=True,
110
110
  null=True,
111
111
  on_delete=django.db.models.deletion.PROTECT,
112
- related_name="object_metadatas",
112
+ related_name="object_metadata",
113
113
  to="extras.team",
114
114
  ),
115
115
  ),
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.15 on 2024-08-09 14:28
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("extras", "0113_saved_views"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="computedfield",
14
+ name="grouping",
15
+ field=models.CharField(blank=True, max_length=255),
16
+ ),
17
+ ]
@@ -88,6 +88,11 @@ class ComputedField(
88
88
  help_text="Internal field name. Please use underscores rather than dashes in this key.",
89
89
  slugify_function=slugify_dashes_to_underscores,
90
90
  )
91
+ grouping = models.CharField(
92
+ max_length=CHARFIELD_MAX_LENGTH,
93
+ blank=True,
94
+ help_text="Human-readable grouping that this computed field belongs to.",
95
+ )
91
96
  label = models.CharField(max_length=CHARFIELD_MAX_LENGTH, help_text="Name of the field as displayed to users")
92
97
  description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
93
98
  template = models.TextField(max_length=500, help_text="Jinja2 template code for field value")
@@ -295,6 +300,55 @@ class CustomFieldModel(models.Model):
295
300
  return computed_field.render(context={"obj": self})
296
301
  return computed_field.template
297
302
 
303
+ def get_computed_fields_grouping_basic(self):
304
+ """
305
+ This method exists to help call get_computed_field_groupings() in templates where a function argument (advanced_ui) cannot be specified.
306
+ Return a dictonary of computed fields grouped by the same grouping in the form
307
+ {
308
+ <grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
309
+ ...
310
+ <grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
311
+ ...
312
+ }
313
+ which have advanced_ui set to False
314
+ """
315
+ return self.get_computed_fields_grouping(advanced_ui=False)
316
+
317
+ def get_computed_fields_grouping_advanced(self):
318
+ """
319
+ This method exists to help call get_computed_field_groupings() in templates where a function argument (advanced_ui) cannot be specified.
320
+ Return a dictonary of computed fields grouped by the same grouping in the form
321
+ {
322
+ <grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
323
+ ...
324
+ <grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
325
+ ...
326
+ }
327
+ which have advanced_ui set to True
328
+ """
329
+ return self.get_computed_fields_grouping(advanced_ui=True)
330
+
331
+ def get_computed_fields_grouping(self, advanced_ui=None):
332
+ """
333
+ Return a dictonary of computed fields grouped by the same grouping in the form
334
+ {
335
+ <grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
336
+ ...
337
+ <grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
338
+ ...
339
+ }
340
+ """
341
+ record = {}
342
+ computed_fields = ComputedField.objects.get_for_model(self)
343
+ if advanced_ui is not None:
344
+ computed_fields = computed_fields.filter(advanced_ui=advanced_ui)
345
+
346
+ for field in computed_fields:
347
+ data = (field, field.render(context={"obj": self}))
348
+ record.setdefault(field.grouping, []).append(data)
349
+ record = dict(sorted(record.items()))
350
+ return record
351
+
298
352
  def get_computed_fields(self, label_as_key=False, advanced_ui=None):
299
353
  """
300
354
  Return a dictionary of all computed fields and their rendered values for this model.
@@ -456,6 +456,8 @@ class JobLogEntry(BaseModel):
456
456
  log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
457
457
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
458
458
 
459
+ is_metadata_associable_model = False
460
+
459
461
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
460
462
 
461
463
  def __str__(self):
@@ -1075,6 +1077,87 @@ class ScheduledJob(BaseModel):
1075
1077
  day_of_week=day_of_week,
1076
1078
  )
1077
1079
 
1080
+ @classmethod
1081
+ def create_schedule(
1082
+ cls,
1083
+ job_model,
1084
+ user,
1085
+ name=None,
1086
+ start_time=None,
1087
+ interval=JobExecutionType.TYPE_IMMEDIATELY,
1088
+ crontab="",
1089
+ profile=False,
1090
+ approval_required=False,
1091
+ task_queue=None,
1092
+ **job_kwargs,
1093
+ ):
1094
+ """
1095
+ Schedule a job with the specified parameters.
1096
+
1097
+ This method creates a schedule for a job to be executed at a specific time
1098
+ or interval. It handles immediate execution, custom start times, and
1099
+ crontab-based scheduling.
1100
+
1101
+ Parameters:
1102
+ job_model (JobModel): The job model instance.
1103
+ user (User): The user who is scheduling the job.
1104
+ name (str, optional): The name of the scheduled job. Defaults to None.
1105
+ start_time (datetime, optional): The start time for the job. Defaults to None.
1106
+ interval (JobExecutionType, optional): The interval type for the job execution.
1107
+ Defaults to JobExecutionType.TYPE_IMMEDIATELY.
1108
+ crontab (str, optional): The crontab string for the schedule. Defaults to "".
1109
+ profile (bool, optional): Flag indicating whether to profile the job. Defaults to False.
1110
+ approval_required (bool, optional): Flag indicating if approval is required. Defaults to False.
1111
+ task_queue (str, optional): The task queue for the job. Defaults to None, which will use the configured default celery queue.
1112
+ **job_kwargs: Additional keyword arguments to pass to the job.
1113
+
1114
+ Returns:
1115
+ ScheduledJob instance
1116
+ """
1117
+
1118
+ if interval == JobExecutionType.TYPE_IMMEDIATELY:
1119
+ start_time = timezone.now()
1120
+ name = name or f"{job_model.name} - {start_time}"
1121
+ elif interval == JobExecutionType.TYPE_CUSTOM:
1122
+ if start_time is None:
1123
+ # "start_time" is checked against models.ScheduledJob.earliest_possible_time()
1124
+ # which returns timezone.now() + timedelta(seconds=15)
1125
+ start_time = timezone.now() + timedelta(seconds=20)
1126
+
1127
+ celery_kwargs = {
1128
+ "nautobot_job_profile": profile,
1129
+ "queue": task_queue,
1130
+ }
1131
+ if job_model.soft_time_limit > 0:
1132
+ celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
1133
+ if job_model.time_limit > 0:
1134
+ celery_kwargs["time_limit"] = job_model.time_limit
1135
+
1136
+ # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
1137
+ #
1138
+ # We pass in task and job_model here partly for forward/backward compatibility logic, and
1139
+ # part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
1140
+ # FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
1141
+ # null) you still have a bit of context on the ScheduledJob as to what it was originally
1142
+ # scheduled for.
1143
+ scheduled_job = cls(
1144
+ name=name,
1145
+ task=job_model.class_path,
1146
+ job_model=job_model,
1147
+ start_time=start_time,
1148
+ description=f"Nautobot job {name} scheduled by {user} for {start_time}",
1149
+ kwargs=job_kwargs,
1150
+ celery_kwargs=celery_kwargs,
1151
+ interval=interval,
1152
+ one_off=(interval == JobExecutionType.TYPE_FUTURE),
1153
+ user=user,
1154
+ approval_required=approval_required,
1155
+ crontab=crontab,
1156
+ queue=task_queue,
1157
+ )
1158
+ scheduled_job.validated_save()
1159
+ return scheduled_job
1160
+
1078
1161
  def to_cron(self):
1079
1162
  t = self.start_time
1080
1163
  if self.interval == JobExecutionType.TYPE_HOURLY:
@@ -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,7 @@ 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>
110
111
  """
111
112
 
112
113
  OBJECTCHANGE_OBJECT = """
@@ -715,7 +716,10 @@ class JobTable(BaseTable):
715
716
  return render_markdown(value)
716
717
 
717
718
  def render_name(self, value):
718
- return format_html('<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}', value)
719
+ return format_html(
720
+ '<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
721
+ value,
722
+ )
719
723
 
720
724
  class Meta(BaseTable.Meta):
721
725
  model = JobModel
@@ -887,7 +891,16 @@ class JobResultTable(BaseTable):
887
891
  "summary",
888
892
  "actions",
889
893
  )
890
- default_columns = ("pk", "date_created", "name", "job_model", "user", "status", "summary", "actions")
894
+ default_columns = (
895
+ "pk",
896
+ "date_created",
897
+ "name",
898
+ "job_model",
899
+ "user",
900
+ "status",
901
+ "summary",
902
+ "actions",
903
+ )
891
904
 
892
905
 
893
906
  class JobButtonTable(BaseTable):
@@ -957,10 +970,8 @@ class MetadataTypeTable(BaseTable):
957
970
  class ObjectMetadataTable(BaseTable):
958
971
  pk = ToggleColumn()
959
972
  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"),
973
+ assigned_object = tables.TemplateColumn(
974
+ template_code=ASSIGNED_OBJECT, verbose_name="Assigned object", orderable=False
964
975
  )
965
976
  # This is needed so that render_value method below does not skip itself
966
977
  # when metadata_type.data_type is TYPE_CONTACT_TEAM and we need it to display either contact or team
@@ -974,7 +985,6 @@ class ObjectMetadataTable(BaseTable):
974
985
  "metadata_type",
975
986
  "scoped_fields",
976
987
  "value",
977
- "actions",
978
988
  )
979
989
  default_columns = (
980
990
  "pk",
@@ -982,11 +992,12 @@ class ObjectMetadataTable(BaseTable):
982
992
  "scoped_fields",
983
993
  "value",
984
994
  "metadata_type",
985
- "actions",
986
995
  )
987
996
 
988
- def render_scoped_fields(self, record):
989
- return render_json(record.scoped_fields, pretty_print=True)
997
+ def render_scoped_fields(self, value):
998
+ if not value:
999
+ return "(all fields)"
1000
+ return format_html_join(", ", "<code>{}</code>", ([v] for v in sorted(value)))
990
1001
 
991
1002
  def render_value(self, record):
992
1003
  if record.value is not None and record.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_JSON:
@@ -1108,7 +1119,15 @@ class RelationshipAssociationTable(BaseTable):
1108
1119
 
1109
1120
  class Meta(BaseTable.Meta):
1110
1121
  model = RelationshipAssociation
1111
- fields = ("pk", "relationship", "source_type", "source", "destination_type", "destination", "actions")
1122
+ fields = (
1123
+ "pk",
1124
+ "relationship",
1125
+ "source_type",
1126
+ "source",
1127
+ "destination_type",
1128
+ "destination",
1129
+ "actions",
1130
+ )
1112
1131
  default_columns = ("pk", "relationship", "source", "destination", "actions")
1113
1132
 
1114
1133
 
@@ -1224,7 +1243,15 @@ class TagTable(BaseTable):
1224
1243
 
1225
1244
  class Meta(BaseTable.Meta):
1226
1245
  model = Tag
1227
- fields = ("pk", "name", "items", "color", "content_types", "description", "actions")
1246
+ fields = (
1247
+ "pk",
1248
+ "name",
1249
+ "items",
1250
+ "color",
1251
+ "content_types",
1252
+ "description",
1253
+ "actions",
1254
+ )
1228
1255
 
1229
1256
 
1230
1257
  class TaggedItemTable(BaseTable):
@@ -1304,7 +1331,9 @@ class WebhookTable(BaseTable):
1304
1331
  class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
1305
1332
  pk = ToggleColumn()
1306
1333
  contact_type = tables.TemplateColumn(
1307
- CONTACT_OR_TEAM_ICON, verbose_name="Type", attrs={"td": {"style": "width:20px;"}}
1334
+ CONTACT_OR_TEAM_ICON,
1335
+ verbose_name="Type",
1336
+ attrs={"td": {"style": "width:20px;"}},
1308
1337
  )
1309
1338
  name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
1310
1339
  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 %}
@@ -15,7 +15,7 @@ from rest_framework import status
15
15
  from nautobot.core.choices import ColorChoices
16
16
  from nautobot.core.models.fields import slugify_dashes_to_underscores
17
17
  from nautobot.core.testing import APITestCase, APIViewTestCases
18
- from nautobot.core.testing.utils import disable_warnings
18
+ from nautobot.core.testing.utils import disable_warnings, get_deletable_objects
19
19
  from nautobot.core.utils.lookup import get_route_for_model
20
20
  from nautobot.core.utils.permissions import get_permission_for_model
21
21
  from nautobot.dcim.models import (
@@ -2741,21 +2741,21 @@ class ObjectMetadataTest(APIViewTestCases.APIViewTestCase):
2741
2741
  value="Hey",
2742
2742
  scoped_fields=["parent", "status"],
2743
2743
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
2744
- assigned_object_id=IPAddress.objects.first().pk,
2744
+ assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
2745
2745
  )
2746
2746
  ObjectMetadata.objects.create(
2747
2747
  metadata_type=mdts[0],
2748
2748
  value="Hello",
2749
2749
  scoped_fields=["namespace"],
2750
2750
  assigned_object_type=ContentType.objects.get_for_model(Prefix),
2751
- assigned_object_id=Prefix.objects.first().pk,
2751
+ assigned_object_id=Prefix.objects.filter(associated_object_metadata__isnull=True).first().pk,
2752
2752
  )
2753
2753
  ObjectMetadata.objects.create(
2754
2754
  metadata_type=mdts[2],
2755
2755
  contact=Contact.objects.first(),
2756
2756
  scoped_fields=["status"],
2757
2757
  assigned_object_type=ContentType.objects.get_for_model(Prefix),
2758
- assigned_object_id=Prefix.objects.last().pk,
2758
+ assigned_object_id=Prefix.objects.filter(associated_object_metadata__isnull=True).last().pk,
2759
2759
  )
2760
2760
  cls.create_data = [
2761
2761
  {
@@ -2763,34 +2763,41 @@ class ObjectMetadataTest(APIViewTestCases.APIViewTestCase):
2763
2763
  "scoped_fields": ["location_type"],
2764
2764
  "value": "random words",
2765
2765
  "assigned_object_type": "dcim.location",
2766
- "assigned_object_id": Location.objects.first().pk,
2766
+ "assigned_object_id": Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
2767
2767
  },
2768
2768
  {
2769
2769
  "metadata_type": mdts[1].pk,
2770
2770
  "scoped_fields": ["name"],
2771
2771
  "value": "random words",
2772
2772
  "assigned_object_type": "dcim.location",
2773
- "assigned_object_id": Location.objects.first().pk,
2773
+ "assigned_object_id": Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
2774
2774
  },
2775
2775
  {
2776
2776
  "metadata_type": mdts[2].pk,
2777
- "scoped_fields": ["device_type"],
2777
+ "scoped_fields": [],
2778
2778
  "contact": Contact.objects.first().pk,
2779
2779
  "assigned_object_type": "dcim.device",
2780
- "assigned_object_id": Device.objects.first().pk,
2780
+ "assigned_object_id": Device.objects.filter(associated_object_metadata__isnull=True).first().pk,
2781
2781
  },
2782
2782
  {
2783
2783
  "metadata_type": mdts[2].pk,
2784
2784
  "scoped_fields": ["interfaces"],
2785
2785
  "team": Team.objects.first().pk,
2786
2786
  "assigned_object_type": "dcim.device",
2787
- "assigned_object_id": Device.objects.first().pk,
2787
+ "assigned_object_id": Device.objects.filter(associated_object_metadata__isnull=True).last().pk,
2788
2788
  },
2789
2789
  ]
2790
2790
  cls.update_data = {
2791
2791
  "scoped_fields": ["pk"],
2792
2792
  }
2793
2793
 
2794
+ def get_deletable_object(self):
2795
+ # TODO: CSV round-trip doesn't work for empty scoped_fields values at present. :-(
2796
+ instance = get_deletable_objects(self.model, self._get_queryset().exclude(scoped_fields=[])).first()
2797
+ if instance is None:
2798
+ self.fail("Couldn't find a single deletable object with non-empty scoped_fields")
2799
+ return instance
2800
+
2794
2801
 
2795
2802
  class NoteTest(APIViewTestCases.APIViewTestCase):
2796
2803
  model = Note
@@ -55,10 +55,10 @@ class JobTest(TestCase):
55
55
 
56
56
  self.assertInHTML(
57
57
  """<tr><th><label for="id_var_int">Var int:</label></th><td>
58
- <input class="form-control form-control" id="id_var_int" max="3600" name="var_int" placeholder="None" required type="number" value="0">
58
+ <input class="form-control" id="id_var_int" max="3600" name="var_int" placeholder="None" required type="number" value="0">
59
59
  <br><span class="helptext">Test default of 0 Falsey</span></td></tr>
60
60
  <tr><th><label for="id_var_int_no_default">Var int no default:</label></th><td>
61
- <input class="form-control form-control" id="id_var_int_no_default" max="3600" name="var_int_no_default" placeholder="None" type="number">
61
+ <input class="form-control" id="id_var_int_no_default" max="3600" name="var_int_no_default" placeholder="None" type="number">
62
62
  <br><span class="helptext">Test default without default</span></td></tr>""",
63
63
  form.as_table(),
64
64
  )