nautobot 2.3.0__py3-none-any.whl → 2.3.0b1__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 (366) hide show
  1. nautobot/cloud/factory.py +0 -2
  2. nautobot/cloud/filters.py +0 -3
  3. nautobot/cloud/forms.py +1 -7
  4. nautobot/cloud/migrations/0001_initial.py +1 -1
  5. nautobot/cloud/models.py +2 -1
  6. nautobot/cloud/tables.py +17 -1
  7. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +7 -1
  8. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -11
  9. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -4
  10. nautobot/cloud/tests/test_filters.py +0 -12
  11. nautobot/core/filters.py +1 -15
  12. nautobot/core/forms/forms.py +2 -10
  13. nautobot/core/graphql/generators.py +2 -2
  14. nautobot/core/graphql/schema.py +14 -6
  15. nautobot/core/jobs/__init__.py +1 -4
  16. nautobot/core/management/commands/generate_test_data.py +2 -2
  17. nautobot/core/models/__init__.py +2 -2
  18. nautobot/core/settings.py +2 -13
  19. nautobot/core/settings.yaml +2 -16
  20. nautobot/core/tables.py +0 -3
  21. nautobot/core/templates/generic/object_retrieve.html +5 -5
  22. nautobot/core/templates/nautobot_config.py.j2 +0 -15
  23. nautobot/core/testing/filters.py +1 -12
  24. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  25. nautobot/core/tests/test_jobs.py +1 -74
  26. nautobot/core/views/generic.py +1 -1
  27. nautobot/core/views/mixins.py +1 -1
  28. nautobot/core/views/utils.py +6 -8
  29. nautobot/dcim/factory.py +1 -4
  30. nautobot/dcim/filters/__init__.py +0 -4
  31. nautobot/dcim/forms.py +0 -5
  32. nautobot/dcim/migrations/0061_module_models.py +0 -1
  33. nautobot/dcim/models/device_components.py +0 -7
  34. nautobot/dcim/models/devices.py +4 -6
  35. nautobot/dcim/models/racks.py +1 -0
  36. nautobot/dcim/tables/devices.py +3 -17
  37. nautobot/dcim/tables/devicetypes.py +1 -1
  38. nautobot/dcim/templates/dcim/device/base.html +1 -1
  39. nautobot/dcim/templates/dcim/device.html +2 -2
  40. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
  41. nautobot/dcim/templates/dcim/moduletype_retrieve.html +0 -17
  42. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +2 -2
  43. nautobot/dcim/tests/test_api.py +0 -2
  44. nautobot/dcim/tests/test_filters.py +7 -14
  45. nautobot/dcim/tests/test_models.py +0 -31
  46. nautobot/dcim/tests/test_views.py +0 -39
  47. nautobot/dcim/views.py +1 -4
  48. nautobot/extras/api/views.py +59 -7
  49. nautobot/extras/factory.py +12 -50
  50. nautobot/extras/forms/base.py +4 -10
  51. nautobot/extras/homepage.py +2 -12
  52. nautobot/extras/jobs.py +2 -2
  53. nautobot/extras/migrations/0111_metadata.py +4 -4
  54. nautobot/extras/models/jobs.py +0 -83
  55. nautobot/extras/models/metadata.py +18 -18
  56. nautobot/extras/models/models.py +0 -2
  57. nautobot/extras/signals.py +1 -14
  58. nautobot/extras/tables.py +14 -43
  59. nautobot/extras/templates/extras/job_detail.html +0 -11
  60. nautobot/extras/tests/test_api.py +9 -16
  61. nautobot/extras/tests/test_jobs.py +2 -2
  62. nautobot/extras/tests/test_models.py +18 -20
  63. nautobot/extras/tests/test_views.py +3 -23
  64. nautobot/extras/utils.py +6 -35
  65. nautobot/extras/views.py +50 -27
  66. nautobot/ipam/filters.py +1 -1
  67. nautobot/ipam/forms.py +1 -1
  68. nautobot/ipam/models.py +20 -9
  69. nautobot/ipam/tables.py +0 -4
  70. nautobot/ipam/tests/test_models.py +2 -3
  71. nautobot/ipam/views.py +11 -6
  72. nautobot/project-static/css/base.css +0 -1
  73. nautobot/project-static/docs/404.html +18 -18
  74. nautobot/project-static/docs/apps/index.html +18 -18
  75. nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
  76. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +1 -0
  77. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +1 -0
  78. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
  79. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
  80. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +18 -66
  81. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
  82. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
  83. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
  84. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
  85. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
  86. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +18 -66
  87. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +18 -34
  88. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +18 -82
  89. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +21 -75
  90. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
  91. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +18 -34
  92. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +18 -34
  93. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
  94. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
  95. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
  96. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
  97. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
  98. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
  99. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +19 -21
  100. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +18 -34
  101. nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
  102. nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
  103. nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
  104. nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
  105. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
  106. nautobot/project-static/docs/development/apps/api/models/index.html +22 -33
  107. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
  108. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
  109. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
  110. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
  111. nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
  112. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
  113. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
  114. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
  115. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
  116. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
  117. nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
  118. nautobot/project-static/docs/development/apps/api/setup.html +18 -18
  119. nautobot/project-static/docs/development/apps/api/testing.html +18 -18
  120. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
  121. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
  122. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
  123. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
  124. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
  125. nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
  126. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
  127. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
  128. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
  129. nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
  130. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
  131. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
  132. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
  133. nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
  134. nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
  135. nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
  136. nautobot/project-static/docs/development/apps/index.html +18 -18
  137. nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
  138. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
  139. nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
  140. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
  141. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
  142. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
  143. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
  144. nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
  145. nautobot/project-static/docs/development/core/application-registry.html +18 -18
  146. nautobot/project-static/docs/development/core/best-practices.html +18 -18
  147. nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
  148. nautobot/project-static/docs/development/core/caching.html +18 -18
  149. nautobot/project-static/docs/development/core/controllers.html +18 -18
  150. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
  151. nautobot/project-static/docs/development/core/generic-views.html +18 -18
  152. nautobot/project-static/docs/development/core/getting-started.html +18 -18
  153. nautobot/project-static/docs/development/core/homepage.html +18 -18
  154. nautobot/project-static/docs/development/core/index.html +18 -29
  155. nautobot/project-static/docs/development/core/model-checklist.html +20 -26
  156. nautobot/project-static/docs/development/core/model-features.html +18 -18
  157. nautobot/project-static/docs/development/core/natural-keys.html +18 -18
  158. nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
  159. nautobot/project-static/docs/development/core/release-checklist.html +18 -18
  160. nautobot/project-static/docs/development/core/role-internals.html +18 -18
  161. nautobot/project-static/docs/development/core/settings.html +18 -18
  162. nautobot/project-static/docs/development/core/style-guide.html +19 -19
  163. nautobot/project-static/docs/development/core/templates.html +18 -18
  164. nautobot/project-static/docs/development/core/testing.html +18 -18
  165. nautobot/project-static/docs/development/core/user-preferences.html +18 -18
  166. nautobot/project-static/docs/development/index.html +18 -18
  167. nautobot/project-static/docs/development/jobs/index.html +379 -393
  168. nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
  169. nautobot/project-static/docs/index.html +13 -9032
  170. nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
  171. nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
  172. nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
  173. nautobot/project-static/docs/objects.inv +0 -0
  174. nautobot/project-static/docs/overview/application_stack.html +18 -18
  175. nautobot/project-static/docs/overview/design_philosophy.html +20 -20
  176. nautobot/project-static/docs/overview/index.html +9032 -13
  177. nautobot/project-static/docs/release-notes/index.html +19 -252
  178. nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
  179. nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
  180. nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
  181. nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
  182. nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
  183. nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
  184. nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
  185. nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
  186. nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
  187. nautobot/project-static/docs/release-notes/version-2.2.html +111 -248
  188. nautobot/project-static/docs/release-notes/version-2.3.html +90 -520
  189. nautobot/project-static/docs/requirements.txt +3 -3
  190. nautobot/project-static/docs/search/search_index.json +1 -1
  191. nautobot/project-static/docs/sitemap.xml +278 -278
  192. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  193. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
  194. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
  195. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
  196. nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
  197. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +20 -52
  198. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
  199. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
  200. nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
  201. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +18 -22
  202. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
  203. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
  204. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
  205. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
  206. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
  207. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
  208. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
  209. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
  210. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +82 -69
  211. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
  212. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +52 -60
  213. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +87 -80
  214. nautobot/project-static/docs/user-guide/administration/installation/services.html +44 -37
  215. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
  216. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
  217. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
  218. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
  219. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
  220. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -76
  221. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
  222. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
  223. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
  224. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
  225. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
  226. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
  227. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
  228. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
  229. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
  230. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
  231. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
  232. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
  233. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
  234. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
  235. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
  236. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
  237. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
  238. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
  239. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
  240. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
  241. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
  242. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
  284. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +18 -62
  285. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
  286. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
  287. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
  288. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
  289. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
  290. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
  291. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
  292. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
  293. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
  294. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
  295. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
  296. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
  297. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
  298. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
  299. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
  300. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
  301. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
  302. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
  303. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
  304. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
  305. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
  306. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
  312. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
  313. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
  314. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
  315. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
  316. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
  317. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
  318. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
  319. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
  320. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
  321. nautobot/project-static/docs/user-guide/index.html +18 -18
  322. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
  323. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
  324. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
  325. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
  326. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
  327. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
  328. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
  329. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
  330. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
  331. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
  332. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
  333. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
  334. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
  335. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
  336. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
  337. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
  338. nautobot/project-static/docs/user-guide/platform-functionality/{objectmetadata.html → metadata.html} +84 -197
  339. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
  340. nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
  341. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
  342. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
  343. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
  344. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
  345. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
  346. nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
  347. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
  348. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
  349. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
  350. nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
  351. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
  352. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
  353. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
  354. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
  355. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
  356. nautobot/tenancy/templates/tenancy/tenant.html +4 -4
  357. nautobot/virtualization/models.py +2 -0
  358. nautobot/virtualization/tables.py +5 -2
  359. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +3 -3
  360. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +364 -364
  361. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +0 -1
  362. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +0 -1
  363. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
  364. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
  365. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
  366. {nautobot-2.3.0.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
@@ -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" id="id_var_int" max="3600" name="var_int" placeholder="None" required type="number" value="0">
58
+ <input class="form-control 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" id="id_var_int_no_default" max="3600" name="var_int_no_default" placeholder="None" type="number">
61
+ <input class="form-control 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
  )
@@ -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.filter(associated_object_metadata__isnull=True).first().pk,
1355
+ assigned_object_id=Contact.objects.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(
1365
+ instance1 = ObjectMetadata.objects.create(
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.filter(associated_object_metadata__isnull=True).first().pk,
1371
+ assigned_object_id=Contact.objects.first().pk,
1372
1372
  )
1373
- instance2 = ObjectMetadata(
1373
+ instance2 = ObjectMetadata.objects.create(
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.filter(associated_object_metadata__isnull=True).last().pk,
1379
+ assigned_object_id=Contact.objects.last().pk,
1380
1380
  )
1381
- instance3 = ObjectMetadata(
1381
+ instance3 = ObjectMetadata.objects.create(
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.filter(associated_object_metadata__isnull=True).first().pk,
1387
+ assigned_object_id=Team.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1410
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1443
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1487
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1528
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1562
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1599
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1666
+ assigned_object_id=Location.objects.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.filter(associated_object_metadata__isnull=True).first().pk,
1692
+ assigned_object_id=Location.objects.first().pk,
1693
1693
  )
1694
1694
  obj_metadata.validated_save()
1695
1695
 
@@ -1700,22 +1700,20 @@ 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
- """
1704
- Test that overlapping scoped_fields of ObjectMetadata with same metadata_type/assigned_object is not allowed.
1705
- """
1703
+ """Test that overlapping between scoped_fields of ObjectMetadata with the same metadata_type and the same assigned_object is not allowed"""
1706
1704
  ObjectMetadata.objects.create(
1707
1705
  metadata_type=MetadataType.objects.first(),
1708
1706
  contact=Contact.objects.first(),
1709
1707
  scoped_fields=["host", "mask_length", "type", "role", "status"],
1710
1708
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
1711
- assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
1709
+ assigned_object_id=IPAddress.objects.first().pk,
1712
1710
  )
1713
1711
  instance2 = ObjectMetadata.objects.create(
1714
1712
  metadata_type=MetadataType.objects.first(),
1715
1713
  contact=Contact.objects.first(),
1716
1714
  scoped_fields=[],
1717
1715
  assigned_object_type=ContentType.objects.get_for_model(IPAddress),
1718
- assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
1716
+ assigned_object_id=IPAddress.objects.first().pk,
1719
1717
  )
1720
1718
  with self.assertRaises(ValidationError):
1721
1719
  # 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" required placeholder="None" id="id_var">',
2597
+ '<input type="text" name="var" value="456" class="form-control 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,27 +2994,6 @@ 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
-
3018
2997
  # TODO: Convert to StandardTestCases.Views
3019
2998
  class ObjectChangeTestCase(TestCase):
3020
2999
  user_permissions = ("extras.view_objectchange",)
@@ -3050,7 +3029,8 @@ class ObjectChangeTestCase(TestCase):
3050
3029
 
3051
3030
 
3052
3031
  class ObjectMetadataTestCase(
3053
- ViewTestCases.GetObjectViewTestCase,
3032
+ ViewTestCases.DeleteObjectViewTestCase,
3033
+ ViewTestCases.BulkDeleteObjectsViewTestCase,
3054
3034
  ViewTestCases.GetObjectChangelogViewTestCase,
3055
3035
  ViewTestCases.ListObjectsViewTestCase,
3056
3036
  ):
nautobot/extras/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import collections
2
- import contextlib
3
2
  import hashlib
4
3
  import hmac
5
4
  import logging
@@ -15,7 +14,6 @@ from django.db import transaction
15
14
  from django.db.models import Q
16
15
  from django.template.loader import get_template, TemplateDoesNotExist
17
16
  from django.utils.deconstruct import deconstructible
18
- import redis.exceptions
19
17
 
20
18
  from nautobot.core.choices import ColorChoices
21
19
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
@@ -111,17 +109,12 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
111
109
  def change_logged_models_queryset():
112
110
  """
113
111
  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).
116
112
  """
117
- queryset = None
118
113
  cache_key = "nautobot.extras.utils.change_logged_models_queryset"
119
- with contextlib.suppress(redis.exceptions.ConnectionError):
120
- queryset = cache.get(cache_key)
114
+ queryset = cache.get(cache_key)
121
115
  if queryset is None:
122
116
  queryset = ChangeLoggedModelsQuery().as_queryset()
123
- with contextlib.suppress(redis.exceptions.ConnectionError):
124
- cache.set(cache_key, queryset)
117
+ cache.set(cache_key, queryset)
125
118
  return queryset
126
119
 
127
120
 
@@ -167,7 +160,7 @@ class FeatureQuery:
167
160
  """
168
161
  Given an extras feature, return a iterable of app_label: [models] for content type lookup.
169
162
 
170
- Misnamed, as it returns an iterable of (key, value) (i.e. dict.items()) rather than an actual dict.
163
+ Mis-named, as it returns an iterable of (key, value) (i.e. dict.items()) rather than an actual dict.
171
164
 
172
165
  Raises a KeyError if the given feature doesn't exist.
173
166
  """
@@ -180,34 +173,12 @@ class FeatureQuery:
180
173
 
181
174
  >>> FeatureQuery('statuses').get_choices()
182
175
  [('dcim.device', 13), ('dcim.rack', 34)]
183
-
184
- Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
185
176
  """
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
177
+ return [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
195
178
 
196
179
  def list_subclasses(self):
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
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())]
211
182
 
212
183
 
213
184
  @deconstructible
nautobot/extras/views.py CHANGED
@@ -1,3 +1,4 @@
1
+ from datetime import timedelta
1
2
  import logging
2
3
  from urllib.parse import parse_qs
3
4
 
@@ -1387,25 +1388,55 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
1387
1388
  schedule_type = schedule_form.cleaned_data["_schedule_type"]
1388
1389
 
1389
1390
  if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
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"),
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,
1395
1425
  interval=schedule_type,
1396
- crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
1426
+ one_off=schedule_type == JobExecutionType.TYPE_FUTURE,
1427
+ queue=task_queue,
1428
+ user=request.user,
1397
1429
  approval_required=job_model.approval_required,
1398
- task_queue=task_queue,
1399
- profile=profile,
1400
- **job_class.serialize_data(job_form.cleaned_data),
1430
+ crontab=crontab,
1401
1431
  )
1432
+ scheduled_job.validated_save()
1402
1433
 
1403
1434
  if job_model.approval_required:
1404
- messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
1405
- return redirect(return_url or "extras:scheduledjob_approval_queue_list")
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")
1406
1437
  else:
1407
- messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
1408
- return redirect(return_url or "extras:scheduledjob_list")
1438
+ messages.success(request, f"Job {schedule_name} successfully scheduled")
1439
+ return redirect(return_url if return_url else "extras:scheduledjob_list")
1409
1440
 
1410
1441
  else:
1411
1442
  # Enqueue job for immediate execution
@@ -1691,12 +1722,7 @@ class SavedViewUIViewSet(
1691
1722
  view_name = new_global_default_view.view
1692
1723
  message = ""
1693
1724
  if new_global_default_view.is_global_default:
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
- )
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>."
1700
1726
  return message
1701
1727
 
1702
1728
  def list(self, request, *args, **kwargs):
@@ -2050,13 +2076,8 @@ class JobLogEntryTableView(generic.GenericView):
2050
2076
  else:
2051
2077
  queryset = instance.job_log_entries.all()
2052
2078
  log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
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)
2079
+ RequestConfig(request).configure(log_table)
2080
+ return HttpResponse(log_table.as_html(request))
2060
2081
 
2061
2082
 
2062
2083
  #
@@ -2216,13 +2237,15 @@ class MetadataTypeUIViewSet(NautobotUIViewSet):
2216
2237
 
2217
2238
 
2218
2239
  class ObjectMetadataUIViewSet(
2240
+ ObjectBulkDestroyViewMixin,
2219
2241
  ObjectChangeLogViewMixin,
2242
+ ObjectDestroyViewMixin,
2220
2243
  ObjectDetailViewMixin,
2221
2244
  ObjectListViewMixin,
2222
2245
  ):
2223
2246
  filterset_class = filters.ObjectMetadataFilterSet
2224
2247
  filterset_form_class = forms.ObjectMetadataFilterForm
2225
- queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "assigned_object_id", "scoped_fields")
2248
+ queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "scoped_fields")
2226
2249
  serializer_class = serializers.ObjectMetadataSerializer
2227
2250
  table_class = tables.ObjectMetadataTable
2228
2251
  action_buttons = ("export",)
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.vc_interfaces.values_list("id", flat=True))
501
+ interface_ids.extend(device.all_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.vc_interfaces.values_list("id", flat=True)
902
+ interfaces__in=self.instance.device.all_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,6 +49,7 @@ logger = logging.getLogger(__name__)
49
49
  @extras_features(
50
50
  "custom_links",
51
51
  "custom_validators",
52
+ "dynamic_groups",
52
53
  "export_templates",
53
54
  "graphql",
54
55
  "locations",
@@ -398,6 +399,7 @@ class RIR(OrganizationalModel):
398
399
  @extras_features(
399
400
  "custom_links",
400
401
  "custom_validators",
402
+ "dynamic_groups",
401
403
  "export_templates",
402
404
  "graphql",
403
405
  "locations",
@@ -499,6 +501,11 @@ class Prefix(PrimaryModel):
499
501
  "type",
500
502
  "vlan",
501
503
  ]
504
+ """
505
+ dynamic_group_filter_fields = {
506
+ "vrf": "vrf_id", # Duplicate filter fields that will be collapsed in 2.0
507
+ }
508
+ """
502
509
 
503
510
  class Meta:
504
511
  ordering = (
@@ -979,6 +986,7 @@ class PrefixLocationAssignment(BaseModel):
979
986
  @extras_features(
980
987
  "custom_links",
981
988
  "custom_validators",
989
+ "dynamic_groups",
982
990
  "export_templates",
983
991
  "graphql",
984
992
  "statuses",
@@ -1012,7 +1020,7 @@ class IPAddress(PrimaryModel):
1012
1020
  parent = models.ForeignKey(
1013
1021
  "ipam.Prefix",
1014
1022
  blank=True,
1015
- null=True, # TODO remove this, it shouldn't be permitted for the database!
1023
+ null=True,
1016
1024
  related_name="ip_addresses", # `IPAddress` to use `related_name="ip_addresses"`
1017
1025
  on_delete=models.PROTECT,
1018
1026
  help_text="The parent Prefix of this IPAddress.",
@@ -1109,7 +1117,7 @@ class IPAddress(PrimaryModel):
1109
1117
  raise ValidationError({"namespace": "No suitable parent Prefix exists in this Namespace"}) from e
1110
1118
 
1111
1119
  def clean(self):
1112
- self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1120
+ super().clean()
1113
1121
 
1114
1122
  # Validate that host is not being modified
1115
1123
  if self.present_in_database:
@@ -1123,8 +1131,8 @@ class IPAddress(PrimaryModel):
1123
1131
 
1124
1132
  closest_parent = self._get_closest_parent()
1125
1133
  # Validate `parent` can be used as the parent for this ipaddress
1126
- if closest_parent is not None:
1127
- if self.parent is not None and self.parent != closest_parent:
1134
+ if self.parent and closest_parent:
1135
+ if self.parent != closest_parent:
1128
1136
  raise ValidationError(
1129
1137
  {
1130
1138
  "parent": (
@@ -1136,20 +1144,23 @@ class IPAddress(PrimaryModel):
1136
1144
  self.parent = closest_parent
1137
1145
  self._namespace = None
1138
1146
 
1147
+ def save(self, *args, **kwargs):
1139
1148
  # 3.0 TODO: uncomment the below to enforce this constraint
1140
1149
  # if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
1141
1150
  # err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
1142
1151
  # raise ValidationError({"address": err_msg})
1143
1152
 
1153
+ self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1154
+
1144
1155
  # Force dns_name to lowercase
1145
1156
  if not self.dns_name.islower:
1146
1157
  self.dns_name = self.dns_name.lower()
1147
1158
 
1148
- super().clean()
1149
-
1150
- def save(self, *args, **kwargs):
1151
- self.clean() # MUST do data fixup as above
1152
-
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
1153
1164
  super().save(*args, **kwargs)
1154
1165
 
1155
1166
  @property
nautobot/ipam/tables.py CHANGED
@@ -355,9 +355,6 @@ 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
- )
361
358
 
362
359
  class Meta(BaseTable.Meta):
363
360
  model = Prefix
@@ -371,7 +368,6 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
371
368
  "namespace",
372
369
  "tenant",
373
370
  "location_count",
374
- "cloud_networks_count",
375
371
  "vlan",
376
372
  "role",
377
373
  "rir",
@@ -1003,9 +1003,8 @@ class TestIPAddress(ModelTestCases.BaseModelTestCase):
1003
1003
  def test_duplicate_global_unique(self):
1004
1004
  """Test that duplicate IPs in the same Namespace raises an error."""
1005
1005
  IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1006
- duplicate_ip = IPAddress(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1007
- with self.assertRaises(ValidationError):
1008
- duplicate_ip.full_clean()
1006
+ with self.assertRaises(IntegrityError):
1007
+ IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1009
1008
 
1010
1009
  def test_multiple_nat_outside_list(self):
1011
1010
  """
nautobot/ipam/views.py CHANGED
@@ -15,7 +15,7 @@ from django.views.generic import View
15
15
  from django_tables2 import RequestConfig
16
16
  import netaddr
17
17
 
18
- from nautobot.cloud.tables import CloudNetworkTable
18
+ from nautobot.cloud.tables import CloudNetworkPrefixAssignmentTable
19
19
  from nautobot.core.models.querysets import count_related
20
20
  from nautobot.core.utils.config import get_settings_or_config
21
21
  from nautobot.core.utils.permissions import get_permission_for_model
@@ -456,9 +456,8 @@ class PrefixView(generic.ObjectView):
456
456
  vrfs = instance.vrf_assignments.restrict(request.user, "view")
457
457
  vrf_table = tables.VRFPrefixAssignmentTable(vrfs, orderable=False)
458
458
 
459
- cloud_networks = instance.cloud_networks.restrict(request.user, "view")
460
- cloud_network_table = CloudNetworkTable(cloud_networks, orderable=False)
461
- cloud_network_table.exclude = ("actions", "assigned_prefix_count", "circuit_count", "cloud_service_count")
459
+ cloud_networks = instance.cloud_network_assignments.restrict(request.user, "view")
460
+ cloud_network_table = CloudNetworkPrefixAssignmentTable(cloud_networks, orderable=False)
462
461
 
463
462
  return {
464
463
  "vrf_table": vrf_table,
@@ -935,8 +934,14 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
935
934
  ip_addresses = IPAddress.objects.restrict(request.user, "view").filter(pk__in=pks)
936
935
  interface.ip_addresses.add(*ip_addresses)
937
936
  return redirect(self.get_return_url(request))
938
- messages.error(request, "Please select at least one IP Address from the table.")
939
- return redirect(request.get_full_path())
937
+
938
+ return render(
939
+ request,
940
+ "ipam/ipaddress_assign.html",
941
+ {
942
+ "return_url": self.get_return_url(request),
943
+ },
944
+ )
940
945
 
941
946
 
942
947
  class IPAddressMergeView(view_mixins.GetReturnURLMixin, view_mixins.ObjectPermissionRequiredMixin, View):
@@ -300,7 +300,6 @@ table.report th a {
300
300
  }
301
301
  .panel table {
302
302
  margin-bottom: 0;
303
- overflow: hidden;
304
303
  }
305
304
  .panel .table th {
306
305
  border-bottom-width: 1px;