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
@@ -1352,7 +1352,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1352
1352
  value="Invalid assigned object type",
1353
1353
  scoped_fields=["status"],
1354
1354
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
1355
- assigned_object_id=Contact.objects.first().pk,
1355
+ assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
1356
1356
  )
1357
1357
  obj_metadata.validated_save()
1358
1358
 
@@ -1362,29 +1362,29 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1362
1362
  )
1363
1363
  type_contact_team.content_types.add(ContentType.objects.get_for_model(Contact))
1364
1364
  type_contact_team.content_types.add(ContentType.objects.get_for_model(Team))
1365
- instance1 = ObjectMetadata.objects.create(
1365
+ instance1 = ObjectMetadata(
1366
1366
  metadata_type=type_contact_team,
1367
1367
  contact=Contact.objects.first(),
1368
1368
  team=Team.objects.first(),
1369
1369
  scoped_fields=["address"],
1370
1370
  assigned_object_type=ContentType.objects.get_for_model(Contact),
1371
- assigned_object_id=Contact.objects.first().pk,
1371
+ assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
1372
1372
  )
1373
- instance2 = ObjectMetadata.objects.create(
1373
+ instance2 = ObjectMetadata(
1374
1374
  metadata_type=type_contact_team,
1375
1375
  contact=None,
1376
1376
  team=None,
1377
1377
  scoped_fields=["phone"],
1378
1378
  assigned_object_type=ContentType.objects.get_for_model(Contact),
1379
- assigned_object_id=Contact.objects.last().pk,
1379
+ assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).last().pk,
1380
1380
  )
1381
- instance3 = ObjectMetadata.objects.create(
1381
+ instance3 = ObjectMetadata(
1382
1382
  metadata_type=type_contact_team,
1383
1383
  contact=Contact.objects.first(),
1384
1384
  team=None,
1385
1385
  scoped_fields=["email"],
1386
1386
  assigned_object_type=ContentType.objects.get_for_model(Team),
1387
- assigned_object_id=Team.objects.first().pk,
1387
+ assigned_object_id=Team.objects.filter(associated_object_metadata__isnull=True).first().pk,
1388
1388
  )
1389
1389
  with self.assertRaises(ValidationError):
1390
1390
  instance1.validated_save()
@@ -1407,7 +1407,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1407
1407
  value="Some text value",
1408
1408
  scoped_fields=["status", "parent"],
1409
1409
  assigned_object_type=obj_type,
1410
- assigned_object_id=Location.objects.first().pk,
1410
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1411
1411
  )
1412
1412
  obj_metadata.save()
1413
1413
 
@@ -1440,7 +1440,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1440
1440
  value=15,
1441
1441
  scoped_fields=["status", "parent"],
1442
1442
  assigned_object_type=obj_type,
1443
- assigned_object_id=Location.objects.first().pk,
1443
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1444
1444
  )
1445
1445
  obj_metadata.validated_save()
1446
1446
 
@@ -1484,7 +1484,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1484
1484
  value=15.245,
1485
1485
  scoped_fields=["status", "parent"],
1486
1486
  assigned_object_type=obj_type,
1487
- assigned_object_id=Location.objects.first().pk,
1487
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1488
1488
  )
1489
1489
  obj_metadata.validated_save()
1490
1490
 
@@ -1525,7 +1525,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1525
1525
  value=False,
1526
1526
  scoped_fields=["status", "parent"],
1527
1527
  assigned_object_type=obj_type,
1528
- assigned_object_id=Location.objects.first().pk,
1528
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1529
1529
  )
1530
1530
  obj_metadata.validated_save()
1531
1531
 
@@ -1559,7 +1559,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1559
1559
  value="1994-01-01",
1560
1560
  scoped_fields=["status", "parent"],
1561
1561
  assigned_object_type=obj_type,
1562
- assigned_object_id=Location.objects.first().pk,
1562
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1563
1563
  )
1564
1564
  obj_metadata.validated_save()
1565
1565
 
@@ -1596,7 +1596,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1596
1596
  value="2024-06-27T17:58:47-0500",
1597
1597
  scoped_fields=["status", "parent"],
1598
1598
  assigned_object_type=obj_type,
1599
- assigned_object_id=Location.objects.first().pk,
1599
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1600
1600
  )
1601
1601
  obj_metadata.validated_save()
1602
1602
 
@@ -1663,7 +1663,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1663
1663
  value="Option A",
1664
1664
  scoped_fields=["status", "parent"],
1665
1665
  assigned_object_type=obj_type,
1666
- assigned_object_id=Location.objects.first().pk,
1666
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1667
1667
  )
1668
1668
  obj_metadata.validated_save()
1669
1669
 
@@ -1689,7 +1689,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1689
1689
  value=["Option A"],
1690
1690
  scoped_fields=["status", "parent"],
1691
1691
  assigned_object_type=obj_type,
1692
- assigned_object_id=Location.objects.first().pk,
1692
+ assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
1693
1693
  )
1694
1694
  obj_metadata.validated_save()
1695
1695
 
@@ -1700,20 +1700,22 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
1700
1700
  self.assertIn(f"Invalid choice(s) ({invalid_options})", str(context.exception))
1701
1701
 
1702
1702
  def test_no_scoped_fields_overlap(self):
1703
- """Test that overlapping between scoped_fields of ObjectMetadata with the same metadata_type and the same assigned_object is not allowed"""
1703
+ """
1704
+ Test that overlapping scoped_fields of ObjectMetadata with same metadata_type/assigned_object is not allowed.
1705
+ """
1704
1706
  ObjectMetadata.objects.create(
1705
1707
  metadata_type=MetadataType.objects.first(),
1706
1708
  contact=Contact.objects.first(),
1707
1709
  scoped_fields=["host", "mask_length", "type", "role", "status"],
1708
1710
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
1709
- assigned_object_id=IPAddress.objects.first().pk,
1711
+ assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
1710
1712
  )
1711
1713
  instance2 = ObjectMetadata.objects.create(
1712
1714
  metadata_type=MetadataType.objects.first(),
1713
1715
  contact=Contact.objects.first(),
1714
1716
  scoped_fields=[],
1715
1717
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
1716
- assigned_object_id=IPAddress.objects.first().pk,
1718
+ assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
1717
1719
  )
1718
1720
  with self.assertRaises(ValidationError):
1719
1721
  # try scope all fields
@@ -2594,7 +2594,7 @@ class JobTestCase(
2594
2594
 
2595
2595
  self.assertInHTML('<option value="uniquequeue" selected>', content)
2596
2596
  self.assertInHTML(
2597
- '<input type="text" name="var" value="456" class="form-control form-control" required placeholder="None" id="id_var">',
2597
+ '<input type="text" name="var" value="456" class="form-control" required placeholder="None" id="id_var">',
2598
2598
  content,
2599
2599
  )
2600
2600
  self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
@@ -2994,6 +2994,27 @@ class JobButtonRenderingTestCase(TestCase):
2994
2994
  )
2995
2995
 
2996
2996
 
2997
+ class JobCustomTemplateTestCase(TestCase):
2998
+ @classmethod
2999
+ def setUpTestData(cls):
3000
+ # Job model objects are automatically created during database migrations
3001
+
3002
+ # But we do need to make sure the ones we're testing are flagged appropriately
3003
+ cls.example_job = Job.objects.get(job_class_name="ExampleCustomFormJob")
3004
+ cls.example_job.enabled = True
3005
+ cls.example_job.save()
3006
+
3007
+ cls.run_url = reverse("extras:job_run", kwargs={"pk": cls.example_job.pk})
3008
+
3009
+ def test_rendering_custom_template(self):
3010
+ obj_perm = ObjectPermission(name="Test permission", actions=["view", "run"])
3011
+ obj_perm.save()
3012
+ obj_perm.users.add(self.user)
3013
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
3014
+ with self.assertTemplateUsed("example_app/custom_job_form.html"):
3015
+ self.client.get(self.run_url)
3016
+
3017
+
2997
3018
  # TODO: Convert to StandardTestCases.Views
2998
3019
  class ObjectChangeTestCase(TestCase):
2999
3020
  user_permissions = ("extras.view_objectchange",)
@@ -3029,8 +3050,7 @@ class ObjectChangeTestCase(TestCase):
3029
3050
 
3030
3051
 
3031
3052
  class ObjectMetadataTestCase(
3032
- ViewTestCases.DeleteObjectViewTestCase,
3033
- ViewTestCases.BulkDeleteObjectsViewTestCase,
3053
+ ViewTestCases.GetObjectViewTestCase,
3034
3054
  ViewTestCases.GetObjectChangelogViewTestCase,
3035
3055
  ViewTestCases.ListObjectsViewTestCase,
3036
3056
  ):
nautobot/extras/utils.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections
2
+ import contextlib
2
3
  import hashlib
3
4
  import hmac
4
5
  import logging
@@ -14,6 +15,7 @@ from django.db import transaction
14
15
  from django.db.models import Q
15
16
  from django.template.loader import get_template, TemplateDoesNotExist
16
17
  from django.utils.deconstruct import deconstructible
18
+ import redis.exceptions
17
19
 
18
20
  from nautobot.core.choices import ColorChoices
19
21
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
@@ -109,12 +111,17 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
109
111
  def change_logged_models_queryset():
110
112
  """
111
113
  Cacheable function for cases where we need this queryset many times, such as when saving multiple objects.
114
+
115
+ Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
112
116
  """
117
+ queryset = None
113
118
  cache_key = "nautobot.extras.utils.change_logged_models_queryset"
114
- queryset = cache.get(cache_key)
119
+ with contextlib.suppress(redis.exceptions.ConnectionError):
120
+ queryset = cache.get(cache_key)
115
121
  if queryset is None:
116
122
  queryset = ChangeLoggedModelsQuery().as_queryset()
117
- cache.set(cache_key, queryset)
123
+ with contextlib.suppress(redis.exceptions.ConnectionError):
124
+ cache.set(cache_key, queryset)
118
125
  return queryset
119
126
 
120
127
 
@@ -160,7 +167,7 @@ class FeatureQuery:
160
167
  """
161
168
  Given an extras feature, return a iterable of app_label: [models] for content type lookup.
162
169
 
163
- Mis-named, as it returns an iterable of (key, value) (i.e. dict.items()) rather than an actual dict.
170
+ Misnamed, as it returns an iterable of (key, value) (i.e. dict.items()) rather than an actual dict.
164
171
 
165
172
  Raises a KeyError if the given feature doesn't exist.
166
173
  """
@@ -173,12 +180,34 @@ class FeatureQuery:
173
180
 
174
181
  >>> FeatureQuery('statuses').get_choices()
175
182
  [('dcim.device', 13), ('dcim.rack', 34)]
183
+
184
+ Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
176
185
  """
177
- return [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
186
+ choices = None
187
+ cache_key = f"nautobot.extras.utils.FeatureQuery.choices.{self.feature}"
188
+ with contextlib.suppress(redis.exceptions.ConnectionError):
189
+ choices = cache.get(cache_key)
190
+ if choices is None:
191
+ choices = [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
192
+ with contextlib.suppress(redis.exceptions.ConnectionError):
193
+ cache.set(cache_key, choices)
194
+ return choices
178
195
 
179
196
  def list_subclasses(self):
180
- """Return a list of model classes that declare this feature."""
181
- return [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
197
+ """
198
+ Return a list of model classes that declare this feature.
199
+
200
+ Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
201
+ """
202
+ subclasses = None
203
+ cache_key = f"nautobot.extras.utils.FeatureQuery.subclasses.{self.feature}"
204
+ with contextlib.suppress(redis.exceptions.ConnectionError):
205
+ subclasses = cache.get(cache_key)
206
+ if subclasses is None:
207
+ subclasses = [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
208
+ with contextlib.suppress(redis.exceptions.ConnectionError):
209
+ cache.set(cache_key, subclasses)
210
+ return subclasses
182
211
 
183
212
 
184
213
  @deconstructible
nautobot/extras/views.py CHANGED
@@ -1,4 +1,3 @@
1
- from datetime import timedelta
2
1
  import logging
3
2
  from urllib.parse import parse_qs
4
3
 
@@ -1388,55 +1387,25 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
1388
1387
  schedule_type = schedule_form.cleaned_data["_schedule_type"]
1389
1388
 
1390
1389
  if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
1391
- crontab = ""
1392
-
1393
- if schedule_type == JobExecutionType.TYPE_IMMEDIATELY:
1394
- # The job must be approved.
1395
- # If the schedule_type is immediate, we still create the task, but mark it for approval
1396
- # as a once in the future task with the due date set to the current time. This means
1397
- # when approval is granted, the task is immediately due for execution.
1398
- schedule_type = JobExecutionType.TYPE_FUTURE
1399
- schedule_datetime = timezone.now()
1400
- schedule_name = f"{job_model} - {schedule_datetime}"
1401
-
1402
- else:
1403
- schedule_name = schedule_form.cleaned_data["_schedule_name"]
1404
-
1405
- if schedule_type == JobExecutionType.TYPE_CUSTOM:
1406
- crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
1407
- # doing .get("key", "default") returns None instead of "default" here for some reason
1408
- schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
1409
- if schedule_datetime is None:
1410
- # "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
1411
- # which returns timezone.now() + timedelta(seconds=15)
1412
- schedule_datetime = timezone.now() + timedelta(seconds=20)
1413
- else:
1414
- schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
1415
-
1416
- celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
1417
- scheduled_job = ScheduledJob(
1418
- name=schedule_name,
1419
- task=job_model.class_path,
1420
- job_model=job_model,
1421
- start_time=schedule_datetime,
1422
- description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
1423
- kwargs=job_class.serialize_data(job_form.cleaned_data),
1424
- celery_kwargs=celery_kwargs,
1390
+ scheduled_job = ScheduledJob.create_schedule(
1391
+ job_model,
1392
+ request.user,
1393
+ name=schedule_form.cleaned_data.get("_schedule_name"),
1394
+ start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
1425
1395
  interval=schedule_type,
1426
- one_off=schedule_type == JobExecutionType.TYPE_FUTURE,
1427
- queue=task_queue,
1428
- user=request.user,
1396
+ crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
1429
1397
  approval_required=job_model.approval_required,
1430
- crontab=crontab,
1398
+ task_queue=task_queue,
1399
+ profile=profile,
1400
+ **job_class.serialize_data(job_form.cleaned_data),
1431
1401
  )
1432
- scheduled_job.validated_save()
1433
1402
 
1434
1403
  if job_model.approval_required:
1435
- messages.success(request, f"Job {schedule_name} successfully submitted for approval")
1436
- return redirect(return_url if return_url else "extras:scheduledjob_approval_queue_list")
1404
+ messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
1405
+ return redirect(return_url or "extras:scheduledjob_approval_queue_list")
1437
1406
  else:
1438
- messages.success(request, f"Job {schedule_name} successfully scheduled")
1439
- return redirect(return_url if return_url else "extras:scheduledjob_list")
1407
+ messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
1408
+ return redirect(return_url or "extras:scheduledjob_list")
1440
1409
 
1441
1410
  else:
1442
1411
  # Enqueue job for immediate execution
@@ -1722,7 +1691,12 @@ class SavedViewUIViewSet(
1722
1691
  view_name = new_global_default_view.view
1723
1692
  message = ""
1724
1693
  if new_global_default_view.is_global_default:
1725
- message += f"<br>The global default saved View for '{view_name}' is set to <a href='{new_global_default_view.get_absolute_url()}'>{new_global_default_view.name}</a>."
1694
+ message = format_html(
1695
+ '<br>The global default saved view for "{}" is set to <a href="{}">{}</a>',
1696
+ view_name,
1697
+ new_global_default_view.get_absolute_url(),
1698
+ new_global_default_view.name,
1699
+ )
1726
1700
  return message
1727
1701
 
1728
1702
  def list(self, request, *args, **kwargs):
@@ -2076,8 +2050,13 @@ class JobLogEntryTableView(generic.GenericView):
2076
2050
  else:
2077
2051
  queryset = instance.job_log_entries.all()
2078
2052
  log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
2079
- RequestConfig(request).configure(log_table)
2080
- return HttpResponse(log_table.as_html(request))
2053
+ paginate = {
2054
+ "paginator_class": EnhancedPaginator,
2055
+ "per_page": get_paginate_count(request),
2056
+ }
2057
+ RequestConfig(request, paginate).configure(log_table)
2058
+ table = log_table.as_html(request)
2059
+ return HttpResponse(table)
2081
2060
 
2082
2061
 
2083
2062
  #
@@ -2237,15 +2216,13 @@ class MetadataTypeUIViewSet(NautobotUIViewSet):
2237
2216
 
2238
2217
 
2239
2218
  class ObjectMetadataUIViewSet(
2240
- ObjectBulkDestroyViewMixin,
2241
2219
  ObjectChangeLogViewMixin,
2242
- ObjectDestroyViewMixin,
2243
2220
  ObjectDetailViewMixin,
2244
2221
  ObjectListViewMixin,
2245
2222
  ):
2246
2223
  filterset_class = filters.ObjectMetadataFilterSet
2247
2224
  filterset_form_class = forms.ObjectMetadataFilterForm
2248
- queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "scoped_fields")
2225
+ queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "assigned_object_id", "scoped_fields")
2249
2226
  serializer_class = serializers.ObjectMetadataSerializer
2250
2227
  table_class = tables.ObjectMetadataTable
2251
2228
  action_buttons = ("export",)
@@ -2444,7 +2421,7 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
2444
2421
 
2445
2422
  if ContentType.objects.get_for_model(Prefix) in context["content_types"]:
2446
2423
  prefixes = instance.prefixes.restrict(request.user, "view")
2447
- prefix_table = PrefixTable(prefixes)
2424
+ prefix_table = PrefixTable(prefixes, hide_hierarchy_ui=True)
2448
2425
  prefix_table.columns.hide("role")
2449
2426
  RequestConfig(request, paginate).configure(prefix_table)
2450
2427
  context["prefix_table"] = prefix_table
nautobot/ipam/filters.py CHANGED
@@ -498,7 +498,7 @@ class IPAddressFilterSet(
498
498
  return queryset.none()
499
499
  interface_ids = []
500
500
  for device in devices:
501
- interface_ids.extend(device.all_interfaces.values_list("id", flat=True))
501
+ interface_ids.extend(device.vc_interfaces.values_list("id", flat=True))
502
502
  return queryset.filter(interfaces__in=interface_ids)
503
503
 
504
504
  def filter_virtual_machine(self, queryset, name, value):
nautobot/ipam/forms.py CHANGED
@@ -899,7 +899,7 @@ class ServiceForm(NautobotModelForm):
899
899
  # Limit IP address choices to those assigned to interfaces of the parent device/VM
900
900
  if self.instance.device:
901
901
  self.fields["ip_addresses"].queryset = IPAddress.objects.filter(
902
- interfaces__in=self.instance.device.all_interfaces.values_list("id", flat=True)
902
+ interfaces__in=self.instance.device.vc_interfaces.values_list("id", flat=True)
903
903
  )
904
904
  elif self.instance.virtual_machine:
905
905
  self.fields["ip_addresses"].queryset = IPAddress.objects.filter(
nautobot/ipam/models.py CHANGED
@@ -49,7 +49,6 @@ logger = logging.getLogger(__name__)
49
49
  @extras_features(
50
50
  "custom_links",
51
51
  "custom_validators",
52
- "dynamic_groups",
53
52
  "export_templates",
54
53
  "graphql",
55
54
  "locations",
@@ -399,7 +398,6 @@ class RIR(OrganizationalModel):
399
398
  @extras_features(
400
399
  "custom_links",
401
400
  "custom_validators",
402
- "dynamic_groups",
403
401
  "export_templates",
404
402
  "graphql",
405
403
  "locations",
@@ -501,11 +499,6 @@ class Prefix(PrimaryModel):
501
499
  "type",
502
500
  "vlan",
503
501
  ]
504
- """
505
- dynamic_group_filter_fields = {
506
- "vrf": "vrf_id", # Duplicate filter fields that will be collapsed in 2.0
507
- }
508
- """
509
502
 
510
503
  class Meta:
511
504
  ordering = (
@@ -986,7 +979,6 @@ class PrefixLocationAssignment(BaseModel):
986
979
  @extras_features(
987
980
  "custom_links",
988
981
  "custom_validators",
989
- "dynamic_groups",
990
982
  "export_templates",
991
983
  "graphql",
992
984
  "statuses",
@@ -1020,7 +1012,7 @@ class IPAddress(PrimaryModel):
1020
1012
  parent = models.ForeignKey(
1021
1013
  "ipam.Prefix",
1022
1014
  blank=True,
1023
- null=True,
1015
+ null=True, # TODO remove this, it shouldn't be permitted for the database!
1024
1016
  related_name="ip_addresses", # `IPAddress` to use `related_name="ip_addresses"`
1025
1017
  on_delete=models.PROTECT,
1026
1018
  help_text="The parent Prefix of this IPAddress.",
@@ -1117,7 +1109,7 @@ class IPAddress(PrimaryModel):
1117
1109
  raise ValidationError({"namespace": "No suitable parent Prefix exists in this Namespace"}) from e
1118
1110
 
1119
1111
  def clean(self):
1120
- super().clean()
1112
+ self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1121
1113
 
1122
1114
  # Validate that host is not being modified
1123
1115
  if self.present_in_database:
@@ -1131,8 +1123,8 @@ class IPAddress(PrimaryModel):
1131
1123
 
1132
1124
  closest_parent = self._get_closest_parent()
1133
1125
  # Validate `parent` can be used as the parent for this ipaddress
1134
- if self.parent and closest_parent:
1135
- if self.parent != closest_parent:
1126
+ if closest_parent is not None:
1127
+ if self.parent is not None and self.parent != closest_parent:
1136
1128
  raise ValidationError(
1137
1129
  {
1138
1130
  "parent": (
@@ -1144,23 +1136,20 @@ class IPAddress(PrimaryModel):
1144
1136
  self.parent = closest_parent
1145
1137
  self._namespace = None
1146
1138
 
1147
- def save(self, *args, **kwargs):
1148
1139
  # 3.0 TODO: uncomment the below to enforce this constraint
1149
1140
  # if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
1150
1141
  # err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
1151
1142
  # raise ValidationError({"address": err_msg})
1152
1143
 
1153
- self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1154
-
1155
1144
  # Force dns_name to lowercase
1156
1145
  if not self.dns_name.islower:
1157
1146
  self.dns_name = self.dns_name.lower()
1158
1147
 
1159
- # Host and mask_length are required to get closest parent
1160
- closest_parent = self._get_closest_parent()
1161
- if closest_parent is not None:
1162
- self.parent = closest_parent
1163
- self._namespace = None
1148
+ super().clean()
1149
+
1150
+ def save(self, *args, **kwargs):
1151
+ self.clean() # MUST do data fixup as above
1152
+
1164
1153
  super().save(*args, **kwargs)
1165
1154
 
1166
1155
  @property
@@ -1,6 +1,7 @@
1
1
  import re
2
2
 
3
3
  from django.core.exceptions import ValidationError
4
+ from django.core.validators import validate_ipv46_address
4
5
  from django.db.models import ProtectedError, Q
5
6
  import netaddr
6
7
 
@@ -397,6 +398,31 @@ class IPAddressQuerySet(BaseNetworkQuerySet):
397
398
  """
398
399
  return super().order_by("host")
399
400
 
401
+ def get_or_create(self, **kwargs):
402
+ from nautobot.ipam.models import get_default_namespace, Prefix
403
+
404
+ parent = kwargs.get("parent")
405
+ namespace = kwargs.pop("namespace", None)
406
+ host = kwargs.get("host")
407
+ mask_length = kwargs.get("mask_length")
408
+ # If `host` or `mask_length` is None skip; then there is no way of getting the closest parent;
409
+ if parent is None and host is not None and mask_length is not None:
410
+ if namespace is None:
411
+ namespace = get_default_namespace()
412
+ cidr = f"{host}/{mask_length}"
413
+
414
+ try:
415
+ validate_ipv46_address(host)
416
+ except ValidationError as err:
417
+ raise ValidationError({"host": err.error_list}) from err
418
+ try:
419
+ netaddr.IPNetwork(cidr)
420
+ except netaddr.AddrFormatError as err:
421
+ raise ValidationError(f"{cidr} does not appear to be an IPv4 or IPv6 network.") from err
422
+ parent = Prefix.objects.filter(namespace=namespace).get_closest_parent(cidr=cidr, include_self=True)
423
+ kwargs["parent"] = parent
424
+ return super().get_or_create(**kwargs)
425
+
400
426
  def string_search(self, search):
401
427
  """
402
428
  Interpret a search string and return useful results.
nautobot/ipam/tables.py CHANGED
@@ -355,6 +355,9 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
355
355
  location_count = LinkedCountColumn(
356
356
  viewname="dcim:location_list", url_params={"prefixes": "pk"}, verbose_name="Locations"
357
357
  )
358
+ cloud_networks_count = LinkedCountColumn(
359
+ viewname="cloud:cloudnetwork_list", url_params={"prefixes": "pk"}, verbose_name="Cloud Networks"
360
+ )
358
361
 
359
362
  class Meta(BaseTable.Meta):
360
363
  model = Prefix
@@ -368,6 +371,7 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
368
371
  "namespace",
369
372
  "tenant",
370
373
  "location_count",
374
+ "cloud_networks_count",
371
375
  "vlan",
372
376
  "role",
373
377
  "rir",