nautobot 2.2.8__py3-none-any.whl → 2.2.9__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 (299) hide show
  1. nautobot/core/filters.py +15 -1
  2. nautobot/core/graphql/generators.py +2 -2
  3. nautobot/core/graphql/schema.py +2 -3
  4. nautobot/core/jobs/__init__.py +4 -1
  5. nautobot/core/settings.py +13 -2
  6. nautobot/core/settings.yaml +14 -0
  7. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  8. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  9. nautobot/core/tests/test_jobs.py +82 -2
  10. nautobot/core/tests/test_templatetags_netutils.py +3 -3
  11. nautobot/dcim/models/device_components.py +7 -0
  12. nautobot/dcim/models/devices.py +4 -0
  13. nautobot/dcim/tables/devices.py +19 -4
  14. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  15. nautobot/dcim/tests/test_models.py +31 -0
  16. nautobot/dcim/tests/test_views.py +13 -0
  17. nautobot/dcim/views.py +8 -2
  18. nautobot/extras/api/views.py +7 -59
  19. nautobot/extras/homepage.py +12 -2
  20. nautobot/extras/jobs.py +2 -2
  21. nautobot/extras/models/jobs.py +81 -0
  22. nautobot/extras/signals.py +14 -1
  23. nautobot/extras/tables.py +36 -5
  24. nautobot/extras/templates/extras/job_detail.html +11 -0
  25. nautobot/extras/tests/test_views.py +21 -0
  26. nautobot/extras/utils.py +34 -5
  27. nautobot/extras/views.py +20 -46
  28. nautobot/ipam/models.py +9 -12
  29. nautobot/ipam/tests/test_models.py +3 -2
  30. nautobot/ipam/views.py +2 -8
  31. nautobot/project-static/css/base.css +1 -0
  32. nautobot/project-static/docs/404.html +2 -2
  33. nautobot/project-static/docs/apps/index.html +2 -2
  34. nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
  35. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
  36. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
  37. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
  38. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
  39. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
  40. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
  41. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
  42. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
  43. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
  44. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
  45. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
  46. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
  47. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
  48. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
  49. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
  50. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
  51. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
  52. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
  53. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
  54. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
  55. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
  56. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +4 -2
  57. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
  58. nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
  59. nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
  60. nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
  61. nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
  62. nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
  63. nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
  64. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
  65. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
  66. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
  67. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
  68. nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
  69. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
  70. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
  71. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
  72. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
  73. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
  74. nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
  75. nautobot/project-static/docs/development/apps/api/setup.html +2 -2
  76. nautobot/project-static/docs/development/apps/api/testing.html +2 -2
  77. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
  78. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
  79. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
  80. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
  81. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
  82. nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
  83. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
  84. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
  85. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
  86. nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
  87. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
  88. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
  89. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
  90. nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
  91. nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
  92. nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
  93. nautobot/project-static/docs/development/apps/index.html +2 -2
  94. nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
  95. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  96. nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
  97. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
  98. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
  99. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
  100. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
  101. nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
  102. nautobot/project-static/docs/development/core/application-registry.html +2 -2
  103. nautobot/project-static/docs/development/core/best-practices.html +2 -2
  104. nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
  105. nautobot/project-static/docs/development/core/caching.html +2 -2
  106. nautobot/project-static/docs/development/core/controllers.html +2 -2
  107. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
  108. nautobot/project-static/docs/development/core/generic-views.html +2 -2
  109. nautobot/project-static/docs/development/core/getting-started.html +2 -2
  110. nautobot/project-static/docs/development/core/homepage.html +2 -2
  111. nautobot/project-static/docs/development/core/index.html +13 -2
  112. nautobot/project-static/docs/development/core/model-checklist.html +2 -2
  113. nautobot/project-static/docs/development/core/model-features.html +2 -2
  114. nautobot/project-static/docs/development/core/natural-keys.html +2 -2
  115. nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
  116. nautobot/project-static/docs/development/core/release-checklist.html +2 -2
  117. nautobot/project-static/docs/development/core/role-internals.html +2 -2
  118. nautobot/project-static/docs/development/core/settings.html +2 -2
  119. nautobot/project-static/docs/development/core/style-guide.html +2 -2
  120. nautobot/project-static/docs/development/core/templates.html +2 -2
  121. nautobot/project-static/docs/development/core/testing.html +2 -2
  122. nautobot/project-static/docs/development/core/user-preferences.html +2 -2
  123. nautobot/project-static/docs/development/index.html +2 -2
  124. nautobot/project-static/docs/development/jobs/index.html +377 -363
  125. nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
  126. nautobot/project-static/docs/index.html +8228 -13
  127. nautobot/project-static/docs/overview/application_stack.html +2 -2
  128. nautobot/project-static/docs/overview/design_philosophy.html +4 -4
  129. nautobot/project-static/docs/overview/index.html +13 -8228
  130. nautobot/project-static/docs/release-notes/index.html +2 -2
  131. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  132. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  133. nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
  134. nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
  135. nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
  136. nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
  137. nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
  138. nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
  139. nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
  140. nautobot/project-static/docs/release-notes/version-2.2.html +232 -95
  141. nautobot/project-static/docs/search/search_index.json +1 -1
  142. nautobot/project-static/docs/sitemap.xml +260 -260
  143. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  144. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
  145. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
  146. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
  147. nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
  148. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +34 -2
  149. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +2 -2
  150. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
  151. nautobot/project-static/docs/user-guide/administration/guides/caching.html +2 -2
  152. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +6 -2
  153. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +2 -2
  154. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
  155. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
  156. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
  157. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
  158. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  159. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
  160. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
  161. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
  162. nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
  163. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
  164. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
  165. nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
  166. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +2 -2
  167. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +2 -2
  168. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +2 -2
  169. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
  170. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
  171. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +60 -8
  172. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
  173. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
  174. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
  175. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
  176. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
  177. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
  180. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
  181. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
  182. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
  183. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
  184. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
  185. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
  186. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
  224. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
  225. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
  226. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
  227. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
  228. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
  229. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
  230. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
  231. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
  232. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
  233. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
  234. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
  235. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
  236. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
  237. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
  238. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
  239. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
  240. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
  241. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
  242. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
  243. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
  244. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
  245. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
  246. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
  247. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
  248. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
  249. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
  250. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
  251. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
  252. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
  253. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
  254. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
  255. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
  256. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
  257. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
  258. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
  259. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
  260. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
  261. nautobot/project-static/docs/user-guide/index.html +2 -2
  262. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
  263. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
  264. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
  265. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
  266. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
  267. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
  268. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
  269. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
  270. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
  271. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
  272. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
  273. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
  274. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
  275. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
  276. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
  277. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
  278. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
  279. nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
  280. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
  281. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
  282. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
  283. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
  284. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
  285. nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
  286. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
  287. nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
  288. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
  289. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
  290. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
  291. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
  292. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
  293. nautobot/virtualization/tables.py +2 -5
  294. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/METADATA +2 -2
  295. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/RECORD +299 -299
  296. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
  297. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
  298. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
  299. {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/entry_points.txt +0 -0
@@ -1074,6 +1074,87 @@ class ScheduledJob(BaseModel):
1074
1074
  day_of_week=day_of_week,
1075
1075
  )
1076
1076
 
1077
+ @classmethod
1078
+ def create_schedule(
1079
+ cls,
1080
+ job_model,
1081
+ user,
1082
+ name=None,
1083
+ start_time=None,
1084
+ interval=JobExecutionType.TYPE_IMMEDIATELY,
1085
+ crontab="",
1086
+ profile=False,
1087
+ approval_required=False,
1088
+ task_queue=None,
1089
+ **job_kwargs,
1090
+ ):
1091
+ """
1092
+ Schedule a job with the specified parameters.
1093
+
1094
+ This method creates a schedule for a job to be executed at a specific time
1095
+ or interval. It handles immediate execution, custom start times, and
1096
+ crontab-based scheduling.
1097
+
1098
+ Parameters:
1099
+ job_model (JobModel): The job model instance.
1100
+ user (User): The user who is scheduling the job.
1101
+ name (str, optional): The name of the scheduled job. Defaults to None.
1102
+ start_time (datetime, optional): The start time for the job. Defaults to None.
1103
+ interval (JobExecutionType, optional): The interval type for the job execution.
1104
+ Defaults to JobExecutionType.TYPE_IMMEDIATELY.
1105
+ crontab (str, optional): The crontab string for the schedule. Defaults to "".
1106
+ profile (bool, optional): Flag indicating whether to profile the job. Defaults to False.
1107
+ approval_required (bool, optional): Flag indicating if approval is required. Defaults to False.
1108
+ task_queue (str, optional): The task queue for the job. Defaults to None, which will use the configured default celery queue.
1109
+ **job_kwargs: Additional keyword arguments to pass to the job.
1110
+
1111
+ Returns:
1112
+ ScheduledJob instance
1113
+ """
1114
+
1115
+ if interval == JobExecutionType.TYPE_IMMEDIATELY:
1116
+ start_time = timezone.now()
1117
+ name = name or f"{job_model.name} - {start_time}"
1118
+ elif interval == JobExecutionType.TYPE_CUSTOM:
1119
+ if start_time is None:
1120
+ # "start_time" is checked against models.ScheduledJob.earliest_possible_time()
1121
+ # which returns timezone.now() + timedelta(seconds=15)
1122
+ start_time = timezone.now() + timedelta(seconds=20)
1123
+
1124
+ celery_kwargs = {
1125
+ "nautobot_job_profile": profile,
1126
+ "queue": task_queue,
1127
+ }
1128
+ if job_model.soft_time_limit > 0:
1129
+ celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
1130
+ if job_model.time_limit > 0:
1131
+ celery_kwargs["time_limit"] = job_model.time_limit
1132
+
1133
+ # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
1134
+ #
1135
+ # We pass in task and job_model here partly for forward/backward compatibility logic, and
1136
+ # part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
1137
+ # FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
1138
+ # null) you still have a bit of context on the ScheduledJob as to what it was originally
1139
+ # scheduled for.
1140
+ scheduled_job = cls(
1141
+ name=name,
1142
+ task=job_model.class_path,
1143
+ job_model=job_model,
1144
+ start_time=start_time,
1145
+ description=f"Nautobot job {name} scheduled by {user} for {start_time}",
1146
+ kwargs=job_kwargs,
1147
+ celery_kwargs=celery_kwargs,
1148
+ interval=interval,
1149
+ one_off=(interval == JobExecutionType.TYPE_FUTURE),
1150
+ user=user,
1151
+ approval_required=approval_required,
1152
+ crontab=crontab,
1153
+ queue=task_queue,
1154
+ )
1155
+ scheduled_job.validated_save()
1156
+ return scheduled_job
1157
+
1077
1158
  def to_cron(self):
1078
1159
  t = self.start_time
1079
1160
  if self.interval == JobExecutionType.TYPE_HOURLY:
@@ -15,7 +15,7 @@ from django.core.cache import cache
15
15
  from django.core.exceptions import ValidationError
16
16
  from django.core.files.storage import get_storage_class
17
17
  from django.db import transaction
18
- from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete, pre_save
18
+ from django.db.models.signals import m2m_changed, post_delete, post_migrate, post_save, pre_delete, pre_save
19
19
  from django.dispatch import receiver
20
20
  from django.utils import timezone
21
21
  from django_prometheus.models import model_deletes, model_inserts, model_updates
@@ -268,6 +268,19 @@ def _handle_deleted_object(sender, instance, **kwargs):
268
268
  model_deletes.labels(instance._meta.model_name).inc()
269
269
 
270
270
 
271
+ #
272
+ # Content types
273
+ #
274
+
275
+
276
+ @receiver(post_migrate)
277
+ def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs):
278
+ """Clear various content-type caches after a migration."""
279
+ with contextlib.suppress(redis.exceptions.ConnectionError):
280
+ cache.delete("nautobot.extras.utils.change_logged_models_queryset")
281
+ cache.delete_pattern("nautobot.extras.utils.FeatureQuery.*")
282
+
283
+
271
284
  #
272
285
  # Custom fields
273
286
  #
nautobot/extras/tables.py CHANGED
@@ -99,6 +99,7 @@ GITREPOSITORY_BUTTONS = """
99
99
 
100
100
  JOB_BUTTONS = """
101
101
  <a href="{% url 'extras:job' pk=record.pk %}" class="btn btn-default btn-xs" title="Details"><i class="mdi mdi-information-outline" aria-hidden="true"></i></a>
102
+ <a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="btn btn-default btn-xs" title="Job Results"><i class="mdi mdi-format-list-bulleted" aria-hidden="true"></i></a>
102
103
  """
103
104
 
104
105
  OBJECTCHANGE_OBJECT = """
@@ -639,7 +640,10 @@ class JobTable(BaseTable):
639
640
  return render_markdown(value)
640
641
 
641
642
  def render_name(self, value):
642
- return format_html('<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}', value)
643
+ return format_html(
644
+ '<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
645
+ value,
646
+ )
643
647
 
644
648
  class Meta(BaseTable.Meta):
645
649
  model = JobModel
@@ -811,7 +815,16 @@ class JobResultTable(BaseTable):
811
815
  "summary",
812
816
  "actions",
813
817
  )
814
- default_columns = ("pk", "date_created", "name", "job_model", "user", "status", "summary", "actions")
818
+ default_columns = (
819
+ "pk",
820
+ "date_created",
821
+ "name",
822
+ "job_model",
823
+ "user",
824
+ "status",
825
+ "summary",
826
+ "actions",
827
+ )
815
828
 
816
829
 
817
830
  class JobButtonTable(BaseTable):
@@ -953,7 +966,15 @@ class RelationshipAssociationTable(BaseTable):
953
966
 
954
967
  class Meta(BaseTable.Meta):
955
968
  model = RelationshipAssociation
956
- fields = ("pk", "relationship", "source_type", "source", "destination_type", "destination", "actions")
969
+ fields = (
970
+ "pk",
971
+ "relationship",
972
+ "source_type",
973
+ "source",
974
+ "destination_type",
975
+ "destination",
976
+ "actions",
977
+ )
957
978
  default_columns = ("pk", "relationship", "source", "destination", "actions")
958
979
 
959
980
 
@@ -1069,7 +1090,15 @@ class TagTable(BaseTable):
1069
1090
 
1070
1091
  class Meta(BaseTable.Meta):
1071
1092
  model = Tag
1072
- fields = ("pk", "name", "items", "color", "content_types", "description", "actions")
1093
+ fields = (
1094
+ "pk",
1095
+ "name",
1096
+ "items",
1097
+ "color",
1098
+ "content_types",
1099
+ "description",
1100
+ "actions",
1101
+ )
1073
1102
 
1074
1103
 
1075
1104
  class TaggedItemTable(BaseTable):
@@ -1149,7 +1178,9 @@ class WebhookTable(BaseTable):
1149
1178
  class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
1150
1179
  pk = ToggleColumn()
1151
1180
  contact_type = tables.TemplateColumn(
1152
- CONTACT_OR_TEAM_ICON, verbose_name="Type", attrs={"td": {"style": "width:20px;"}}
1181
+ CONTACT_OR_TEAM_ICON,
1182
+ verbose_name="Type",
1183
+ attrs={"td": {"style": "width:20px;"}},
1153
1184
  )
1154
1185
  name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
1155
1186
  contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team.phone", verbose_name="Phone")
@@ -115,6 +115,17 @@
115
115
  <td>{{ object.enabled | render_boolean }}</td>
116
116
  <td></td>
117
117
  </tr>
118
+ <tr>
119
+ <td>Job Results</td>
120
+ <td>
121
+ {% if object.job_results.exists %}
122
+ <a href="{% url 'extras:jobresult_list' %}?job_model={{ object.name | urlencode }}">{{ object.job_results.count }}</a>
123
+ {% else %}
124
+ {{ None|placeholder }}
125
+ {% endif %}
126
+ </td>
127
+ <td></td>
128
+ </tr>
118
129
  </table>
119
130
  </div>
120
131
  {% endblock content_left_page %}
@@ -2532,6 +2532,27 @@ class JobButtonRenderingTestCase(TestCase):
2532
2532
  )
2533
2533
 
2534
2534
 
2535
+ class JobCustomTemplateTestCase(TestCase):
2536
+ @classmethod
2537
+ def setUpTestData(cls):
2538
+ # Job model objects are automatically created during database migrations
2539
+
2540
+ # But we do need to make sure the ones we're testing are flagged appropriately
2541
+ cls.example_job = Job.objects.get(job_class_name="ExampleCustomFormJob")
2542
+ cls.example_job.enabled = True
2543
+ cls.example_job.save()
2544
+
2545
+ cls.run_url = reverse("extras:job_run", kwargs={"pk": cls.example_job.pk})
2546
+
2547
+ def test_rendering_custom_template(self):
2548
+ obj_perm = ObjectPermission(name="Test permission", actions=["view", "run"])
2549
+ obj_perm.save()
2550
+ obj_perm.users.add(self.user)
2551
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
2552
+ with self.assertTemplateUsed("example_app/custom_job_form.html"):
2553
+ self.client.get(self.run_url)
2554
+
2555
+
2535
2556
  # TODO: Convert to StandardTestCases.Views
2536
2557
  class ObjectChangeTestCase(TestCase):
2537
2558
  user_permissions = ("extras.view_objectchange",)
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
 
@@ -163,12 +170,34 @@ class FeatureQuery:
163
170
 
164
171
  >>> FeatureQuery('statuses').get_choices()
165
172
  [('dcim.device', 13), ('dcim.rack', 34)]
173
+
174
+ Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
166
175
  """
167
- return [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
176
+ choices = None
177
+ cache_key = f"nautobot.extras.utils.FeatureQuery.choices.{self.feature}"
178
+ with contextlib.suppress(redis.exceptions.ConnectionError):
179
+ choices = cache.get(cache_key)
180
+ if choices is None:
181
+ choices = [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
182
+ with contextlib.suppress(redis.exceptions.ConnectionError):
183
+ cache.set(cache_key, choices)
184
+ return choices
168
185
 
169
186
  def list_subclasses(self):
170
- """Return a list of model classes that declare this feature."""
171
- return [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
187
+ """
188
+ Return a list of model classes that declare this feature.
189
+
190
+ Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
191
+ """
192
+ subclasses = None
193
+ cache_key = f"nautobot.extras.utils.FeatureQuery.subclasses.{self.feature}"
194
+ with contextlib.suppress(redis.exceptions.ConnectionError):
195
+ subclasses = cache.get(cache_key)
196
+ if subclasses is None:
197
+ subclasses = [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
198
+ with contextlib.suppress(redis.exceptions.ConnectionError):
199
+ cache.set(cache_key, subclasses)
200
+ return subclasses
172
201
 
173
202
 
174
203
  @deconstructible
nautobot/extras/views.py CHANGED
@@ -1,4 +1,3 @@
1
- from datetime import timedelta
2
1
  import logging
3
2
 
4
3
  from celery import chain
@@ -1345,55 +1344,25 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
1345
1344
  schedule_type = schedule_form.cleaned_data["_schedule_type"]
1346
1345
 
1347
1346
  if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
1348
- crontab = ""
1349
-
1350
- if schedule_type == JobExecutionType.TYPE_IMMEDIATELY:
1351
- # The job must be approved.
1352
- # If the schedule_type is immediate, we still create the task, but mark it for approval
1353
- # as a once in the future task with the due date set to the current time. This means
1354
- # when approval is granted, the task is immediately due for execution.
1355
- schedule_type = JobExecutionType.TYPE_FUTURE
1356
- schedule_datetime = timezone.now()
1357
- schedule_name = f"{job_model} - {schedule_datetime}"
1358
-
1359
- else:
1360
- schedule_name = schedule_form.cleaned_data["_schedule_name"]
1361
-
1362
- if schedule_type == JobExecutionType.TYPE_CUSTOM:
1363
- crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
1364
- # doing .get("key", "default") returns None instead of "default" here for some reason
1365
- schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
1366
- if schedule_datetime is None:
1367
- # "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
1368
- # which returns timezone.now() + timedelta(seconds=15)
1369
- schedule_datetime = timezone.now() + timedelta(seconds=20)
1370
- else:
1371
- schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
1372
-
1373
- celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
1374
- scheduled_job = ScheduledJob(
1375
- name=schedule_name,
1376
- task=job_model.class_path,
1377
- job_model=job_model,
1378
- start_time=schedule_datetime,
1379
- description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
1380
- kwargs=job_class.serialize_data(job_form.cleaned_data),
1381
- celery_kwargs=celery_kwargs,
1347
+ scheduled_job = ScheduledJob.create_schedule(
1348
+ job_model,
1349
+ request.user,
1350
+ name=schedule_form.cleaned_data.get("_schedule_name"),
1351
+ start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
1382
1352
  interval=schedule_type,
1383
- one_off=schedule_type == JobExecutionType.TYPE_FUTURE,
1384
- queue=task_queue,
1385
- user=request.user,
1353
+ crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
1386
1354
  approval_required=job_model.approval_required,
1387
- crontab=crontab,
1355
+ task_queue=task_queue,
1356
+ profile=profile,
1357
+ **job_class.serialize_data(job_form.cleaned_data),
1388
1358
  )
1389
- scheduled_job.validated_save()
1390
1359
 
1391
1360
  if job_model.approval_required:
1392
- messages.success(request, f"Job {schedule_name} successfully submitted for approval")
1393
- return redirect(return_url if return_url else "extras:scheduledjob_approval_queue_list")
1361
+ messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
1362
+ return redirect(return_url or "extras:scheduledjob_approval_queue_list")
1394
1363
  else:
1395
- messages.success(request, f"Job {schedule_name} successfully scheduled")
1396
- return redirect(return_url if return_url else "extras:scheduledjob_list")
1364
+ messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
1365
+ return redirect(return_url or "extras:scheduledjob_list")
1397
1366
 
1398
1367
  else:
1399
1368
  # Enqueue job for immediate execution
@@ -1787,8 +1756,13 @@ class JobLogEntryTableView(generic.GenericView):
1787
1756
  else:
1788
1757
  queryset = instance.job_log_entries.all()
1789
1758
  log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
1790
- RequestConfig(request).configure(log_table)
1791
- return HttpResponse(log_table.as_html(request))
1759
+ paginate = {
1760
+ "paginator_class": EnhancedPaginator,
1761
+ "per_page": get_paginate_count(request),
1762
+ }
1763
+ RequestConfig(request, paginate).configure(log_table)
1764
+ table = log_table.as_html(request)
1765
+ return HttpResponse(table)
1792
1766
 
1793
1767
 
1794
1768
  #
nautobot/ipam/models.py CHANGED
@@ -1011,7 +1011,7 @@ class IPAddress(PrimaryModel):
1011
1011
  parent = models.ForeignKey(
1012
1012
  "ipam.Prefix",
1013
1013
  blank=True,
1014
- null=True,
1014
+ null=True, # TODO remove this, it shouldn't be permitted for the database!
1015
1015
  related_name="ip_addresses", # `IPAddress` to use `related_name="ip_addresses"`
1016
1016
  on_delete=models.PROTECT,
1017
1017
  help_text="The parent Prefix of this IPAddress.",
@@ -1108,7 +1108,7 @@ class IPAddress(PrimaryModel):
1108
1108
  raise ValidationError({"namespace": "No suitable parent Prefix exists in this Namespace"}) from e
1109
1109
 
1110
1110
  def clean(self):
1111
- super().clean()
1111
+ self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1112
1112
 
1113
1113
  # Validate that host is not being modified
1114
1114
  if self.present_in_database:
@@ -1122,8 +1122,8 @@ class IPAddress(PrimaryModel):
1122
1122
 
1123
1123
  closest_parent = self._get_closest_parent()
1124
1124
  # Validate `parent` can be used as the parent for this ipaddress
1125
- if self.parent and closest_parent:
1126
- if self.parent != closest_parent:
1125
+ if closest_parent is not None:
1126
+ if self.parent is not None and self.parent != closest_parent:
1127
1127
  raise ValidationError(
1128
1128
  {
1129
1129
  "parent": (
@@ -1135,23 +1135,20 @@ class IPAddress(PrimaryModel):
1135
1135
  self.parent = closest_parent
1136
1136
  self._namespace = None
1137
1137
 
1138
- def save(self, *args, **kwargs):
1139
1138
  # 3.0 TODO: uncomment the below to enforce this constraint
1140
1139
  # if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
1141
1140
  # err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
1142
1141
  # raise ValidationError({"address": err_msg})
1143
1142
 
1144
- self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
1145
-
1146
1143
  # Force dns_name to lowercase
1147
1144
  if not self.dns_name.islower:
1148
1145
  self.dns_name = self.dns_name.lower()
1149
1146
 
1150
- # Host and mask_length are required to get closest parent
1151
- closest_parent = self._get_closest_parent()
1152
- if closest_parent is not None:
1153
- self.parent = closest_parent
1154
- self._namespace = None
1147
+ super().clean()
1148
+
1149
+ def save(self, *args, **kwargs):
1150
+ self.clean() # MUST do data fixup as above
1151
+
1155
1152
  super().save(*args, **kwargs)
1156
1153
 
1157
1154
  @property
@@ -997,8 +997,9 @@ class TestIPAddress(ModelTestCases.BaseModelTestCase):
997
997
  def test_duplicate_global_unique(self):
998
998
  """Test that duplicate IPs in the same Namespace raises an error."""
999
999
  IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1000
- with self.assertRaises(IntegrityError):
1001
- IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1000
+ duplicate_ip = IPAddress(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
1001
+ with self.assertRaises(ValidationError):
1002
+ duplicate_ip.full_clean()
1002
1003
 
1003
1004
  def test_multiple_nat_outside_list(self):
1004
1005
  """
nautobot/ipam/views.py CHANGED
@@ -943,14 +943,8 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
943
943
  ip_addresses = IPAddress.objects.restrict(request.user, "view").filter(pk__in=pks)
944
944
  interface.ip_addresses.add(*ip_addresses)
945
945
  return redirect(self.get_return_url(request))
946
-
947
- return render(
948
- request,
949
- "ipam/ipaddress_assign.html",
950
- {
951
- "return_url": self.get_return_url(request),
952
- },
953
- )
946
+ messages.error(request, "Please select at least one IP Address from the table.")
947
+ return redirect(request.get_full_path())
954
948
 
955
949
 
956
950
  class IPAddressMergeView(view_mixins.GetReturnURLMixin, view_mixins.ObjectPermissionRequiredMixin, View):
@@ -299,6 +299,7 @@ table.report th a {
299
299
  }
300
300
  .panel table {
301
301
  margin-bottom: 0;
302
+ overflow: hidden;
302
303
  }
303
304
  .panel .table th {
304
305
  border-bottom-width: 1px;
@@ -218,7 +218,7 @@
218
218
 
219
219
 
220
220
  <li class="md-tabs__item">
221
- <a href="/projects/core/en/stable/overview/index.html" class="md-tabs__link">
221
+ <a href="/projects/core/en/stable/index.html" class="md-tabs__link">
222
222
 
223
223
 
224
224
 
@@ -387,7 +387,7 @@
387
387
 
388
388
 
389
389
  <div class="md-nav__link md-nav__container">
390
- <a href="/projects/core/en/stable/overview/index.html" class="md-nav__link ">
390
+ <a href="/projects/core/en/stable/index.html" class="md-nav__link ">
391
391
 
392
392
 
393
393
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../overview/index.html" class="md-tabs__link">
241
+ <a href="../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../overview/index.html" class="md-nav__link ">
412
+ <a href="../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">
@@ -236,7 +236,7 @@
236
236
 
237
237
 
238
238
  <li class="md-tabs__item">
239
- <a href="../overview/index.html" class="md-tabs__link">
239
+ <a href="../index.html" class="md-tabs__link">
240
240
 
241
241
 
242
242
 
@@ -407,7 +407,7 @@
407
407
 
408
408
 
409
409
  <div class="md-nav__link md-nav__container">
410
- <a href="../overview/index.html" class="md-nav__link ">
410
+ <a href="../index.html" class="md-nav__link ">
411
411
 
412
412
 
413
413
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../../../overview/index.html" class="md-tabs__link">
241
+ <a href="../../../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../../../overview/index.html" class="md-nav__link ">
412
+ <a href="../../../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../../../overview/index.html" class="md-tabs__link">
241
+ <a href="../../../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../../../overview/index.html" class="md-nav__link ">
412
+ <a href="../../../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../../../overview/index.html" class="md-tabs__link">
241
+ <a href="../../../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../../../overview/index.html" class="md-nav__link ">
412
+ <a href="../../../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../../../overview/index.html" class="md-tabs__link">
241
+ <a href="../../../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../../../overview/index.html" class="md-nav__link ">
412
+ <a href="../../../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">
@@ -238,7 +238,7 @@
238
238
 
239
239
 
240
240
  <li class="md-tabs__item">
241
- <a href="../../../overview/index.html" class="md-tabs__link">
241
+ <a href="../../../index.html" class="md-tabs__link">
242
242
 
243
243
 
244
244
 
@@ -409,7 +409,7 @@
409
409
 
410
410
 
411
411
  <div class="md-nav__link md-nav__container">
412
- <a href="../../../overview/index.html" class="md-nav__link ">
412
+ <a href="../../../index.html" class="md-nav__link ">
413
413
 
414
414
 
415
415
  <span class="md-ellipsis">